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