1use crate::error::{TransactionState, TxError};
6use crate::pool::Connection;
7use std::sync::Arc;
8use std::time::Duration;
9use tokio::sync::Mutex;
10
11#[derive(Debug, Clone, PartialEq, Default)]
15pub enum IsolationLevel {
16 ReadUncommitted,
17 ReadCommitted,
18 #[default]
19 RepeatableRead,
20 Serializable,
21 Snapshot,
22}
23
24impl std::fmt::Display for IsolationLevel {
25 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
26 match self {
27 IsolationLevel::ReadUncommitted => write!(f, "READ UNCOMMITTED"),
28 IsolationLevel::ReadCommitted => write!(f, "READ COMMITTED"),
29 IsolationLevel::RepeatableRead => write!(f, "REPEATABLE READ"),
30 IsolationLevel::Serializable => write!(f, "SERIALIZABLE"),
31 IsolationLevel::Snapshot => write!(f, "SNAPSHOT"),
32 }
33 }
34}
35
36#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
37pub enum AutoCommit {
38 #[default]
39 On,
40 Off,
41}
42
43pub struct TransactOptions {
44 pub isolation_level: Option<IsolationLevel>,
45 pub read_only: bool,
46 pub timeout: Option<Duration>,
47 pub max_nesting_depth: u32,
52}
53
54pub const DEFAULT_MAX_NESTING_DEPTH: u32 = 8;
56
57impl Default for TransactOptions {
58 fn default() -> Self {
59 Self {
60 isolation_level: None,
61 read_only: false,
62 timeout: None,
63 max_nesting_depth: DEFAULT_MAX_NESTING_DEPTH,
64 }
65 }
66}
67
68impl TransactOptions {
69 pub fn with_isolation(mut self, level: IsolationLevel) -> Self {
70 self.isolation_level = Some(level);
71 self
72 }
73
74 pub fn read_only(mut self) -> Self {
75 self.read_only = true;
76 self
77 }
78
79 pub fn with_timeout(mut self, timeout: Duration) -> Self {
80 self.timeout = Some(timeout);
81 self
82 }
83
84 pub fn with_max_nesting_depth(mut self, max_depth: u32) -> Self {
86 self.max_nesting_depth = max_depth;
87 self
88 }
89}
90
91fn validate_savepoint_name(name: &str) -> Result<(), TxError> {
98 if name.is_empty() {
99 return Err(TxError::InvalidSavepointName(name.to_string()));
100 }
101 if !name.chars().all(|c| c.is_ascii_alphanumeric() || c == '_') {
102 return Err(TxError::InvalidSavepointName(name.to_string()));
103 }
104 if name.starts_with(|c: char| c.is_ascii_digit()) {
105 return Err(TxError::InvalidSavepointName(name.to_string()));
106 }
107 Ok(())
108}
109
110pub struct Transaction {
117 conn: Arc<Mutex<Option<Box<dyn Connection>>>>,
118 state: TransactionState,
119 options: TransactOptions,
120 savepoint_counter: u32,
121}
122
123impl Transaction {
124 pub fn new(conn: Box<dyn Connection>, options: TransactOptions) -> Self {
142 Self {
143 conn: Arc::new(Mutex::new(Some(conn))),
144 state: TransactionState::Active,
145 options,
146 savepoint_counter: 0,
147 }
148 }
149
150 pub fn state(&self) -> TransactionState {
152 self.state
153 }
154
155 pub fn is_active(&self) -> bool {
157 self.state == TransactionState::Active
158 }
159
160 pub async fn commit(&mut self) -> Result<(), TxError> {
179 if self.state != TransactionState::Active {
180 return Err(TxError::NotActive(self.state));
181 }
182 let mut conn_guard = self.conn.lock().await;
183 let conn = conn_guard.as_mut().ok_or(TxError::ConnectionTaken)?;
184 conn.commit()
185 .await
186 .map_err(|e| TxError::CommitFailed(e.to_string()))?;
187 self.state = TransactionState::Committed;
188 Ok(())
189 }
190
191 pub async fn rollback(&mut self) -> Result<(), TxError> {
193 if self.state != TransactionState::Active {
194 return Err(TxError::NotActive(self.state));
195 }
196 let mut conn_guard = self.conn.lock().await;
197 let conn = conn_guard.as_mut().ok_or(TxError::ConnectionTaken)?;
198 conn.rollback()
199 .await
200 .map_err(|e| TxError::RollbackFailed(e.to_string()))?;
201 self.state = TransactionState::RolledBack;
202 Ok(())
203 }
204
205 pub async fn execute(&mut self, sql: &str) -> Result<u64, TxError> {
207 if self.state != TransactionState::Active {
208 return Err(TxError::NotActive(self.state));
209 }
210 let mut conn_guard = self.conn.lock().await;
211 let conn = conn_guard.as_mut().ok_or(TxError::ConnectionTaken)?;
212 let result = conn
213 .execute(sql)
214 .await
215 .map_err(|e| TxError::CommitFailed(e.to_string()))?;
216 Ok(result)
217 }
218
219 pub async fn query(
221 &mut self,
222 sql: &str,
223 ) -> Result<Vec<std::collections::HashMap<String, crate::value::Value>>, TxError> {
224 if self.state != TransactionState::Active {
225 return Err(TxError::NotActive(self.state));
226 }
227 let mut conn_guard = self.conn.lock().await;
228 let conn = conn_guard.as_mut().ok_or(TxError::ConnectionTaken)?;
229 let result = conn
230 .query(sql)
231 .await
232 .map_err(|e| TxError::CommitFailed(e.to_string()))?;
233 Ok(result)
234 }
235
236 pub async fn savepoint(&mut self) -> Result<String, TxError> {
261 if self.state != TransactionState::Active {
262 return Err(TxError::NotActive(self.state));
263 }
264 let next_depth = self.savepoint_counter + 1;
267 if next_depth > self.options.max_nesting_depth {
268 return Err(TxError::MaxNestingDepthExceeded {
269 current_depth: next_depth,
270 max_depth: self.options.max_nesting_depth,
271 });
272 }
273 self.savepoint_counter += 1;
274 let name = format!("sp_{}", self.savepoint_counter);
275 validate_savepoint_name(&name)?;
277 let sql = format!("SAVEPOINT {}", name);
278 let mut conn_guard = self.conn.lock().await;
279 let conn = conn_guard.as_mut().ok_or(TxError::ConnectionTaken)?;
280 conn.execute(&sql)
281 .await
282 .map_err(|e| TxError::SavepointError(e.to_string()))?;
283 Ok(name)
284 }
285
286 pub async fn rollback_to_savepoint(&mut self, name: &str) -> Result<(), TxError> {
291 if self.state != TransactionState::Active {
292 return Err(TxError::NotActive(self.state));
293 }
294 validate_savepoint_name(name)?;
295 let sql = format!("ROLLBACK TO SAVEPOINT {}", name);
296 let mut conn_guard = self.conn.lock().await;
297 let conn = conn_guard.as_mut().ok_or(TxError::ConnectionTaken)?;
298 conn.execute(&sql)
299 .await
300 .map_err(|e| TxError::SavepointError(e.to_string()))?;
301 Ok(())
302 }
303
304 pub async fn release_savepoint(&mut self, name: &str) -> Result<(), TxError> {
309 if self.state != TransactionState::Active {
310 return Err(TxError::NotActive(self.state));
311 }
312 validate_savepoint_name(name)?;
313 let sql = format!("RELEASE SAVEPOINT {}", name);
314 let mut conn_guard = self.conn.lock().await;
315 let conn = conn_guard.as_mut().ok_or(TxError::ConnectionTaken)?;
316 conn.execute(&sql)
317 .await
318 .map_err(|e| TxError::SavepointError(e.to_string()))?;
319 Ok(())
320 }
321
322 pub async fn take_connection(&mut self) -> Result<Box<dyn Connection>, TxError> {
334 if self.state == TransactionState::Active {
335 return Err(TxError::NotActive(self.state));
336 }
337 let mut conn_guard = self.conn.lock().await;
338 conn_guard.take().ok_or(TxError::ConnectionTaken)
339 }
340
341 pub fn options(&self) -> &TransactOptions {
343 &self.options
344 }
345}
346
347pub fn is_deadlock_error(err_msg: &str) -> bool {
356 let lower = err_msg.to_lowercase();
357 if lower.contains("deadlock found when trying to get lock") {
359 return true;
360 }
361 if lower.contains("error 1213") || lower.contains("(1213)") {
363 return true;
364 }
365 if lower.contains("deadlock detected") || lower.contains("40p01") {
367 return true;
368 }
369 if lower.contains("database is locked") || lower.contains("database table is locked") {
371 return true;
372 }
373 if lower.contains("ora-00060") {
375 return true;
376 }
377 if lower.contains("transaction (process id") && lower.contains("was deadlocked") {
379 return true;
380 }
381 if lower.contains("error 1205") || lower.contains("(1205)") {
382 return true;
383 }
384 false
385}
386
387pub async fn retry_on_deadlock<F, Fut, T>(
412 max_attempts: u32,
413 backoff: Duration,
414 operation: F,
415) -> Result<T, TxError>
416where
417 F: Fn(u32) -> Fut,
418 Fut: std::future::Future<Output = Result<T, TxError>>,
419{
420 let mut last_err: Option<TxError> = None;
421 for attempt in 1..=max_attempts {
422 match operation(attempt).await {
423 Ok(v) => return Ok(v),
424 Err(e) => {
425 let err_msg = format!("{}", e);
427 if is_deadlock_error(&err_msg) && attempt < max_attempts {
428 tokio::time::sleep(backoff).await;
429 last_err = Some(TxError::DeadlockDetected {
430 attempt,
431 max_attempts,
432 });
433 continue;
434 }
435 return Err(e);
437 }
438 }
439 }
440 Err(last_err.unwrap_or(TxError::DeadlockDetected {
442 attempt: max_attempts,
443 max_attempts,
444 }))
445}
446
447impl Drop for Transaction {
448 fn drop(&mut self) {
449 if self.state == TransactionState::Active {
452 let conn = self.conn.clone();
453 if let Ok(handle) = tokio::runtime::Handle::try_current() {
455 handle.spawn(async move {
458 let mut conn_guard = conn.lock().await;
459 if let Some(ref mut conn) = *conn_guard {
460 let _ = conn.rollback().await;
461 }
462 });
463 }
464 self.state = TransactionState::RolledBack;
466 }
467 }
468}
469
470pub struct TransactionManager {
472 transactions: Arc<Mutex<std::collections::HashMap<String, Transaction>>>,
473}
474
475impl TransactionManager {
476 pub fn new() -> Self {
477 Self {
478 transactions: Arc::new(Mutex::new(std::collections::HashMap::new())),
479 }
480 }
481
482 pub async fn begin(
484 &self,
485 id: String,
486 conn: Box<dyn Connection>,
487 options: TransactOptions,
488 ) -> Result<(), TxError> {
489 let mut conn = conn;
490 conn.begin_transaction()
491 .await
492 .map_err(|e| TxError::CommitFailed(e.to_string()))?;
493 let tx = Transaction::new(conn, options);
494 let mut txs = self.transactions.lock().await;
495 txs.insert(id, tx);
496 Ok(())
497 }
498
499 pub async fn commit(&self, id: &str) -> Result<(), TxError> {
501 let mut txs = self.transactions.lock().await;
502 let tx = txs
503 .get_mut(id)
504 .ok_or_else(|| TxError::SavepointError(format!("Transaction {} not found", id)))?;
505 tx.commit().await
506 }
507
508 pub async fn rollback(&self, id: &str) -> Result<(), TxError> {
510 let mut txs = self.transactions.lock().await;
511 let tx = txs
512 .get_mut(id)
513 .ok_or_else(|| TxError::SavepointError(format!("Transaction {} not found", id)))?;
514 tx.rollback().await
515 }
516
517 pub async fn state(&self, id: &str) -> Option<TransactionState> {
519 let txs = self.transactions.lock().await;
520 txs.get(id).map(|tx| tx.state())
521 }
522
523 pub async fn list(&self) -> Vec<String> {
525 let txs = self.transactions.lock().await;
526 txs.keys().cloned().collect()
527 }
528
529 pub async fn remove(&self, id: &str) -> Option<Transaction> {
531 let mut txs = self.transactions.lock().await;
532 txs.remove(id)
533 }
534}
535
536impl Default for TransactionManager {
537 fn default() -> Self {
538 Self::new()
539 }
540}
541
542#[cfg(test)]
543mod tests {
544 use super::*;
545 use std::future::Future;
546 use std::pin::Pin;
547
548 struct MockConnection {
550 begin_called: bool,
551 commit_called: bool,
552 rollback_called: bool,
553 executed_sql: Vec<String>,
554 }
555
556 impl MockConnection {
557 fn new() -> Self {
558 Self {
559 begin_called: false,
560 commit_called: false,
561 rollback_called: false,
562 executed_sql: Vec::new(),
563 }
564 }
565 }
566
567 impl Connection for MockConnection {
568 fn execute<'a>(
569 &'a mut self,
570 sql: &'a str,
571 ) -> Pin<Box<dyn Future<Output = Result<u64, crate::DbError>> + Send + 'a>> {
572 Box::pin(async move {
573 self.executed_sql.push(sql.to_string());
574 Ok(1)
575 })
576 }
577
578 fn query<'a>(
579 &'a mut self,
580 _sql: &'a str,
581 ) -> Pin<
582 Box<
583 dyn Future<
584 Output = Result<
585 Vec<std::collections::HashMap<String, crate::value::Value>>,
586 crate::DbError,
587 >,
588 > + Send
589 + 'a,
590 >,
591 > {
592 Box::pin(async move { Ok(vec![]) })
593 }
594
595 fn begin_transaction<'a>(
596 &'a mut self,
597 ) -> Pin<Box<dyn Future<Output = Result<(), crate::DbError>> + Send + 'a>> {
598 Box::pin(async move {
599 self.begin_called = true;
600 Ok(())
601 })
602 }
603
604 fn commit<'a>(
605 &'a mut self,
606 ) -> Pin<Box<dyn Future<Output = Result<(), crate::DbError>> + Send + 'a>> {
607 Box::pin(async move {
608 self.commit_called = true;
609 Ok(())
610 })
611 }
612
613 fn rollback<'a>(
614 &'a mut self,
615 ) -> Pin<Box<dyn Future<Output = Result<(), crate::DbError>> + Send + 'a>> {
616 Box::pin(async move {
617 self.rollback_called = true;
618 Ok(())
619 })
620 }
621
622 fn is_connected(&self) -> bool {
623 true
624 }
625
626 fn ping<'a>(&'a mut self) -> Pin<Box<dyn Future<Output = bool> + Send + 'a>> {
627 Box::pin(async move { true })
628 }
629
630 fn close<'a>(
631 &'a mut self,
632 ) -> Pin<Box<dyn Future<Output = Result<(), crate::DbError>> + Send + 'a>> {
633 Box::pin(async move { Ok(()) })
634 }
635 }
636
637 #[test]
638 fn test_isolation_level_display() {
639 assert_eq!(IsolationLevel::ReadCommitted.to_string(), "READ COMMITTED");
640 assert_eq!(IsolationLevel::Serializable.to_string(), "SERIALIZABLE");
641 }
642
643 #[test]
644 fn test_transaction_state_default() {
645 let opts = TransactOptions::default();
646 assert!(opts.isolation_level.is_none());
647 assert!(!opts.read_only);
648 }
649
650 #[test]
651 fn test_transact_options_builder() {
652 let opts = TransactOptions {
653 isolation_level: Some(IsolationLevel::Serializable),
654 read_only: true,
655 timeout: Some(Duration::from_secs(30)),
656 max_nesting_depth: DEFAULT_MAX_NESTING_DEPTH,
657 };
658
659 assert_eq!(opts.isolation_level, Some(IsolationLevel::Serializable));
660 assert!(opts.read_only);
661 assert_eq!(opts.timeout, Some(Duration::from_secs(30)));
662 }
663
664 #[test]
665 fn test_auto_commit_default() {
666 assert_eq!(AutoCommit::default(), AutoCommit::On);
667 }
668
669 #[test]
670 fn test_transaction_state() {
671 assert_eq!(TransactionState::Active, TransactionState::Active);
672 assert_ne!(TransactionState::Active, TransactionState::Committed);
673 }
674
675 #[test]
676 fn test_transact_options_chaining() {
677 let opts = TransactOptions::default()
678 .with_isolation(IsolationLevel::Serializable)
679 .read_only()
680 .with_timeout(Duration::from_secs(60));
681 assert_eq!(opts.isolation_level, Some(IsolationLevel::Serializable));
682 assert!(opts.read_only);
683 assert_eq!(opts.timeout, Some(Duration::from_secs(60)));
684 }
685
686 #[tokio::test]
687 async fn test_transaction_commit() {
688 let conn = Box::new(MockConnection::new());
689 let mut tx = Transaction::new(conn, TransactOptions::default());
690 assert!(tx.is_active());
691
692 let result = tx.execute("INSERT INTO users VALUES (1)").await;
693 assert!(result.is_ok());
694
695 tx.commit().await.unwrap();
696 assert_eq!(tx.state(), TransactionState::Committed);
697
698 let result = tx.commit().await;
700 assert!(result.is_err());
701 match result {
702 Err(TxError::NotActive(state)) => {
703 assert_eq!(state, TransactionState::Committed);
704 }
705 _ => panic!("Expected NotActive error"),
706 }
707 }
708
709 #[tokio::test]
710 async fn test_transaction_rollback() {
711 let conn = Box::new(MockConnection::new());
712 let mut tx = Transaction::new(conn, TransactOptions::default());
713
714 tx.rollback().await.unwrap();
715 assert_eq!(tx.state(), TransactionState::RolledBack);
716
717 let result = tx.rollback().await;
719 assert!(result.is_err());
720 match result {
721 Err(TxError::NotActive(state)) => {
722 assert_eq!(state, TransactionState::RolledBack);
723 }
724 _ => panic!("Expected NotActive error"),
725 }
726 }
727
728 #[tokio::test]
729 async fn test_transaction_execute_after_commit() {
730 let conn = Box::new(MockConnection::new());
731 let mut tx = Transaction::new(conn, TransactOptions::default());
732 tx.commit().await.unwrap();
733
734 let result = tx.execute("SELECT 1").await;
735 assert!(result.is_err());
736 match result {
737 Err(TxError::NotActive(_)) => {}
738 _ => panic!("Expected NotActive error"),
739 }
740 }
741
742 #[tokio::test]
743 async fn test_transaction_query_after_commit_returns_not_active() {
744 let conn = Box::new(MockConnection::new());
745 let mut tx = Transaction::new(conn, TransactOptions::default());
746 tx.commit().await.unwrap();
747
748 let result = tx.query("SELECT 1").await;
749 assert!(result.is_err());
750 match result {
751 Err(TxError::NotActive(_)) => {}
752 _ => panic!("Expected NotActive error"),
753 }
754 }
755
756 #[tokio::test]
757 async fn test_transaction_savepoint() {
758 let conn = Box::new(MockConnection::new());
759 let mut tx = Transaction::new(conn, TransactOptions::default());
760
761 let sp1 = tx.savepoint().await.unwrap();
762 assert_eq!(sp1, "sp_1");
763
764 let sp2 = tx.savepoint().await.unwrap();
765 assert_eq!(sp2, "sp_2");
766
767 tx.rollback_to_savepoint(&sp1).await.unwrap();
768 tx.release_savepoint(&sp2).await.unwrap();
769 }
770
771 #[tokio::test]
772 async fn test_transaction_savepoint_name_validation() {
773 let conn = Box::new(MockConnection::new());
774 let mut tx = Transaction::new(conn, TransactOptions::default());
775
776 let result = tx.rollback_to_savepoint("sp'; DROP TABLE--").await;
778 assert!(result.is_err());
779 match result {
780 Err(TxError::InvalidSavepointName(_)) => {}
781 _ => panic!("Expected InvalidSavepointName error"),
782 }
783
784 let result = tx.release_savepoint("1sp").await;
786 assert!(result.is_err());
787 match result {
788 Err(TxError::InvalidSavepointName(_)) => {}
789 _ => panic!("Expected InvalidSavepointName error"),
790 }
791
792 let result = tx.rollback_to_savepoint("").await;
794 assert!(result.is_err());
795 match result {
796 Err(TxError::InvalidSavepointName(_)) => {}
797 _ => panic!("Expected InvalidSavepointName error"),
798 }
799
800 let result = tx.rollback_to_savepoint("sp_test_1").await;
802 assert!(result.is_ok());
803 }
804
805 #[tokio::test]
806 async fn test_transaction_take_connection() {
807 let conn = Box::new(MockConnection::new());
808 let mut tx = Transaction::new(conn, TransactOptions::default());
809
810 let result = tx.take_connection().await;
812 assert!(result.is_err());
813 match result {
814 Err(TxError::NotActive(_)) => {}
815 _ => panic!("Expected NotActive error"),
816 }
817
818 tx.commit().await.unwrap();
820 let conn = tx.take_connection().await;
821 assert!(conn.is_ok());
822
823 let result = tx.take_connection().await;
825 assert!(result.is_err());
826 match result {
827 Err(TxError::ConnectionTaken) => {}
828 _ => panic!("Expected ConnectionTaken error"),
829 }
830 }
831
832 #[tokio::test]
833 async fn test_transaction_manager() {
834 let mgr = TransactionManager::new();
835 let conn = Box::new(MockConnection::new());
836
837 mgr.begin("tx1".to_string(), conn, TransactOptions::default())
838 .await
839 .unwrap();
840
841 let state = mgr.state("tx1").await;
842 assert_eq!(state, Some(TransactionState::Active));
843
844 mgr.commit("tx1").await.unwrap();
845 let state = mgr.state("tx1").await;
846 assert_eq!(state, Some(TransactionState::Committed));
847
848 let list = mgr.list().await;
849 assert!(list.contains(&"tx1".to_string()));
850 }
851
852 #[tokio::test]
853 async fn test_transaction_manager_rollback() {
854 let mgr = TransactionManager::new();
855 let conn = Box::new(MockConnection::new());
856
857 mgr.begin("tx2".to_string(), conn, TransactOptions::default())
858 .await
859 .unwrap();
860
861 mgr.rollback("tx2").await.unwrap();
862 let state = mgr.state("tx2").await;
863 assert_eq!(state, Some(TransactionState::RolledBack));
864 }
865
866 #[tokio::test]
867 async fn test_transaction_manager_not_found() {
868 let mgr = TransactionManager::new();
869 let result = mgr.commit("nonexistent").await;
870 assert!(result.is_err());
871 }
872
873 #[tokio::test]
874 async fn test_transaction_manager_remove() {
875 let mgr = TransactionManager::new();
876 let conn = Box::new(MockConnection::new());
877
878 mgr.begin("tx3".to_string(), conn, TransactOptions::default())
879 .await
880 .unwrap();
881
882 let removed = mgr.remove("tx3").await;
883 assert!(removed.is_some());
884
885 let state = mgr.state("tx3").await;
886 assert_eq!(state, None);
887 }
888
889 #[tokio::test]
891 async fn test_transaction_drop_rolls_back_when_active() {
892 use std::sync::atomic::{AtomicBool, Ordering};
893 use std::sync::Arc as StdArc;
894
895 struct TrackingConnection {
896 rollback_called: StdArc<AtomicBool>,
897 }
898
899 impl Connection for TrackingConnection {
900 fn execute<'a>(
901 &'a mut self,
902 _sql: &'a str,
903 ) -> Pin<Box<dyn Future<Output = Result<u64, crate::DbError>> + Send + 'a>>
904 {
905 Box::pin(async { Ok(1) })
906 }
907 fn query<'a>(
908 &'a mut self,
909 _sql: &'a str,
910 ) -> Pin<
911 Box<
912 dyn Future<
913 Output = Result<
914 Vec<std::collections::HashMap<String, crate::value::Value>>,
915 crate::DbError,
916 >,
917 > + Send
918 + 'a,
919 >,
920 > {
921 Box::pin(async { Ok(vec![]) })
922 }
923 fn begin_transaction<'a>(
924 &'a mut self,
925 ) -> Pin<Box<dyn Future<Output = Result<(), crate::DbError>> + Send + 'a>> {
926 Box::pin(async { Ok(()) })
927 }
928 fn commit<'a>(
929 &'a mut self,
930 ) -> Pin<Box<dyn Future<Output = Result<(), crate::DbError>> + Send + 'a>> {
931 Box::pin(async { Ok(()) })
932 }
933 fn rollback<'a>(
934 &'a mut self,
935 ) -> Pin<Box<dyn Future<Output = Result<(), crate::DbError>> + Send + 'a>> {
936 let flag = self.rollback_called.clone();
937 Box::pin(async move {
938 flag.store(true, Ordering::SeqCst);
939 Ok(())
940 })
941 }
942 fn is_connected(&self) -> bool {
943 true
944 }
945 fn ping<'a>(&'a mut self) -> Pin<Box<dyn Future<Output = bool> + Send + 'a>> {
946 Box::pin(async { true })
947 }
948 fn close<'a>(
949 &'a mut self,
950 ) -> Pin<Box<dyn Future<Output = Result<(), crate::DbError>> + Send + 'a>> {
951 Box::pin(async { Ok(()) })
952 }
953 }
954
955 let rollback_flag = StdArc::new(AtomicBool::new(false));
956 let conn = Box::new(TrackingConnection {
957 rollback_called: rollback_flag.clone(),
958 });
959 {
960 let _tx = Transaction::new(conn, TransactOptions::default());
961 }
963 tokio::time::sleep(Duration::from_millis(50)).await;
965 assert!(
966 rollback_flag.load(Ordering::SeqCst),
967 "Drop should have triggered rollback"
968 );
969 }
970
971 #[test]
974 fn test_h8_default_max_nesting_depth_is_8() {
975 let opts = TransactOptions::default();
976 assert_eq!(opts.max_nesting_depth, DEFAULT_MAX_NESTING_DEPTH);
977 assert_eq!(opts.max_nesting_depth, 8);
978 }
979
980 #[test]
981 fn test_h8_with_max_nesting_depth_builder() {
982 let opts = TransactOptions::default().with_max_nesting_depth(3);
983 assert_eq!(opts.max_nesting_depth, 3);
984 }
985
986 #[tokio::test]
987 async fn test_h8_savepoint_within_default_depth_succeeds() {
988 let conn = Box::new(MockConnection::new());
989 let mut tx = Transaction::new(conn, TransactOptions::default());
990
991 for i in 1..=8 {
993 let sp = tx.savepoint().await.unwrap();
994 assert_eq!(sp, format!("sp_{}", i));
995 }
996 }
997
998 #[tokio::test]
999 async fn test_h8_savepoint_exceeding_default_depth_fails() {
1000 let conn = Box::new(MockConnection::new());
1001 let mut tx = Transaction::new(conn, TransactOptions::default());
1002
1003 for _ in 0..8 {
1005 tx.savepoint().await.unwrap();
1006 }
1007
1008 let result = tx.savepoint().await;
1010 assert!(result.is_err());
1011 match result {
1012 Err(TxError::MaxNestingDepthExceeded {
1013 current_depth,
1014 max_depth,
1015 }) => {
1016 assert_eq!(current_depth, 9);
1017 assert_eq!(max_depth, 8);
1018 }
1019 _ => panic!("Expected MaxNestingDepthExceeded error"),
1020 }
1021 }
1022
1023 #[tokio::test]
1024 async fn test_h8_savepoint_with_custom_depth_3() {
1025 let conn = Box::new(MockConnection::new());
1026 let mut tx = Transaction::new(conn, TransactOptions::default().with_max_nesting_depth(3));
1027
1028 for i in 1..=3 {
1030 let sp = tx.savepoint().await.unwrap();
1031 assert_eq!(sp, format!("sp_{}", i));
1032 }
1033
1034 let result = tx.savepoint().await;
1036 assert!(result.is_err());
1037 match result {
1038 Err(TxError::MaxNestingDepthExceeded {
1039 current_depth,
1040 max_depth,
1041 }) => {
1042 assert_eq!(current_depth, 4);
1043 assert_eq!(max_depth, 3);
1044 }
1045 _ => panic!("Expected MaxNestingDepthExceeded error"),
1046 }
1047 }
1048
1049 #[tokio::test]
1050 async fn test_h8_savepoint_depth_zero_disables_nesting() {
1051 let conn = Box::new(MockConnection::new());
1052 let mut tx = Transaction::new(conn, TransactOptions::default().with_max_nesting_depth(0));
1053
1054 let result = tx.savepoint().await;
1056 assert!(result.is_err());
1057 match result {
1058 Err(TxError::MaxNestingDepthExceeded {
1059 current_depth,
1060 max_depth,
1061 }) => {
1062 assert_eq!(current_depth, 1);
1063 assert_eq!(max_depth, 0);
1064 }
1065 _ => panic!("Expected MaxNestingDepthExceeded error"),
1066 }
1067 }
1068
1069 #[tokio::test]
1070 async fn test_h8_savepoint_after_rollback_to_still_respects_depth() {
1071 let conn = Box::new(MockConnection::new());
1074 let mut tx = Transaction::new(conn, TransactOptions::default().with_max_nesting_depth(2));
1075
1076 let sp1 = tx.savepoint().await.unwrap();
1077 let sp2 = tx.savepoint().await.unwrap();
1078
1079 tx.rollback_to_savepoint(&sp1).await.unwrap();
1081 tx.release_savepoint(&sp2).await.unwrap();
1082
1083 let result = tx.savepoint().await;
1085 assert!(result.is_err());
1086 match result {
1087 Err(TxError::MaxNestingDepthExceeded {
1088 current_depth,
1089 max_depth,
1090 }) => {
1091 assert_eq!(current_depth, 3);
1092 assert_eq!(max_depth, 2);
1093 }
1094 _ => panic!("Expected MaxNestingDepthExceeded error"),
1095 }
1096 }
1097
1098 #[tokio::test]
1099 async fn test_h8_max_nesting_depth_error_display() {
1100 let err = TxError::MaxNestingDepthExceeded {
1101 current_depth: 10,
1102 max_depth: 8,
1103 };
1104 let msg = format!("{}", err);
1105 assert!(msg.contains("10"));
1106 assert!(msg.contains("8"));
1107 assert!(msg.contains("exceeds"));
1108 }
1109
1110 #[test]
1113 fn test_m8_is_deadlock_error_mysql() {
1114 assert!(is_deadlock_error(
1115 "Deadlock found when trying to get lock; try restarting transaction"
1116 ));
1117 assert!(is_deadlock_error("Error 1213: Deadlock found"));
1118 assert!(is_deadlock_error("MySQL error (1213)"));
1119 }
1120
1121 #[test]
1122 fn test_m8_is_deadlock_error_postgresql() {
1123 assert!(is_deadlock_error("deadlock detected"));
1124 assert!(is_deadlock_error("ERROR: deadlock detected (40P01)"));
1125 assert!(is_deadlock_error("SQLSTATE 40P01"));
1126 }
1127
1128 #[test]
1129 fn test_m8_is_deadlock_error_sqlite() {
1130 assert!(is_deadlock_error("database is locked"));
1131 assert!(is_deadlock_error("database table is locked"));
1132 }
1133
1134 #[test]
1135 fn test_m8_is_deadlock_error_oracle() {
1136 assert!(is_deadlock_error(
1137 "ORA-00060: deadlock detected while waiting for resource"
1138 ));
1139 }
1140
1141 #[test]
1142 fn test_m8_is_deadlock_error_sql_server() {
1143 assert!(is_deadlock_error(
1144 "Transaction (Process ID 52) was deadlocked on lock resources"
1145 ));
1146 assert!(is_deadlock_error("Error 1205: Transaction was deadlocked"));
1147 }
1148
1149 #[test]
1150 fn test_m8_is_deadlock_error_non_deadlock() {
1151 assert!(!is_deadlock_error("connection refused"));
1152 assert!(!is_deadlock_error("syntax error near SELECT"));
1153 assert!(!is_deadlock_error("permission denied for table users"));
1154 assert!(!is_deadlock_error(""));
1155 }
1156
1157 #[tokio::test]
1158 async fn test_m8_retry_on_deadlock_succeeds_first_attempt() {
1159 use std::sync::atomic::{AtomicU32, Ordering};
1160
1161 let counter = Arc::new(AtomicU32::new(0));
1162 let counter_clone = counter.clone();
1163
1164 let result: Result<u32, TxError> =
1165 retry_on_deadlock(3, Duration::from_millis(1), |_attempt| {
1166 let c = counter_clone.clone();
1167 async move {
1168 c.fetch_add(1, Ordering::SeqCst);
1169 Ok(42u32)
1170 }
1171 })
1172 .await;
1173
1174 assert_eq!(result.unwrap(), 42);
1175 assert_eq!(counter.load(Ordering::SeqCst), 1);
1176 }
1177
1178 #[tokio::test]
1179 async fn test_m8_retry_on_deadlock_retries_on_deadlock_error() {
1180 use std::sync::atomic::{AtomicU32, Ordering};
1181
1182 let counter = Arc::new(AtomicU32::new(0));
1183 let counter_clone = counter.clone();
1184
1185 let result: Result<u32, TxError> =
1186 retry_on_deadlock(3, Duration::from_millis(1), |_attempt| {
1187 let c = counter_clone.clone();
1188 async move {
1189 let n = c.fetch_add(1, Ordering::SeqCst);
1190 if n < 2 {
1191 Err(TxError::CommitFailed(
1193 "Deadlock found when trying to get lock".to_string(),
1194 ))
1195 } else {
1196 Ok(42u32)
1197 }
1198 }
1199 })
1200 .await;
1201
1202 assert_eq!(result.unwrap(), 42);
1203 assert_eq!(counter.load(Ordering::SeqCst), 3);
1204 }
1205
1206 #[tokio::test]
1207 async fn test_m8_retry_on_deadlock_returns_error_after_max_attempts() {
1208 use std::sync::atomic::{AtomicU32, Ordering};
1209
1210 let counter = Arc::new(AtomicU32::new(0));
1211 let counter_clone = counter.clone();
1212
1213 let result: Result<u32, TxError> =
1214 retry_on_deadlock(2, Duration::from_millis(1), |_attempt| {
1215 let c = counter_clone.clone();
1216 async move {
1217 c.fetch_add(1, Ordering::SeqCst);
1218 Err(TxError::CommitFailed(
1219 "Deadlock found when trying to get lock".to_string(),
1220 ))
1221 }
1222 })
1223 .await;
1224
1225 assert!(result.is_err());
1227 assert_eq!(counter.load(Ordering::SeqCst), 2);
1228 }
1229
1230 #[tokio::test]
1231 async fn test_m8_retry_on_deadlock_does_not_retry_non_deadlock_errors() {
1232 use std::sync::atomic::{AtomicU32, Ordering};
1233
1234 let counter = Arc::new(AtomicU32::new(0));
1235 let counter_clone = counter.clone();
1236
1237 let result: Result<u32, TxError> =
1238 retry_on_deadlock(3, Duration::from_millis(1), |_attempt| {
1239 let c = counter_clone.clone();
1240 async move {
1241 c.fetch_add(1, Ordering::SeqCst);
1242 Err(TxError::CommitFailed("syntax error".to_string()))
1243 }
1244 })
1245 .await;
1246
1247 assert!(result.is_err());
1249 assert_eq!(counter.load(Ordering::SeqCst), 1);
1250 }
1251
1252 #[test]
1253 fn test_m8_deadlock_error_display() {
1254 let err = TxError::DeadlockDetected {
1255 attempt: 2,
1256 max_attempts: 3,
1257 };
1258 let msg = format!("{}", err);
1259 assert!(msg.contains("2"));
1260 assert!(msg.contains("3"));
1261 assert!(msg.contains("Deadlock"));
1262 }
1263}