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