Skip to main content

diesel/connection/
transaction_manager.rs

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