Skip to main content

diesel/connection/
transaction_manager.rs

1use crate::connection::Connection;
2use crate::result::{Error, QueryResult};
3use alloc::borrow::Cow;
4use alloc::boxed::Box;
5use core::num::NonZeroU32;
6
7/// Manages the internal transaction state for a connection.
8///
9/// You will not need to interact with this trait, unless you are writing an
10/// implementation of [`Connection`].
11pub trait TransactionManager<Conn: Connection> {
12    /// Data stored as part of the connection implementation
13    /// to track the current transaction state of a connection
14    type TransactionStateData;
15
16    /// Begin a new transaction or savepoint
17    ///
18    /// If the transaction depth is greater than 0,
19    /// this should create a savepoint instead.
20    /// This function is expected to increment the transaction depth by 1.
21    fn begin_transaction(conn: &mut Conn) -> QueryResult<()>;
22
23    /// Rollback the inner-most transaction or savepoint
24    ///
25    /// If the transaction depth is greater than 1,
26    /// this should rollback to the most recent savepoint.
27    /// This function is expected to decrement the transaction depth by 1.
28    fn rollback_transaction(conn: &mut Conn) -> QueryResult<()>;
29
30    /// Commit the inner-most transaction or savepoint
31    ///
32    /// If the transaction depth is greater than 1,
33    /// this should release the most recent savepoint.
34    /// This function is expected to decrement the transaction depth by 1.
35    fn commit_transaction(conn: &mut Conn) -> QueryResult<()>;
36
37    /// Fetch the current transaction status as mutable
38    ///
39    /// Used to ensure that `begin_test_transaction` is not called when already
40    /// inside of a transaction, and that operations are not run in a `InError`
41    /// transaction manager.
42    #[diesel_derives::__diesel_public_if(
43        feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes"
44    )]
45    fn transaction_manager_status_mut(conn: &mut Conn) -> &mut TransactionManagerStatus;
46
47    /// Executes the given function inside of a database transaction
48    ///
49    /// Each implementation of this function needs to fulfill the documented
50    /// behaviour of [`Connection::transaction`]
51    fn transaction<F, R, E>(conn: &mut Conn, callback: F) -> Result<R, E>
52    where
53        F: FnOnce(&mut Conn) -> Result<R, E>,
54        E: From<Error>,
55    {
56        Self::begin_transaction(conn)?;
57        match callback(&mut *conn) {
58            Ok(value) => {
59                Self::commit_transaction(conn)?;
60                Ok(value)
61            }
62            Err(user_error) => match Self::rollback_transaction(conn) {
63                Ok(()) => Err(user_error),
64                Err(Error::BrokenTransactionManager) => {
65                    // In this case we are probably more interested by the
66                    // original error, which likely caused this
67                    Err(user_error)
68                }
69                Err(rollback_error) => Err(rollback_error.into()),
70            },
71        }
72    }
73
74    /// This methods checks if the connection manager is considered to be broken
75    /// by connection pool implementations
76    ///
77    /// A connection manager is considered to be broken by default if it either
78    /// contains an open transaction (because you don't want to have connections
79    /// with open transactions in your pool) or when the transaction manager is
80    /// in an error state.
81    #[diesel_derives::__diesel_public_if(
82        feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes"
83    )]
84    fn is_broken_transaction_manager(conn: &mut Conn) -> bool {
85        match Self::transaction_manager_status_mut(conn).transaction_state() {
86            // all transactions are closed
87            // so we don't consider this connection broken
88            Ok(ValidTransactionManagerStatus {
89                in_transaction: None,
90            }) => false,
91            // The transaction manager is in an error state
92            // Therefore we consider this connection broken
93            Err(_) => true,
94            // The transaction manager contains a open transaction
95            // we do consider this connection broken
96            // if that transaction was not opened by `begin_test_transaction`
97            Ok(ValidTransactionManagerStatus {
98                in_transaction: Some(s),
99            }) => !s.test_transaction,
100        }
101    }
102}
103
104/// An implementation of `TransactionManager` which can be used for backends
105/// which use ANSI standard syntax for savepoints such as SQLite and PostgreSQL.
106#[derive(#[automatically_derived]
impl ::core::default::Default for AnsiTransactionManager {
    #[inline]
    fn default() -> AnsiTransactionManager {
        AnsiTransactionManager { status: ::core::default::Default::default() }
    }
}Default, #[automatically_derived]
impl ::core::fmt::Debug for AnsiTransactionManager {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::debug_struct_field1_finish(f,
            "AnsiTransactionManager", "status", &&self.status)
    }
}Debug)]
107pub struct AnsiTransactionManager {
108    pub(crate) status: TransactionManagerStatus,
109}
110
111/// Status of the transaction manager
112#[doc = " Status of the transaction manager"]
pub enum TransactionManagerStatus {

    /// Valid status, the manager can run operations
    Valid(ValidTransactionManagerStatus),

    /// Error status, probably following a broken connection. The manager will no longer run operations
    InError,
}#[diesel_derives::__diesel_public_if(
113    feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes"
114)]
115#[derive(#[automatically_derived]
impl ::core::fmt::Debug for TransactionManagerStatus {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        match self {
            TransactionManagerStatus::Valid(__self_0) =>
                ::core::fmt::Formatter::debug_tuple_field1_finish(f, "Valid",
                    &__self_0),
            TransactionManagerStatus::InError =>
                ::core::fmt::Formatter::write_str(f, "InError"),
        }
    }
}Debug)]
116pub enum TransactionManagerStatus {
117    /// Valid status, the manager can run operations
118    Valid(ValidTransactionManagerStatus),
119    /// Error status, probably following a broken connection. The manager will no longer run operations
120    InError,
121}
122
123impl Default for TransactionManagerStatus {
124    fn default() -> Self {
125        TransactionManagerStatus::Valid(ValidTransactionManagerStatus::default())
126    }
127}
128
129impl TransactionManagerStatus {
130    /// Returns the transaction depth if the transaction manager's status is valid, or returns
131    /// [`Error::BrokenTransactionManager`] if the transaction manager is in error.
132    pub fn transaction_depth(&self) -> QueryResult<Option<NonZeroU32>> {
133        match self {
134            TransactionManagerStatus::Valid(valid_status) => Ok(valid_status.transaction_depth()),
135            TransactionManagerStatus::InError => Err(Error::BrokenTransactionManager),
136        }
137    }
138
139    #[cfg(any(
140        feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes",
141        feature = "postgres",
142        feature = "mysql",
143        feature = "mariadb",
144        test
145    ))]
146    #[doc =
" If in transaction and transaction manager is not broken, registers that it\'s possible that"]
#[doc =
" the connection can not be used anymore until top-level transaction is rolled back."]
#[doc = ""]
#[doc =
" If that is registered, savepoints rollbacks will still be attempted, but failure to do so"]
#[doc = " will not result in an error. (Some may succeed, some may not.)"]
pub fn set_requires_rollback_maybe_up_to_top_level(&mut self, to: bool) {
    if let TransactionManagerStatus::Valid(ValidTransactionManagerStatus {
            in_transaction: Some(InTransactionStatus {
                requires_rollback_maybe_up_to_top_level, .. }) }) = self {
        *requires_rollback_maybe_up_to_top_level = to;
    }
}#[diesel_derives::__diesel_public_if(
147        feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes"
148    )]
149    /// If in transaction and transaction manager is not broken, registers that it's possible that
150    /// the connection can not be used anymore until top-level transaction is rolled back.
151    ///
152    /// If that is registered, savepoints rollbacks will still be attempted, but failure to do so
153    /// will not result in an error. (Some may succeed, some may not.)
154    pub(crate) fn set_requires_rollback_maybe_up_to_top_level(&mut self, to: bool) {
155        if let TransactionManagerStatus::Valid(ValidTransactionManagerStatus {
156            in_transaction:
157                Some(InTransactionStatus {
158                    requires_rollback_maybe_up_to_top_level,
159                    ..
160                }),
161        }) = self
162        {
163            *requires_rollback_maybe_up_to_top_level = to;
164        }
165    }
166
167    /// Sets the transaction manager status to InError
168    ///
169    /// Subsequent attempts to use transaction-related features will result in a
170    /// [`Error::BrokenTransactionManager`] error
171    pub fn set_in_error(&mut self) {
172        *self = TransactionManagerStatus::InError
173    }
174
175    /// Expose access to the inner transaction state
176    ///
177    /// This function returns an error if the Transaction manager is in a broken
178    /// state
179    #[doc = " Expose access to the inner transaction state"]
#[doc = ""]
#[doc =
" This function returns an error if the Transaction manager is in a broken"]
#[doc = " state"]
pub fn transaction_state(&mut self)
    -> QueryResult<&mut ValidTransactionManagerStatus> {
    match self {
        TransactionManagerStatus::Valid(valid_status) => Ok(valid_status),
        TransactionManagerStatus::InError =>
            Err(Error::BrokenTransactionManager),
    }
}#[diesel_derives::__diesel_public_if(
180        feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes"
181    )]
182    pub(self) fn transaction_state(&mut self) -> QueryResult<&mut ValidTransactionManagerStatus> {
183        match self {
184            TransactionManagerStatus::Valid(valid_status) => Ok(valid_status),
185            TransactionManagerStatus::InError => Err(Error::BrokenTransactionManager),
186        }
187    }
188
189    /// This function allows to flag a transaction manager
190    /// in such a way that it contains a test transaction.
191    ///
192    /// This will disable some checks in regards to open transactions
193    /// to allow `Connection::begin_test_transaction` to work with
194    /// pooled connections as well
195    #[doc = " This function allows to flag a transaction manager"]
#[doc = " in such a way that it contains a test transaction."]
#[doc = ""]
#[doc = " This will disable some checks in regards to open transactions"]
#[doc = " to allow `Connection::begin_test_transaction` to work with"]
#[doc = " pooled connections as well"]
pub fn set_test_transaction_flag(&mut self) {
    if let TransactionManagerStatus::Valid(ValidTransactionManagerStatus {
            in_transaction: Some(s) }) = self {
        s.test_transaction = true;
    }
}#[diesel_derives::__diesel_public_if(
196        feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes"
197    )]
198    pub(crate) fn set_test_transaction_flag(&mut self) {
199        if let TransactionManagerStatus::Valid(ValidTransactionManagerStatus {
200            in_transaction: Some(s),
201        }) = self
202        {
203            s.test_transaction = true;
204        }
205    }
206}
207
208/// Valid transaction status for the manager. Can return the current transaction depth
209#[allow(missing_copy_implementations)]
210#[derive(#[automatically_derived]
#[allow(missing_copy_implementations)]
impl ::core::fmt::Debug for ValidTransactionManagerStatus {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::debug_struct_field1_finish(f,
            "ValidTransactionManagerStatus", "in_transaction",
            &&self.in_transaction)
    }
}Debug, #[automatically_derived]
#[allow(missing_copy_implementations)]
impl ::core::default::Default for ValidTransactionManagerStatus {
    #[inline]
    fn default() -> ValidTransactionManagerStatus {
        ValidTransactionManagerStatus {
            in_transaction: ::core::default::Default::default(),
        }
    }
}Default)]
211#[doc =
" Valid transaction status for the manager. Can return the current transaction depth"]
#[allow(missing_copy_implementations)]
#[non_exhaustive]
pub struct ValidTransactionManagerStatus {
    #[doc = " Inner status, or `None` if no transaction is running"]
    pub in_transaction: Option<InTransactionStatus>,
}#[diesel_derives::__diesel_public_if(
212    feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes",
213    public_fields(in_transaction)
214)]
215pub struct ValidTransactionManagerStatus {
216    /// Inner status, or `None` if no transaction is running
217    in_transaction: Option<InTransactionStatus>,
218}
219
220/// Various status fields to track the status of
221/// a transaction manager with a started transaction
222#[allow(missing_copy_implementations)]
223#[derive(#[automatically_derived]
#[allow(missing_copy_implementations)]
impl ::core::fmt::Debug for InTransactionStatus {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::debug_struct_field3_finish(f,
            "InTransactionStatus", "transaction_depth",
            &self.transaction_depth,
            "requires_rollback_maybe_up_to_top_level",
            &self.requires_rollback_maybe_up_to_top_level, "test_transaction",
            &&self.test_transaction)
    }
}Debug)]
224#[doc = " Various status fields to track the status of"]
#[doc = " a transaction manager with a started transaction"]
#[allow(missing_copy_implementations)]
#[non_exhaustive]
pub struct InTransactionStatus {
    #[doc = " The current depth of nested transactions"]
    pub transaction_depth: NonZeroU32,
    #[doc =
    " If that is registered, savepoints rollbacks will still be attempted, but failure to do so"]
    #[doc = " will not result in an error. (Some may succeed, some may not.)"]
    pub requires_rollback_maybe_up_to_top_level: bool,
    #[doc = " Is this transaction manager status marked as test-transaction?"]
    pub test_transaction: bool,
}#[diesel_derives::__diesel_public_if(
225    feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes",
226    public_fields(
227        test_transaction,
228        transaction_depth,
229        requires_rollback_maybe_up_to_top_level
230    )
231)]
232pub struct InTransactionStatus {
233    /// The current depth of nested transactions
234    transaction_depth: NonZeroU32,
235    /// If that is registered, savepoints rollbacks will still be attempted, but failure to do so
236    /// will not result in an error. (Some may succeed, some may not.)
237    requires_rollback_maybe_up_to_top_level: bool,
238    /// Is this transaction manager status marked as test-transaction?
239    test_transaction: bool,
240}
241
242impl ValidTransactionManagerStatus {
243    /// Return the current transaction depth
244    ///
245    /// This value is `None` if no current transaction is running
246    /// otherwise the number of nested transactions is returned.
247    pub fn transaction_depth(&self) -> Option<NonZeroU32> {
248        self.in_transaction.as_ref().map(|it| it.transaction_depth)
249    }
250
251    /// Update the transaction depth by adding the value of the `transaction_depth_change` parameter if the `query` is
252    /// `Ok(())`
253    pub fn change_transaction_depth(
254        &mut self,
255        transaction_depth_change: TransactionDepthChange,
256    ) -> QueryResult<()> {
257        match (&mut self.in_transaction, transaction_depth_change) {
258            (Some(in_transaction), TransactionDepthChange::IncreaseDepth) => {
259                // Can be replaced with saturating_add directly on NonZeroU32 once
260                // <https://github.com/rust-lang/rust/issues/84186> is stable
261                in_transaction.transaction_depth =
262                    NonZeroU32::new(in_transaction.transaction_depth.get().saturating_add(1))
263                        .expect("nz + nz is always non-zero");
264                Ok(())
265            }
266            (Some(in_transaction), TransactionDepthChange::DecreaseDepth) => {
267                // This sets `transaction_depth` to `None` as soon as we reach zero
268                match NonZeroU32::new(in_transaction.transaction_depth.get() - 1) {
269                    Some(depth) => in_transaction.transaction_depth = depth,
270                    None => self.in_transaction = None,
271                }
272                Ok(())
273            }
274            (None, TransactionDepthChange::IncreaseDepth) => {
275                self.in_transaction = Some(InTransactionStatus {
276                    transaction_depth: NonZeroU32::new(1).expect("1 is non-zero"),
277                    requires_rollback_maybe_up_to_top_level: false,
278                    test_transaction: false,
279                });
280                Ok(())
281            }
282            (None, TransactionDepthChange::DecreaseDepth) => {
283                // We screwed up something somewhere
284                // we cannot decrease the transaction count if
285                // we are not inside a transaction
286                Err(Error::NotInTransaction)
287            }
288        }
289    }
290}
291
292/// Represents a change to apply to the depth of a transaction
293#[derive(#[automatically_derived]
impl ::core::fmt::Debug for TransactionDepthChange {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::write_str(f,
            match self {
                TransactionDepthChange::IncreaseDepth => "IncreaseDepth",
                TransactionDepthChange::DecreaseDepth => "DecreaseDepth",
            })
    }
}Debug, #[automatically_derived]
impl ::core::clone::Clone for TransactionDepthChange {
    #[inline]
    fn clone(&self) -> TransactionDepthChange { *self }
}Clone, #[automatically_derived]
impl ::core::marker::Copy for TransactionDepthChange { }Copy)]
294pub enum TransactionDepthChange {
295    /// Increase the depth of the transaction (corresponds to `BEGIN` or `SAVEPOINT`)
296    IncreaseDepth,
297    /// Decreases the depth of the transaction (corresponds to `COMMIT`/`RELEASE SAVEPOINT` or `ROLLBACK`)
298    DecreaseDepth,
299}
300
301impl AnsiTransactionManager {
302    fn get_transaction_state<Conn>(
303        conn: &mut Conn,
304    ) -> QueryResult<&mut ValidTransactionManagerStatus>
305    where
306        Conn: Connection<TransactionManager = Self>,
307    {
308        conn.transaction_state().status.transaction_state()
309    }
310
311    /// Begin a transaction with custom SQL
312    ///
313    /// This is used by connections to implement more complex transaction APIs
314    /// to set things such as isolation levels.
315    /// Returns an error if already inside of a transaction.
316    pub fn begin_transaction_sql<Conn>(conn: &mut Conn, sql: &str) -> QueryResult<()>
317    where
318        Conn: Connection<TransactionManager = Self>,
319    {
320        let state = Self::get_transaction_state(conn)?;
321        if let Some(_depth) = state.transaction_depth() {
322            return Err(Error::AlreadyInTransaction);
323        }
324        let instrumentation_depth = NonZeroU32::new(1);
325        // Keep remainder of this method in sync with `begin_transaction()`.
326
327        conn.instrumentation().on_connection_event(
328            super::instrumentation::InstrumentationEvent::BeginTransaction {
329                depth: instrumentation_depth.expect("We know that 1 is not zero"),
330            },
331        );
332        conn.batch_execute(sql)?;
333        Self::get_transaction_state(conn)?
334            .change_transaction_depth(TransactionDepthChange::IncreaseDepth)?;
335
336        Ok(())
337    }
338}
339
340impl<Conn> TransactionManager<Conn> for AnsiTransactionManager
341where
342    Conn: Connection<TransactionManager = Self>,
343{
344    type TransactionStateData = Self;
345
346    fn begin_transaction(conn: &mut Conn) -> QueryResult<()> {
347        let transaction_state = Self::get_transaction_state(conn)?;
348        let transaction_depth = transaction_state.transaction_depth();
349        let start_transaction_sql = match transaction_depth {
350            None => Cow::from("BEGIN"),
351            Some(transaction_depth) => Cow::from(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("SAVEPOINT diesel_savepoint_{0}",
                transaction_depth))
    })alloc::format!(
352                "SAVEPOINT diesel_savepoint_{transaction_depth}"
353            )),
354        };
355        let instrumentation_depth =
356            NonZeroU32::new(transaction_depth.map_or(0, NonZeroU32::get).wrapping_add(1));
357        let sql = &start_transaction_sql;
358        // Keep remainder of this method in sync with `begin_transaction_sql()`.
359
360        conn.instrumentation().on_connection_event(
361            super::instrumentation::InstrumentationEvent::BeginTransaction {
362                depth: instrumentation_depth.expect("Transaction depth is too large"),
363            },
364        );
365        conn.batch_execute(sql)?;
366        Self::get_transaction_state(conn)?
367            .change_transaction_depth(TransactionDepthChange::IncreaseDepth)?;
368
369        Ok(())
370    }
371
372    fn rollback_transaction(conn: &mut Conn) -> QueryResult<()> {
373        let transaction_state = Self::get_transaction_state(conn)?;
374
375        let (
376            (rollback_sql, rolling_back_top_level),
377            requires_rollback_maybe_up_to_top_level_before_execute,
378        ) = match transaction_state.in_transaction {
379            Some(ref in_transaction) => (
380                match in_transaction.transaction_depth.get() {
381                    1 => (Cow::Borrowed("ROLLBACK"), true),
382                    depth_gt1 => (
383                        Cow::Owned(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("ROLLBACK TO SAVEPOINT diesel_savepoint_{0}",
                depth_gt1 - 1))
    })alloc::format!(
384                            "ROLLBACK TO SAVEPOINT diesel_savepoint_{}",
385                            depth_gt1 - 1
386                        )),
387                        false,
388                    ),
389                },
390                in_transaction.requires_rollback_maybe_up_to_top_level,
391            ),
392            None => return Err(Error::NotInTransaction),
393        };
394        let depth = transaction_state
395            .transaction_depth()
396            .expect("We know that we are in a transaction here");
397        conn.instrumentation().on_connection_event(
398            super::instrumentation::InstrumentationEvent::RollbackTransaction { depth },
399        );
400
401        match conn.batch_execute(&rollback_sql) {
402            Ok(()) => {
403                match Self::get_transaction_state(conn)?
404                    .change_transaction_depth(TransactionDepthChange::DecreaseDepth)
405                {
406                    Ok(()) => {}
407                    Err(Error::NotInTransaction) if rolling_back_top_level => {
408                        // Transaction exit may have already been detected by connection
409                        // implementation. It's fine.
410                    }
411                    Err(e) => return Err(e),
412                }
413                Ok(())
414            }
415            Err(rollback_error) => {
416                let tm_status = Self::transaction_manager_status_mut(conn);
417                match tm_status {
418                    TransactionManagerStatus::Valid(ValidTransactionManagerStatus {
419                        in_transaction:
420                            Some(InTransactionStatus {
421                                transaction_depth,
422                                requires_rollback_maybe_up_to_top_level,
423                                ..
424                            }),
425                    }) if transaction_depth.get() > 1 => {
426                        // A savepoint failed to rollback - we may still attempt to repair
427                        // the connection by rolling back higher levels.
428
429                        // To make it easier on the user (that they don't have to really
430                        // look at actual transaction depth and can just rely on the number
431                        // of times they have called begin/commit/rollback) we still
432                        // decrement here:
433                        *transaction_depth = NonZeroU32::new(transaction_depth.get() - 1)
434                            .expect("Depth was checked to be > 1");
435                        *requires_rollback_maybe_up_to_top_level = true;
436                        if requires_rollback_maybe_up_to_top_level_before_execute {
437                            // In that case, we tolerate that savepoint releases fail
438                            // -> we should ignore errors
439                            return Ok(());
440                        }
441                    }
442                    TransactionManagerStatus::Valid(ValidTransactionManagerStatus {
443                        in_transaction: None,
444                    }) => {
445                        // we would have returned `NotInTransaction` if that was already the state
446                        // before we made our call
447                        // => Transaction manager status has been fixed by the underlying connection
448                        // so we don't need to set_in_error
449                    }
450                    _ => tm_status.set_in_error(),
451                }
452                Err(rollback_error)
453            }
454        }
455    }
456
457    /// If the transaction fails to commit due to a `SerializationFailure` or a
458    /// `ReadOnlyTransaction` a rollback will be attempted. If the rollback succeeds,
459    /// the original error will be returned, otherwise the error generated by the rollback
460    /// will be returned. In the second case the connection will be considered broken
461    /// as it contains a uncommitted unabortable open transaction.
462    fn commit_transaction(conn: &mut Conn) -> QueryResult<()> {
463        let transaction_state = Self::get_transaction_state(conn)?;
464        let transaction_depth = transaction_state.transaction_depth();
465        let (commit_sql, committing_top_level) = match transaction_depth {
466            None => return Err(Error::NotInTransaction),
467            Some(transaction_depth) if transaction_depth.get() == 1 => {
468                (Cow::Borrowed("COMMIT"), true)
469            }
470            Some(transaction_depth) => (
471                Cow::Owned(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("RELEASE SAVEPOINT diesel_savepoint_{0}",
                transaction_depth.get() - 1))
    })alloc::format!(
472                    "RELEASE SAVEPOINT diesel_savepoint_{}",
473                    transaction_depth.get() - 1
474                )),
475                false,
476            ),
477        };
478        let depth = transaction_state
479            .transaction_depth()
480            .expect("We know that we are in a transaction here");
481        conn.instrumentation().on_connection_event(
482            super::instrumentation::InstrumentationEvent::CommitTransaction { depth },
483        );
484        match conn.batch_execute(&commit_sql) {
485            Ok(()) => {
486                match Self::get_transaction_state(conn)?
487                    .change_transaction_depth(TransactionDepthChange::DecreaseDepth)
488                {
489                    Ok(()) => {}
490                    Err(Error::NotInTransaction) if committing_top_level => {
491                        // Transaction exit may have already been detected by connection.
492                        // It's fine
493                    }
494                    Err(e) => return Err(e),
495                }
496                Ok(())
497            }
498            Err(commit_error) => {
499                if let TransactionManagerStatus::Valid(ValidTransactionManagerStatus {
500                    in_transaction:
501                        Some(InTransactionStatus {
502                            requires_rollback_maybe_up_to_top_level: true,
503                            ..
504                        }),
505                }) = conn.transaction_state().status
506                {
507                    match Self::rollback_transaction(conn) {
508                        Ok(()) => {}
509                        Err(rollback_error) => {
510                            conn.transaction_state().status.set_in_error();
511                            return Err(Error::RollbackErrorOnCommit {
512                                rollback_error: Box::new(rollback_error),
513                                commit_error: Box::new(commit_error),
514                            });
515                        }
516                    }
517                }
518                Err(commit_error)
519            }
520        }
521    }
522
523    fn transaction_manager_status_mut(conn: &mut Conn) -> &mut TransactionManagerStatus {
524        &mut conn.transaction_state().status
525    }
526}
527
528#[cfg(test)]
529// that's a false positive for `panic!`/`assert!` on rust 2018
530#[allow(clippy::uninlined_format_args)]
531mod test {
532    // Mock connection.
533    mod mock {
534        use crate::connection::Instrumentation;
535        use crate::connection::transaction_manager::AnsiTransactionManager;
536        use crate::connection::{
537            Connection, ConnectionSealed, SimpleConnection, TransactionManager,
538        };
539        use crate::result::QueryResult;
540        use crate::test_helpers::TestConnection;
541        use std::collections::VecDeque;
542
543        pub(crate) struct MockConnection {
544            pub(crate) next_results: VecDeque<QueryResult<usize>>,
545            pub(crate) next_batch_execute_results: VecDeque<QueryResult<()>>,
546            pub(crate) top_level_requires_rollback_after_next_batch_execute: bool,
547            transaction_state: AnsiTransactionManager,
548            instrumentation: Option<Box<dyn Instrumentation>>,
549        }
550
551        impl SimpleConnection for MockConnection {
552            fn batch_execute(&mut self, _query: &str) -> QueryResult<()> {
553                let res = self
554                    .next_batch_execute_results
555                    .pop_front()
556                    .expect("No next result");
557                if self.top_level_requires_rollback_after_next_batch_execute {
558                    self.transaction_state
559                        .status
560                        .set_requires_rollback_maybe_up_to_top_level(true);
561                }
562                res
563            }
564        }
565
566        impl ConnectionSealed for MockConnection {}
567
568        impl Connection for MockConnection {
569            type Backend = <TestConnection as Connection>::Backend;
570
571            type TransactionManager = AnsiTransactionManager;
572
573            fn establish(_database_url: &str) -> crate::ConnectionResult<Self> {
574                Ok(Self {
575                    next_results: VecDeque::new(),
576                    next_batch_execute_results: VecDeque::new(),
577                    top_level_requires_rollback_after_next_batch_execute: false,
578                    transaction_state: AnsiTransactionManager::default(),
579                    instrumentation: None,
580                })
581            }
582
583            fn execute_returning_count<T>(&mut self, _source: &T) -> QueryResult<usize>
584            where
585                T: crate::query_builder::QueryFragment<Self::Backend>
586                    + crate::query_builder::QueryId,
587            {
588                self.next_results.pop_front().expect("No next result")
589            }
590
591            fn transaction_state(
592                &mut self,
593            ) -> &mut <Self::TransactionManager as TransactionManager<Self>>::TransactionStateData
594            {
595                &mut self.transaction_state
596            }
597
598            fn instrumentation(&mut self) -> &mut dyn crate::connection::Instrumentation {
599                &mut self.instrumentation
600            }
601
602            fn set_instrumentation(
603                &mut self,
604                instrumentation: impl crate::connection::Instrumentation,
605            ) {
606                self.instrumentation = Some(Box::new(instrumentation));
607            }
608
609            fn set_prepared_statement_cache_size(&mut self, _size: crate::connection::CacheSize) {
610                panic!("implement, if you want to use it")
611            }
612        }
613    }
614
615    #[diesel_test_helper::test]
616    #[cfg(feature = "postgres")]
617    fn transaction_manager_returns_an_error_when_attempting_to_commit_outside_of_a_transaction() {
618        use crate::PgConnection;
619        use crate::connection::transaction_manager::AnsiTransactionManager;
620        use crate::connection::transaction_manager::TransactionManager;
621        use crate::result::Error;
622
623        let conn = &mut crate::test_helpers::pg_connection_no_transaction();
624        assert_eq!(
625            None,
626            <AnsiTransactionManager as TransactionManager<PgConnection>>::transaction_manager_status_mut(
627                conn
628            ).transaction_depth().expect("Transaction depth")
629        );
630        let result = AnsiTransactionManager::commit_transaction(conn);
631        assert!(matches!(result, Err(Error::NotInTransaction)))
632    }
633
634    #[diesel_test_helper::test]
635    #[cfg(feature = "postgres")]
636    fn transaction_manager_returns_an_error_when_attempting_to_rollback_outside_of_a_transaction() {
637        use crate::PgConnection;
638        use crate::connection::transaction_manager::AnsiTransactionManager;
639        use crate::connection::transaction_manager::TransactionManager;
640        use crate::result::Error;
641
642        let conn = &mut crate::test_helpers::pg_connection_no_transaction();
643        assert_eq!(
644            None,
645            <AnsiTransactionManager as TransactionManager<PgConnection>>::transaction_manager_status_mut(
646                conn
647            ).transaction_depth().expect("Transaction depth")
648        );
649        let result = AnsiTransactionManager::rollback_transaction(conn);
650        assert!(matches!(result, Err(Error::NotInTransaction)))
651    }
652
653    #[diesel_test_helper::test]
654    fn transaction_manager_enters_broken_state_when_connection_is_broken() {
655        use crate::connection::TransactionManagerStatus;
656        use crate::connection::transaction_manager::AnsiTransactionManager;
657        use crate::connection::transaction_manager::TransactionManager;
658        use crate::result::{DatabaseErrorKind, Error};
659        use crate::*;
660
661        let mut conn = mock::MockConnection::establish("mock").expect("Mock connection");
662
663        // Set result for BEGIN
664        conn.next_batch_execute_results.push_back(Ok(()));
665        let result = conn.transaction(|conn| {
666            conn.next_results.push_back(Ok(1));
667            let query_result = sql_query("SELECT 1").execute(conn);
668            assert!(query_result.is_ok());
669            // Set result for COMMIT attempt
670            conn.next_batch_execute_results
671                .push_back(Err(Error::DatabaseError(
672                    DatabaseErrorKind::Unknown,
673                    Box::new("commit fails".to_string()),
674                )));
675            conn.top_level_requires_rollback_after_next_batch_execute = true;
676            conn.next_batch_execute_results
677                .push_back(Err(Error::DatabaseError(
678                    DatabaseErrorKind::Unknown,
679                    Box::new("rollback also fails".to_string()),
680                )));
681            Ok(())
682        });
683        assert!(
684            matches!(
685                &result,
686                Err(Error::RollbackErrorOnCommit {
687                    rollback_error,
688                    commit_error
689                }) if matches!(**commit_error, Error::DatabaseError(DatabaseErrorKind::Unknown, _))
690                    && matches!(&**rollback_error,
691                        Error::DatabaseError(DatabaseErrorKind::Unknown, msg)
692                            if msg.message() == "rollback also fails"
693                    )
694            ),
695            "Got {:?}",
696            result
697        );
698        assert!(matches!(
699            *AnsiTransactionManager::transaction_manager_status_mut(&mut conn),
700            TransactionManagerStatus::InError
701        ));
702        // Ensure the transaction manager is unusable
703        let result = conn.transaction(|_conn| Ok(()));
704        assert!(matches!(result, Err(Error::BrokenTransactionManager)))
705    }
706
707    #[diesel_test_helper::test]
708    #[cfg(feature = "mysql")]
709    fn mysql_transaction_is_rolled_back_upon_syntax_error() {
710        use crate::connection::transaction_manager::AnsiTransactionManager;
711        use crate::connection::transaction_manager::TransactionManager;
712        use crate::*;
713        use std::num::NonZeroU32;
714
715        let conn = &mut crate::test_helpers::connection_no_transaction();
716        assert_eq!(
717            None,
718            <AnsiTransactionManager as TransactionManager<MysqlConnection>>::transaction_manager_status_mut(
719                conn
720            ).transaction_depth().expect("Transaction depth")
721        );
722        let _result = conn.transaction(|conn| {
723            assert_eq!(
724                NonZeroU32::new(1),
725                <AnsiTransactionManager as TransactionManager<MysqlConnection>>::transaction_manager_status_mut(
726                    conn
727            ).transaction_depth().expect("Transaction depth")
728            );
729            // In MySQL, a syntax error does not break the transaction block
730            let query_result = sql_query("SELECT_SYNTAX_ERROR 1").execute(conn);
731            assert!(query_result.is_err());
732            query_result
733        });
734        assert_eq!(
735            None,
736            <AnsiTransactionManager as TransactionManager<MysqlConnection>>::transaction_manager_status_mut(
737                conn
738            ).transaction_depth().expect("Transaction depth")
739        );
740    }
741
742    #[diesel_test_helper::test]
743    #[cfg(feature = "__sqlite-shared")]
744    fn sqlite_transaction_is_rolled_back_upon_syntax_error() {
745        use crate::connection::transaction_manager::AnsiTransactionManager;
746        use crate::connection::transaction_manager::TransactionManager;
747        use crate::*;
748        use std::num::NonZeroU32;
749
750        let conn = &mut crate::test_helpers::connection();
751        assert_eq!(
752            None,
753            <AnsiTransactionManager as TransactionManager<SqliteConnection>>::transaction_manager_status_mut(
754                conn
755            ).transaction_depth().expect("Transaction depth")
756        );
757        let _result = conn.transaction(|conn| {
758            assert_eq!(
759                NonZeroU32::new(1),
760                <AnsiTransactionManager as TransactionManager<SqliteConnection>>::transaction_manager_status_mut(
761                    conn
762            ).transaction_depth().expect("Transaction depth")
763            );
764            // In Sqlite, a syntax error does not break the transaction block
765            let query_result = sql_query("SELECT_SYNTAX_ERROR 1").execute(conn);
766            assert!(query_result.is_err());
767            query_result
768        });
769        assert_eq!(
770            None,
771            <AnsiTransactionManager as TransactionManager<SqliteConnection>>::transaction_manager_status_mut(
772                conn
773            ).transaction_depth().expect("Transaction depth")
774        );
775    }
776
777    #[diesel_test_helper::test]
778    #[cfg(feature = "mysql")]
779    fn nested_mysql_transaction_is_rolled_back_upon_syntax_error() {
780        use crate::connection::transaction_manager::AnsiTransactionManager;
781        use crate::connection::transaction_manager::TransactionManager;
782        use crate::*;
783        use std::num::NonZeroU32;
784
785        let conn = &mut crate::test_helpers::connection_no_transaction();
786        assert_eq!(
787            None,
788            <AnsiTransactionManager as TransactionManager<MysqlConnection>>::transaction_manager_status_mut(
789                conn
790            ).transaction_depth().expect("Transaction depth")
791        );
792        let result = conn.transaction(|conn| {
793            assert_eq!(
794                NonZeroU32::new(1),
795                <AnsiTransactionManager as TransactionManager<MysqlConnection>>::transaction_manager_status_mut(
796                    conn
797            ).transaction_depth().expect("Transaction depth")
798            );
799            let result = conn.transaction(|conn| {
800                assert_eq!(
801                    NonZeroU32::new(2),
802                    <AnsiTransactionManager as TransactionManager<MysqlConnection>>::transaction_manager_status_mut(
803                        conn
804            ).transaction_depth().expect("Transaction depth")
805                );
806                // In MySQL, a syntax error does not break the transaction block
807                sql_query("SELECT_SYNTAX_ERROR 1").execute(conn)
808            });
809            assert!(result.is_err());
810            assert_eq!(
811                NonZeroU32::new(1),
812                <AnsiTransactionManager as TransactionManager<MysqlConnection>>::transaction_manager_status_mut(
813                    conn
814            ).transaction_depth().expect("Transaction depth")
815            );
816            let query_result = sql_query("SELECT 1").execute(conn);
817            assert!(query_result.is_ok());
818            query_result
819        });
820        assert!(result.is_ok());
821        assert_eq!(
822            None,
823            <AnsiTransactionManager as TransactionManager<MysqlConnection>>::transaction_manager_status_mut(
824                conn
825            ).transaction_depth().expect("Transaction depth")
826        );
827    }
828
829    #[diesel_test_helper::test]
830    #[cfg(feature = "mysql")]
831    // This function uses a collect with side effects (spawning threads)
832    // so clippy is wrong here
833    #[allow(clippy::needless_collect)]
834    fn mysql_transaction_depth_commits_tracked_properly_on_serialization_failure() {
835        use crate::result::DatabaseErrorKind::SerializationFailure;
836        use crate::result::Error::DatabaseError;
837        use crate::*;
838        use std::num::NonZeroU32;
839        use std::sync::{Arc, Barrier};
840        use std::thread;
841
842        table! {
843            #[sql_name = "mysql_transaction_depth_is_tracked_properly_on_commit_failure"]
844            serialization_example {
845                id -> Integer,
846                class -> Integer,
847            }
848        }
849
850        let conn = &mut crate::test_helpers::connection_no_transaction();
851
852        sql_query(
853            "DROP TABLE IF EXISTS mysql_transaction_depth_is_tracked_properly_on_commit_failure;",
854        )
855        .execute(conn)
856        .unwrap();
857        sql_query(
858            r#"
859            CREATE TABLE mysql_transaction_depth_is_tracked_properly_on_commit_failure (
860                id INT AUTO_INCREMENT PRIMARY KEY,
861                class INTEGER NOT NULL
862            )
863        "#,
864        )
865        .execute(conn)
866        .unwrap();
867
868        insert_into(serialization_example::table)
869            .values(&vec![
870                serialization_example::class.eq(1),
871                serialization_example::class.eq(2),
872            ])
873            .execute(conn)
874            .unwrap();
875
876        let before_barrier = Arc::new(Barrier::new(2));
877        let after_barrier = Arc::new(Barrier::new(2));
878
879        let threads = (1..3)
880            .map(|i| {
881                let before_barrier = before_barrier.clone();
882                let after_barrier = after_barrier.clone();
883                thread::spawn(move || {
884                    use crate::connection::transaction_manager::AnsiTransactionManager;
885                    use crate::connection::transaction_manager::TransactionManager;
886                    let conn = &mut crate::test_helpers::connection_no_transaction();
887                    assert_eq!(None, <AnsiTransactionManager as TransactionManager<MysqlConnection>>::transaction_manager_status_mut(conn).transaction_depth().expect("Transaction depth"));
888                    crate::sql_query("SET TRANSACTION ISOLATION LEVEL SERIALIZABLE").execute(conn)?;
889
890                    let result =
891                    conn.transaction(|conn| {
892                        assert_eq!(NonZeroU32::new(1), <AnsiTransactionManager as TransactionManager<MysqlConnection>>::transaction_manager_status_mut(conn).transaction_depth().expect("Transaction depth"));
893                        let _ = serialization_example::table
894                            .filter(serialization_example::class.eq(i))
895                            .count()
896                            .execute(conn)?;
897
898                        let other_i = if i == 1 { 2 } else { 1 };
899                        let q = insert_into(serialization_example::table)
900                            .values(serialization_example::class.eq(other_i));
901                        before_barrier.wait();
902
903                        let r = q.execute(conn);
904                        after_barrier.wait();
905                        r
906                    });
907
908                    assert_eq!(None, <AnsiTransactionManager as TransactionManager<MysqlConnection>>::transaction_manager_status_mut(conn).transaction_depth().expect("Transaction depth"));
909
910                    let second_trans_result = conn.transaction(|conn| crate::sql_query("SELECT 1").execute(conn));
911                    assert!(second_trans_result.is_ok(), "Expected the thread connections to have been rolled back or committed, but second transaction exited with {:?}", second_trans_result);
912                    result
913                })
914            })
915            .collect::<Vec<_>>();
916        let second_trans_result =
917            conn.transaction(|conn| crate::sql_query("SELECT 1").execute(conn));
918        assert!(
919            second_trans_result.is_ok(),
920            "Expected the main connection to have been rolled back or committed, but second transaction exited with {:?}",
921            second_trans_result
922        );
923
924        let mut results = threads
925            .into_iter()
926            .map(|t| t.join().unwrap())
927            .collect::<Vec<_>>();
928
929        results.sort_by_key(|r| r.is_err());
930        assert!(results[0].is_ok(), "Got {:?} instead", results);
931        // Note that contrary to Postgres, this is not a commit failure
932        assert!(
933            matches!(&results[1], Err(DatabaseError(SerializationFailure, _))),
934            "Got {:?} instead",
935            results
936        );
937    }
938
939    #[diesel_test_helper::test]
940    #[cfg(feature = "mysql")]
941    // This function uses a collect with side effects (spawning threads)
942    // so clippy is wrong here
943    #[allow(clippy::needless_collect)]
944    fn mysql_nested_transaction_depth_commits_tracked_properly_on_serialization_failure() {
945        use crate::result::DatabaseErrorKind::SerializationFailure;
946        use crate::result::Error::DatabaseError;
947        use crate::*;
948        use std::num::NonZeroU32;
949        use std::sync::{Arc, Barrier};
950        use std::thread;
951
952        table! {
953            #[sql_name = "mysql_nested_trans_depth_is_tracked_properly_on_commit_failure"]
954            serialization_example {
955                id -> Integer,
956                class -> Integer,
957            }
958        }
959
960        let conn = &mut crate::test_helpers::connection_no_transaction();
961
962        sql_query(
963            "DROP TABLE IF EXISTS mysql_nested_trans_depth_is_tracked_properly_on_commit_failure;",
964        )
965        .execute(conn)
966        .unwrap();
967        sql_query(
968            r#"
969            CREATE TABLE mysql_nested_trans_depth_is_tracked_properly_on_commit_failure (
970                id INT AUTO_INCREMENT PRIMARY KEY,
971                class INTEGER NOT NULL
972            )
973        "#,
974        )
975        .execute(conn)
976        .unwrap();
977
978        insert_into(serialization_example::table)
979            .values(&vec![
980                serialization_example::class.eq(1),
981                serialization_example::class.eq(2),
982            ])
983            .execute(conn)
984            .unwrap();
985
986        let before_barrier = Arc::new(Barrier::new(2));
987        let after_barrier = Arc::new(Barrier::new(2));
988
989        let threads = (1..3)
990            .map(|i| {
991                let before_barrier = before_barrier.clone();
992                let after_barrier = after_barrier.clone();
993                thread::spawn(move || {
994                    use crate::connection::transaction_manager::AnsiTransactionManager;
995                    use crate::connection::transaction_manager::TransactionManager;
996                    let conn = &mut crate::test_helpers::connection_no_transaction();
997                    assert_eq!(None, <AnsiTransactionManager as TransactionManager<MysqlConnection>>::transaction_manager_status_mut(conn).transaction_depth().expect("Transaction depth"));
998                    crate::sql_query("SET TRANSACTION ISOLATION LEVEL SERIALIZABLE").execute(conn)?;
999
1000                    let result =
1001                    conn.transaction(|conn| {
1002                        assert_eq!(NonZeroU32::new(1), <AnsiTransactionManager as TransactionManager<MysqlConnection>>::transaction_manager_status_mut(conn).transaction_depth().expect("Transaction depth"));
1003                       conn.transaction(|conn| {
1004                            assert_eq!(NonZeroU32::new(2), <AnsiTransactionManager as TransactionManager<MysqlConnection>>::transaction_manager_status_mut(conn).transaction_depth().expect("Transaction depth"));
1005                            let _ = serialization_example::table
1006                                .filter(serialization_example::class.eq(i))
1007                                .count()
1008                                .execute(conn)?;
1009
1010                            let other_i = if i == 1 { 2 } else { 1 };
1011                            let q = insert_into(serialization_example::table)
1012                                .values(serialization_example::class.eq(other_i));
1013                            before_barrier.wait();
1014
1015                            let r = q.execute(conn);
1016                            after_barrier.wait();
1017                            r
1018                        })
1019                    });
1020
1021                    assert_eq!(None, <AnsiTransactionManager as TransactionManager<MysqlConnection>>::transaction_manager_status_mut(conn).transaction_depth().expect("Transaction depth"));
1022
1023                    let second_trans_result = conn.transaction(|conn| crate::sql_query("SELECT 1").execute(conn));
1024                    assert!(second_trans_result.is_ok(), "Expected the thread connections to have been rolled back or committed, but second transaction exited with {:?}", second_trans_result);
1025                    result
1026                })
1027            })
1028            .collect::<Vec<_>>();
1029        let second_trans_result =
1030            conn.transaction(|conn| crate::sql_query("SELECT 1").execute(conn));
1031        assert!(
1032            second_trans_result.is_ok(),
1033            "Expected the main connection to have been rolled back or committed, but second transaction exited with {:?}",
1034            second_trans_result
1035        );
1036
1037        let mut results = threads
1038            .into_iter()
1039            .map(|t| t.join().unwrap())
1040            .collect::<Vec<_>>();
1041
1042        results.sort_by_key(|r| r.is_err());
1043        assert!(results[0].is_ok(), "Got {:?} instead", results);
1044        assert!(
1045            matches!(&results[1], Err(DatabaseError(SerializationFailure, _))),
1046            "Got {:?} instead",
1047            results
1048        );
1049    }
1050
1051    #[diesel_test_helper::test]
1052    #[cfg(feature = "__sqlite-shared")]
1053    fn sqlite_transaction_is_rolled_back_upon_deferred_constraint_failure() {
1054        use crate::connection::transaction_manager::AnsiTransactionManager;
1055        use crate::connection::transaction_manager::TransactionManager;
1056        use crate::result::Error;
1057        use crate::*;
1058        use std::num::NonZeroU32;
1059
1060        let conn = &mut crate::test_helpers::connection();
1061        assert_eq!(
1062            None,
1063            <AnsiTransactionManager as TransactionManager<SqliteConnection>>::transaction_manager_status_mut(
1064                conn
1065            ).transaction_depth().expect("Transaction depth")
1066        );
1067        let result: Result<_, Error> = conn.transaction(|conn| {
1068            assert_eq!(
1069                NonZeroU32::new(1),
1070                <AnsiTransactionManager as TransactionManager<SqliteConnection>>::transaction_manager_status_mut(
1071                    conn
1072            ).transaction_depth().expect("Transaction depth")
1073            );
1074            sql_query("DROP TABLE IF EXISTS deferred_commit").execute(conn)?;
1075            sql_query("CREATE TABLE deferred_commit(id INT UNIQUE INITIALLY DEFERRED)").execute(conn)?;
1076            sql_query("INSERT INTO deferred_commit VALUES(1)").execute(conn)?;
1077            let result = sql_query("INSERT INTO deferred_commit VALUES(1)").execute(conn);
1078            assert!(result.is_ok());
1079            Ok(())
1080        });
1081        assert!(result.is_err());
1082        assert_eq!(
1083            None,
1084            <AnsiTransactionManager as TransactionManager<SqliteConnection>>::transaction_manager_status_mut(
1085                conn
1086            ).transaction_depth().expect("Transaction depth")
1087        );
1088    }
1089
1090    // regression test for #3470
1091    // crates.io depends on this behaviour
1092    #[diesel_test_helper::test]
1093    #[cfg(feature = "postgres")]
1094    fn some_libpq_failures_are_recoverable_by_rolling_back_the_savepoint_only() {
1095        use crate::connection::{AnsiTransactionManager, TransactionManager};
1096        use crate::prelude::*;
1097        use crate::sql_query;
1098
1099        crate::table! {
1100            rollback_test (id) {
1101                id -> Int4,
1102                value -> Int4,
1103            }
1104        }
1105
1106        let conn = &mut crate::test_helpers::pg_connection_no_transaction();
1107        assert_eq!(
1108            None,
1109            <AnsiTransactionManager as TransactionManager<PgConnection>>::transaction_manager_status_mut(
1110                conn
1111            ).transaction_depth().expect("Transaction depth")
1112        );
1113
1114        let res = conn.transaction(|conn| {
1115            sql_query(
1116                "CREATE TABLE IF NOT EXISTS rollback_test (id INT PRIMARY KEY, value INT NOT NULL)",
1117            )
1118            .execute(conn)?;
1119            conn.transaction(|conn| {
1120                sql_query("SET TRANSACTION READ ONLY").execute(conn)?;
1121                crate::update(rollback_test::table)
1122                    .set(rollback_test::value.eq(0))
1123                    .execute(conn)
1124            })
1125            .map(|_| {
1126                panic!("Should use the `or_else` branch");
1127            })
1128            .or_else(|_| sql_query("SELECT 1").execute(conn))
1129            .map(|_| ())
1130        });
1131        assert!(res.is_ok());
1132
1133        assert_eq!(
1134            None,
1135            <AnsiTransactionManager as TransactionManager<PgConnection>>::transaction_manager_status_mut(
1136                conn
1137            ).transaction_depth().expect("Transaction depth")
1138        );
1139    }
1140
1141    #[diesel_test_helper::test]
1142    #[cfg(feature = "postgres")]
1143    fn other_libpq_failures_are_not_recoverable_by_rolling_back_the_savepoint_only() {
1144        use crate::connection::{AnsiTransactionManager, TransactionManager};
1145        use crate::prelude::*;
1146        use crate::sql_query;
1147        use std::num::NonZeroU32;
1148        use std::sync::{Arc, Barrier};
1149
1150        crate::table! {
1151            rollback_test2 (id) {
1152                id -> Int4,
1153                value -> Int4,
1154            }
1155        }
1156        let conn = &mut crate::test_helpers::pg_connection_no_transaction();
1157
1158        sql_query(
1159            "CREATE TABLE IF NOT EXISTS rollback_test2 (id INT PRIMARY KEY, value INT NOT NULL)",
1160        )
1161        .execute(conn)
1162        .unwrap();
1163
1164        let start_barrier = Arc::new(Barrier::new(2));
1165        let commit_barrier = Arc::new(Barrier::new(2));
1166
1167        let other_start_barrier = start_barrier.clone();
1168        let other_commit_barrier = commit_barrier.clone();
1169
1170        let t1 = std::thread::spawn(move || {
1171            let conn = &mut crate::test_helpers::pg_connection_no_transaction();
1172            assert_eq!(
1173                None,
1174                <AnsiTransactionManager as TransactionManager<PgConnection>>::transaction_manager_status_mut(
1175                    conn
1176                ).transaction_depth().expect("Transaction depth")
1177            );
1178            let r = conn.build_transaction().serializable().run::<_, crate::result::Error, _>(|conn| {
1179                assert_eq!(
1180                    NonZeroU32::new(1),
1181                    <AnsiTransactionManager as TransactionManager<PgConnection>>::transaction_manager_status_mut(
1182                        conn
1183                    ).transaction_depth().expect("Transaction depth")
1184                );
1185                rollback_test2::table.load::<(i32, i32)>(conn)?;
1186                crate::insert_into(rollback_test2::table)
1187                    .values((rollback_test2::id.eq(1), rollback_test2::value.eq(42)))
1188                    .execute(conn)?;
1189                let r = conn.transaction(|conn| {
1190                    assert_eq!(
1191                        NonZeroU32::new(2),
1192                        <AnsiTransactionManager as TransactionManager<PgConnection>>::transaction_manager_status_mut(
1193                            conn
1194                        ).transaction_depth().expect("Transaction depth")
1195                    );
1196                    start_barrier.wait();
1197                    commit_barrier.wait();
1198                    let r = rollback_test2::table.load::<(i32, i32)>(conn);
1199                    assert!(r.is_err());
1200                    Err::<(), _>(crate::result::Error::RollbackTransaction)
1201                });
1202                assert_eq!(
1203                    NonZeroU32::new(1),
1204                    <AnsiTransactionManager as TransactionManager<PgConnection>>::transaction_manager_status_mut(
1205                        conn
1206                    ).transaction_depth().expect("Transaction depth")
1207                );
1208                assert!(
1209                    matches!(r, Err(crate::result::Error::RollbackTransaction)),
1210                    "rollback failed (such errors should be ignored by transaction manager): {}",
1211                    r.unwrap_err()
1212                );
1213                let r = rollback_test2::table.load::<(i32, i32)>(conn);
1214                assert!(r.is_err());
1215                // fun fact: if hitting "commit" after receiving a serialization failure, PG
1216                // returns that the commit has succeeded, but in fact it was actually rolled back.
1217                // soo.. one should avoid doing that
1218                r
1219            });
1220            assert!(r.is_err());
1221            assert_eq!(
1222                None,
1223                <AnsiTransactionManager as TransactionManager<PgConnection>>::transaction_manager_status_mut(
1224                    conn
1225                ).transaction_depth().expect("Transaction depth")
1226            );
1227        });
1228
1229        let t2 = std::thread::spawn(move || {
1230            other_start_barrier.wait();
1231            let conn = &mut crate::test_helpers::pg_connection_no_transaction();
1232            assert_eq!(
1233                None,
1234                <AnsiTransactionManager as TransactionManager<PgConnection>>::transaction_manager_status_mut(
1235                    conn
1236                ).transaction_depth().expect("Transaction depth")
1237            );
1238            let r = conn.build_transaction().serializable().run::<_, crate::result::Error, _>(|conn| {
1239                assert_eq!(
1240                    NonZeroU32::new(1),
1241                    <AnsiTransactionManager as TransactionManager<PgConnection>>::transaction_manager_status_mut(
1242                        conn
1243                    ).transaction_depth().expect("Transaction depth")
1244                );
1245                let _ = rollback_test2::table.load::<(i32, i32)>(conn)?;
1246                crate::insert_into(rollback_test2::table)
1247                    .values((rollback_test2::id.eq(23), rollback_test2::value.eq(42)))
1248                    .execute(conn)?;
1249                Ok(())
1250            });
1251            other_commit_barrier.wait();
1252            assert!(r.is_ok(), "{:?}", r.unwrap_err());
1253            assert_eq!(
1254                None,
1255                <AnsiTransactionManager as TransactionManager<PgConnection>>::transaction_manager_status_mut(
1256                    conn
1257                ).transaction_depth().expect("Transaction depth")
1258            );
1259        });
1260        crate::sql_query("DELETE FROM rollback_test2")
1261            .execute(conn)
1262            .unwrap();
1263        t1.join().unwrap();
1264        t2.join().unwrap();
1265    }
1266}