1use crate::error::{TransactionState, TxError};
6use crate::pool::Connection;
7use std::sync::Arc;
8use std::time::{Duration, Instant};
9use tokio::sync::Mutex;
10
11#[derive(Debug, Clone, PartialEq, Default)]
16pub enum IsolationLevel {
17 ReadUncommitted,
19 ReadCommitted,
21 #[default]
23 RepeatableRead,
24 Serializable,
26 Snapshot,
28}
29
30impl std::fmt::Display for IsolationLevel {
31 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
32 match self {
33 IsolationLevel::ReadUncommitted => write!(f, "READ UNCOMMITTED"),
34 IsolationLevel::ReadCommitted => write!(f, "READ COMMITTED"),
35 IsolationLevel::RepeatableRead => write!(f, "REPEATABLE READ"),
36 IsolationLevel::Serializable => write!(f, "SERIALIZABLE"),
37 IsolationLevel::Snapshot => write!(f, "SNAPSHOT"),
38 }
39 }
40}
41
42#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
44pub enum AutoCommit {
45 #[default]
47 On,
48 Off,
50}
51
52#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
54pub enum PropagationBehavior {
55 #[default]
57 Required,
58 Mandatory,
60 Never,
62 Supports,
64 RequiresNew,
66 Nested,
68}
69
70pub struct TransactOptions {
72 pub isolation_level: Option<IsolationLevel>,
74 pub read_only: bool,
76 pub timeout: Option<Duration>,
78 pub max_nesting_depth: u32,
83 pub propagation: PropagationBehavior,
85}
86
87pub const DEFAULT_MAX_NESTING_DEPTH: u32 = 8;
89
90impl Default for TransactOptions {
91 fn default() -> Self {
92 Self {
93 isolation_level: None,
94 read_only: false,
95 timeout: None,
96 max_nesting_depth: DEFAULT_MAX_NESTING_DEPTH,
97 propagation: PropagationBehavior::default(),
98 }
99 }
100}
101
102impl TransactOptions {
103 pub fn with_isolation(mut self, level: IsolationLevel) -> Self {
105 self.isolation_level = Some(level);
106 self
107 }
108
109 pub fn read_only(mut self) -> Self {
111 self.read_only = true;
112 self
113 }
114
115 pub fn with_timeout(mut self, timeout: Duration) -> Self {
117 self.timeout = Some(timeout);
118 self
119 }
120
121 pub fn with_max_nesting_depth(mut self, max_depth: u32) -> Self {
123 self.max_nesting_depth = max_depth;
124 self
125 }
126
127 pub fn with_propagation(mut self, propagation: PropagationBehavior) -> Self {
129 self.propagation = propagation;
130 self
131 }
132}
133
134fn validate_savepoint_name(name: &str) -> Result<(), TxError> {
141 if name.is_empty() {
142 return Err(TxError::InvalidSavepointName(name.to_string()));
143 }
144 if !name.chars().all(|c| c.is_ascii_alphanumeric() || c == '_') {
145 return Err(TxError::InvalidSavepointName(name.to_string()));
146 }
147 if name.starts_with(|c: char| c.is_ascii_digit()) {
148 return Err(TxError::InvalidSavepointName(name.to_string()));
149 }
150 Ok(())
151}
152
153pub struct Transaction {
160 conn: Arc<Mutex<Option<Box<dyn Connection>>>>,
161 state: TransactionState,
162 options: TransactOptions,
163 savepoint_counter: u32,
164 deadline: Option<Instant>,
166}
167
168impl Transaction {
169 pub fn new(conn: Box<dyn Connection>, options: TransactOptions) -> Self {
187 let deadline = options.timeout.map(|t| Instant::now() + t);
189 Self {
190 conn: Arc::new(Mutex::new(Some(conn))),
191 state: TransactionState::Active,
192 options,
193 savepoint_counter: 0,
194 deadline,
195 }
196 }
197
198 pub fn state(&self) -> TransactionState {
200 self.state
201 }
202
203 pub fn is_active(&self) -> bool {
205 self.state == TransactionState::Active
206 }
207
208 pub async fn commit(&mut self) -> Result<(), TxError> {
227 if self.state != TransactionState::Active {
228 return Err(TxError::NotActive(self.state));
229 }
230 if let Some(deadline) = self.deadline {
232 if Instant::now() > deadline {
233 self.rollback().await.ok();
234 return Err(TxError::CommitFailed("Transaction timeout".to_string()));
235 }
236 }
237 let mut conn_guard = self.conn.lock().await;
238 let conn = conn_guard.as_mut().ok_or(TxError::ConnectionTaken)?;
239 conn.commit()
240 .await
241 .map_err(|e| TxError::CommitFailed(e.to_string()))?;
242 self.state = TransactionState::Committed;
243 Ok(())
244 }
245
246 pub async fn rollback(&mut self) -> Result<(), TxError> {
248 if self.state != TransactionState::Active {
249 return Err(TxError::NotActive(self.state));
250 }
251 let mut conn_guard = self.conn.lock().await;
252 let conn = conn_guard.as_mut().ok_or(TxError::ConnectionTaken)?;
253 conn.rollback()
254 .await
255 .map_err(|e| TxError::RollbackFailed(e.to_string()))?;
256 self.state = TransactionState::RolledBack;
257 Ok(())
258 }
259
260 pub async fn execute(&mut self, sql: &str) -> Result<u64, TxError> {
262 if self.state != TransactionState::Active {
263 return Err(TxError::NotActive(self.state));
264 }
265 let mut conn_guard = self.conn.lock().await;
266 let conn = conn_guard.as_mut().ok_or(TxError::ConnectionTaken)?;
267 let result = conn
268 .execute(sql)
269 .await
270 .map_err(|e| TxError::CommitFailed(e.to_string()))?;
271 Ok(result)
272 }
273
274 pub async fn query(
276 &mut self,
277 sql: &str,
278 ) -> Result<Vec<std::collections::HashMap<String, crate::value::Value>>, TxError> {
279 if self.state != TransactionState::Active {
280 return Err(TxError::NotActive(self.state));
281 }
282 let mut conn_guard = self.conn.lock().await;
283 let conn = conn_guard.as_mut().ok_or(TxError::ConnectionTaken)?;
284 let result = conn
285 .query(sql)
286 .await
287 .map_err(|e| TxError::CommitFailed(e.to_string()))?;
288 Ok(result)
289 }
290
291 pub async fn savepoint(&mut self) -> Result<String, TxError> {
316 if self.state != TransactionState::Active {
317 return Err(TxError::NotActive(self.state));
318 }
319 let next_depth = self.savepoint_counter + 1;
322 if next_depth > self.options.max_nesting_depth {
323 return Err(TxError::MaxNestingDepthExceeded {
324 current_depth: next_depth,
325 max_depth: self.options.max_nesting_depth,
326 });
327 }
328 self.savepoint_counter += 1;
329 let name = format!("sp_{}", self.savepoint_counter);
330 validate_savepoint_name(&name)?;
332 let sql = format!("SAVEPOINT {}", name);
333 let mut conn_guard = self.conn.lock().await;
334 let conn = conn_guard.as_mut().ok_or(TxError::ConnectionTaken)?;
335 conn.execute(&sql)
336 .await
337 .map_err(|e| TxError::SavepointError(e.to_string()))?;
338 Ok(name)
339 }
340
341 pub async fn rollback_to_savepoint(&mut self, name: &str) -> Result<(), TxError> {
346 if self.state != TransactionState::Active {
347 return Err(TxError::NotActive(self.state));
348 }
349 validate_savepoint_name(name)?;
350 let sql = format!("ROLLBACK TO SAVEPOINT {}", name);
351 let mut conn_guard = self.conn.lock().await;
352 let conn = conn_guard.as_mut().ok_or(TxError::ConnectionTaken)?;
353 conn.execute(&sql)
354 .await
355 .map_err(|e| TxError::SavepointError(e.to_string()))?;
356 Ok(())
357 }
358
359 pub async fn release_savepoint(&mut self, name: &str) -> Result<(), TxError> {
364 if self.state != TransactionState::Active {
365 return Err(TxError::NotActive(self.state));
366 }
367 validate_savepoint_name(name)?;
368 let sql = format!("RELEASE SAVEPOINT {}", name);
369 let mut conn_guard = self.conn.lock().await;
370 let conn = conn_guard.as_mut().ok_or(TxError::ConnectionTaken)?;
371 conn.execute(&sql)
372 .await
373 .map_err(|e| TxError::SavepointError(e.to_string()))?;
374 Ok(())
375 }
376
377 pub async fn take_connection(&mut self) -> Result<Box<dyn Connection>, TxError> {
389 if self.state == TransactionState::Active {
390 return Err(TxError::NotActive(self.state));
391 }
392 let mut conn_guard = self.conn.lock().await;
393 conn_guard.take().ok_or(TxError::ConnectionTaken)
394 }
395
396 pub fn options(&self) -> &TransactOptions {
398 &self.options
399 }
400}
401
402pub fn is_deadlock_error(err_msg: &str) -> bool {
411 let lower = err_msg.to_lowercase();
412 if lower.contains("deadlock found when trying to get lock") {
414 return true;
415 }
416 if lower.contains("error 1213") || lower.contains("(1213)") {
418 return true;
419 }
420 if lower.contains("deadlock detected") || lower.contains("40p01") {
422 return true;
423 }
424 if lower.contains("database is locked") || lower.contains("database table is locked") {
426 return true;
427 }
428 if lower.contains("ora-00060") {
430 return true;
431 }
432 if lower.contains("transaction (process id") && lower.contains("was deadlocked") {
434 return true;
435 }
436 if lower.contains("error 1205") || lower.contains("(1205)") {
437 return true;
438 }
439 false
440}
441
442pub async fn retry_on_deadlock<F, Fut, T>(
467 max_attempts: u32,
468 backoff: Duration,
469 operation: F,
470) -> Result<T, TxError>
471where
472 F: Fn(u32) -> Fut,
473 Fut: std::future::Future<Output = Result<T, TxError>>,
474{
475 let mut last_err: Option<TxError> = None;
476 for attempt in 1..=max_attempts {
477 match operation(attempt).await {
478 Ok(v) => return Ok(v),
479 Err(e) => {
480 let err_msg = format!("{}", e);
482 if is_deadlock_error(&err_msg) && attempt < max_attempts {
483 tokio::time::sleep(backoff).await;
484 last_err = Some(TxError::DeadlockDetected {
485 attempt,
486 max_attempts,
487 });
488 continue;
489 }
490 return Err(e);
492 }
493 }
494 }
495 Err(last_err.unwrap_or(TxError::DeadlockDetected {
497 attempt: max_attempts,
498 max_attempts,
499 }))
500}
501
502impl Drop for Transaction {
503 fn drop(&mut self) {
504 if self.state == TransactionState::Active {
507 let conn = self.conn.clone();
508 if let Ok(handle) = tokio::runtime::Handle::try_current() {
510 handle.spawn(async move {
513 let mut conn_guard = conn.lock().await;
514 if let Some(ref mut conn) = *conn_guard {
515 let _ = conn.rollback().await;
516 }
517 });
518 }
519 self.state = TransactionState::RolledBack;
521 }
522 }
523}
524
525pub struct TransactionManager {
528 transactions: Arc<Mutex<std::collections::HashMap<String, Transaction>>>,
529}
530
531impl TransactionManager {
532 pub fn new() -> Self {
534 Self {
535 transactions: Arc::new(Mutex::new(std::collections::HashMap::new())),
536 }
537 }
538
539 pub async fn begin(
541 &self,
542 id: String,
543 conn: Box<dyn Connection>,
544 options: TransactOptions,
545 ) -> Result<(), TxError> {
546 let mut conn = conn;
547 conn.begin_transaction()
548 .await
549 .map_err(|e| TxError::CommitFailed(e.to_string()))?;
550 let tx = Transaction::new(conn, options);
551 let mut txs = self.transactions.lock().await;
552 txs.insert(id, tx);
553 Ok(())
554 }
555
556 pub async fn commit(&self, id: &str) -> Result<(), TxError> {
558 let mut txs = self.transactions.lock().await;
559 let tx = txs
560 .get_mut(id)
561 .ok_or_else(|| TxError::SavepointError(format!("Transaction {} not found", id)))?;
562 tx.commit().await
563 }
564
565 pub async fn rollback(&self, id: &str) -> Result<(), TxError> {
567 let mut txs = self.transactions.lock().await;
568 let tx = txs
569 .get_mut(id)
570 .ok_or_else(|| TxError::SavepointError(format!("Transaction {} not found", id)))?;
571 tx.rollback().await
572 }
573
574 pub async fn state(&self, id: &str) -> Option<TransactionState> {
576 let txs = self.transactions.lock().await;
577 txs.get(id).map(|tx| tx.state())
578 }
579
580 pub async fn list(&self) -> Vec<String> {
582 let txs = self.transactions.lock().await;
583 txs.keys().cloned().collect()
584 }
585
586 pub async fn remove(&self, id: &str) -> Option<Transaction> {
588 let mut txs = self.transactions.lock().await;
589 txs.remove(id)
590 }
591}
592
593impl Default for TransactionManager {
594 fn default() -> Self {
595 Self::new()
596 }
597}
598
599#[cfg(test)]
600mod tests {
601 use super::*;
602 use std::future::Future;
603 use std::pin::Pin;
604
605 struct MockConnection {
607 begin_called: bool,
608 commit_called: bool,
609 rollback_called: bool,
610 executed_sql: Vec<String>,
611 }
612
613 impl MockConnection {
614 fn new() -> Self {
615 Self {
616 begin_called: false,
617 commit_called: false,
618 rollback_called: false,
619 executed_sql: Vec::new(),
620 }
621 }
622 }
623
624 impl Connection for MockConnection {
625 fn execute<'a>(
626 &'a mut self,
627 sql: &'a str,
628 ) -> Pin<Box<dyn Future<Output = Result<u64, crate::DbError>> + Send + 'a>> {
629 Box::pin(async move {
630 self.executed_sql.push(sql.to_string());
631 Ok(1)
632 })
633 }
634
635 fn query<'a>(
636 &'a mut self,
637 _sql: &'a str,
638 ) -> Pin<
639 Box<
640 dyn Future<
641 Output = Result<
642 Vec<std::collections::HashMap<String, crate::value::Value>>,
643 crate::DbError,
644 >,
645 > + Send
646 + 'a,
647 >,
648 > {
649 Box::pin(async move { Ok(vec![]) })
650 }
651
652 fn begin_transaction<'a>(
653 &'a mut self,
654 ) -> Pin<Box<dyn Future<Output = Result<(), crate::DbError>> + Send + 'a>> {
655 Box::pin(async move {
656 self.begin_called = true;
657 Ok(())
658 })
659 }
660
661 fn commit<'a>(
662 &'a mut self,
663 ) -> Pin<Box<dyn Future<Output = Result<(), crate::DbError>> + Send + 'a>> {
664 Box::pin(async move {
665 self.commit_called = true;
666 Ok(())
667 })
668 }
669
670 fn rollback<'a>(
671 &'a mut self,
672 ) -> Pin<Box<dyn Future<Output = Result<(), crate::DbError>> + Send + 'a>> {
673 Box::pin(async move {
674 self.rollback_called = true;
675 Ok(())
676 })
677 }
678
679 fn is_connected(&self) -> bool {
680 true
681 }
682
683 fn ping<'a>(&'a mut self) -> Pin<Box<dyn Future<Output = bool> + Send + 'a>> {
684 Box::pin(async move { true })
685 }
686
687 fn close<'a>(
688 &'a mut self,
689 ) -> Pin<Box<dyn Future<Output = Result<(), crate::DbError>> + Send + 'a>> {
690 Box::pin(async move { Ok(()) })
691 }
692 }
693
694 #[test]
695 fn test_isolation_level_display() {
696 assert_eq!(IsolationLevel::ReadCommitted.to_string(), "READ COMMITTED");
697 assert_eq!(IsolationLevel::Serializable.to_string(), "SERIALIZABLE");
698 }
699
700 #[test]
701 fn test_transaction_state_default() {
702 let opts = TransactOptions::default();
703 assert!(opts.isolation_level.is_none());
704 assert!(!opts.read_only);
705 }
706
707 #[test]
708 fn test_transact_options_builder() {
709 let opts = TransactOptions {
710 isolation_level: Some(IsolationLevel::Serializable),
711 read_only: true,
712 timeout: Some(Duration::from_secs(30)),
713 max_nesting_depth: DEFAULT_MAX_NESTING_DEPTH,
714 propagation: PropagationBehavior::default(),
715 };
716
717 assert_eq!(opts.isolation_level, Some(IsolationLevel::Serializable));
718 assert!(opts.read_only);
719 assert_eq!(opts.timeout, Some(Duration::from_secs(30)));
720 }
721
722 #[test]
723 fn test_auto_commit_default() {
724 assert_eq!(AutoCommit::default(), AutoCommit::On);
725 }
726
727 #[test]
728 fn test_transaction_state() {
729 assert_eq!(TransactionState::Active, TransactionState::Active);
730 assert_ne!(TransactionState::Active, TransactionState::Committed);
731 }
732
733 #[test]
734 fn test_transact_options_chaining() {
735 let opts = TransactOptions::default()
736 .with_isolation(IsolationLevel::Serializable)
737 .read_only()
738 .with_timeout(Duration::from_secs(60));
739 assert_eq!(opts.isolation_level, Some(IsolationLevel::Serializable));
740 assert!(opts.read_only);
741 assert_eq!(opts.timeout, Some(Duration::from_secs(60)));
742 }
743
744 #[tokio::test]
745 async fn test_transaction_commit() -> Result<(), TxError> {
746 let conn = Box::new(MockConnection::new());
747 let mut tx = Transaction::new(conn, TransactOptions::default());
748 assert!(tx.is_active());
749
750 let result = tx.execute("INSERT INTO users VALUES (1)").await;
751 assert!(result.is_ok());
752
753 tx.commit().await?;
754 assert_eq!(tx.state(), TransactionState::Committed);
755
756 let result = tx.commit().await;
758 assert!(result.is_err());
759 match result {
760 Err(TxError::NotActive(state)) => {
761 assert_eq!(state, TransactionState::Committed);
762 }
763 _ => panic!("Expected NotActive error"),
764 }
765 Ok(())
766 }
767
768 #[tokio::test]
769 async fn test_transaction_rollback() -> Result<(), TxError> {
770 let conn = Box::new(MockConnection::new());
771 let mut tx = Transaction::new(conn, TransactOptions::default());
772
773 tx.rollback().await?;
774 assert_eq!(tx.state(), TransactionState::RolledBack);
775
776 let result = tx.rollback().await;
778 assert!(result.is_err());
779 match result {
780 Err(TxError::NotActive(state)) => {
781 assert_eq!(state, TransactionState::RolledBack);
782 }
783 _ => panic!("Expected NotActive error"),
784 }
785 Ok(())
786 }
787
788 #[tokio::test]
789 async fn test_transaction_execute_after_commit() -> Result<(), TxError> {
790 let conn = Box::new(MockConnection::new());
791 let mut tx = Transaction::new(conn, TransactOptions::default());
792 tx.commit().await?;
793
794 let result = tx.execute("SELECT 1").await;
795 assert!(result.is_err());
796 match result {
797 Err(TxError::NotActive(_)) => {}
798 _ => panic!("Expected NotActive error"),
799 }
800 Ok(())
801 }
802
803 #[tokio::test]
804 async fn test_transaction_query_after_commit_returns_not_active() -> Result<(), TxError> {
805 let conn = Box::new(MockConnection::new());
806 let mut tx = Transaction::new(conn, TransactOptions::default());
807 tx.commit().await?;
808
809 let result = tx.query("SELECT 1").await;
810 assert!(result.is_err());
811 match result {
812 Err(TxError::NotActive(_)) => {}
813 _ => panic!("Expected NotActive error"),
814 }
815 Ok(())
816 }
817
818 #[tokio::test]
819 async fn test_transaction_savepoint() -> Result<(), TxError> {
820 let conn = Box::new(MockConnection::new());
821 let mut tx = Transaction::new(conn, TransactOptions::default());
822
823 let sp1 = tx.savepoint().await?;
824 assert_eq!(sp1, "sp_1");
825
826 let sp2 = tx.savepoint().await?;
827 assert_eq!(sp2, "sp_2");
828
829 tx.rollback_to_savepoint(&sp1).await?;
830 tx.release_savepoint(&sp2).await?;
831 Ok(())
832 }
833
834 #[tokio::test]
835 async fn test_transaction_savepoint_name_validation() {
836 let conn = Box::new(MockConnection::new());
837 let mut tx = Transaction::new(conn, TransactOptions::default());
838
839 let result = tx.rollback_to_savepoint("sp'; DROP TABLE--").await;
841 assert!(result.is_err());
842 match result {
843 Err(TxError::InvalidSavepointName(_)) => {}
844 _ => panic!("Expected InvalidSavepointName error"),
845 }
846
847 let result = tx.release_savepoint("1sp").await;
849 assert!(result.is_err());
850 match result {
851 Err(TxError::InvalidSavepointName(_)) => {}
852 _ => panic!("Expected InvalidSavepointName error"),
853 }
854
855 let result = tx.rollback_to_savepoint("").await;
857 assert!(result.is_err());
858 match result {
859 Err(TxError::InvalidSavepointName(_)) => {}
860 _ => panic!("Expected InvalidSavepointName error"),
861 }
862
863 let result = tx.rollback_to_savepoint("sp_test_1").await;
865 assert!(result.is_ok());
866 }
867
868 #[tokio::test]
869 async fn test_transaction_take_connection() -> Result<(), TxError> {
870 let conn = Box::new(MockConnection::new());
871 let mut tx = Transaction::new(conn, TransactOptions::default());
872
873 let result = tx.take_connection().await;
875 assert!(result.is_err());
876 match result {
877 Err(TxError::NotActive(_)) => {}
878 _ => panic!("Expected NotActive error"),
879 }
880
881 tx.commit().await?;
883 let conn = tx.take_connection().await;
884 assert!(conn.is_ok());
885
886 let result = tx.take_connection().await;
888 assert!(result.is_err());
889 match result {
890 Err(TxError::ConnectionTaken) => {}
891 _ => panic!("Expected ConnectionTaken error"),
892 }
893 Ok(())
894 }
895
896 #[tokio::test]
897 async fn test_transaction_manager() -> Result<(), TxError> {
898 let mgr = TransactionManager::new();
899 let conn = Box::new(MockConnection::new());
900
901 mgr.begin("tx1".to_string(), conn, TransactOptions::default())
902 .await?;
903
904 let state = mgr.state("tx1").await;
905 assert_eq!(state, Some(TransactionState::Active));
906
907 mgr.commit("tx1").await?;
908 let state = mgr.state("tx1").await;
909 assert_eq!(state, Some(TransactionState::Committed));
910
911 let list = mgr.list().await;
912 assert!(list.contains(&"tx1".to_string()));
913 Ok(())
914 }
915
916 #[tokio::test]
917 async fn test_transaction_manager_rollback() -> Result<(), TxError> {
918 let mgr = TransactionManager::new();
919 let conn = Box::new(MockConnection::new());
920
921 mgr.begin("tx2".to_string(), conn, TransactOptions::default())
922 .await?;
923
924 mgr.rollback("tx2").await?;
925 let state = mgr.state("tx2").await;
926 assert_eq!(state, Some(TransactionState::RolledBack));
927 Ok(())
928 }
929
930 #[tokio::test]
931 async fn test_transaction_manager_not_found() {
932 let mgr = TransactionManager::new();
933 let result = mgr.commit("nonexistent").await;
934 assert!(result.is_err());
935 }
936
937 #[tokio::test]
938 async fn test_transaction_manager_remove() -> Result<(), TxError> {
939 let mgr = TransactionManager::new();
940 let conn = Box::new(MockConnection::new());
941
942 mgr.begin("tx3".to_string(), conn, TransactOptions::default())
943 .await?;
944
945 let removed = mgr.remove("tx3").await;
946 assert!(removed.is_some());
947
948 let state = mgr.state("tx3").await;
949 assert_eq!(state, None);
950 Ok(())
951 }
952
953 #[tokio::test]
955 async fn test_transaction_drop_rolls_back_when_active() {
956 use std::sync::atomic::{AtomicBool, Ordering};
957 use std::sync::Arc as StdArc;
958
959 struct TrackingConnection {
960 rollback_called: StdArc<AtomicBool>,
961 }
962
963 impl Connection for TrackingConnection {
964 fn execute<'a>(
965 &'a mut self,
966 _sql: &'a str,
967 ) -> Pin<Box<dyn Future<Output = Result<u64, crate::DbError>> + Send + 'a>>
968 {
969 Box::pin(async { Ok(1) })
970 }
971 fn query<'a>(
972 &'a mut self,
973 _sql: &'a str,
974 ) -> Pin<
975 Box<
976 dyn Future<
977 Output = Result<
978 Vec<std::collections::HashMap<String, crate::value::Value>>,
979 crate::DbError,
980 >,
981 > + Send
982 + 'a,
983 >,
984 > {
985 Box::pin(async { Ok(vec![]) })
986 }
987 fn begin_transaction<'a>(
988 &'a mut self,
989 ) -> Pin<Box<dyn Future<Output = Result<(), crate::DbError>> + Send + 'a>> {
990 Box::pin(async { Ok(()) })
991 }
992 fn commit<'a>(
993 &'a mut self,
994 ) -> Pin<Box<dyn Future<Output = Result<(), crate::DbError>> + Send + 'a>> {
995 Box::pin(async { Ok(()) })
996 }
997 fn rollback<'a>(
998 &'a mut self,
999 ) -> Pin<Box<dyn Future<Output = Result<(), crate::DbError>> + Send + 'a>> {
1000 let flag = self.rollback_called.clone();
1001 Box::pin(async move {
1002 flag.store(true, Ordering::SeqCst);
1003 Ok(())
1004 })
1005 }
1006 fn is_connected(&self) -> bool {
1007 true
1008 }
1009 fn ping<'a>(&'a mut self) -> Pin<Box<dyn Future<Output = bool> + Send + 'a>> {
1010 Box::pin(async { true })
1011 }
1012 fn close<'a>(
1013 &'a mut self,
1014 ) -> Pin<Box<dyn Future<Output = Result<(), crate::DbError>> + Send + 'a>> {
1015 Box::pin(async { Ok(()) })
1016 }
1017 }
1018
1019 let rollback_flag = StdArc::new(AtomicBool::new(false));
1020 let conn = Box::new(TrackingConnection {
1021 rollback_called: rollback_flag.clone(),
1022 });
1023 {
1024 let _tx = Transaction::new(conn, TransactOptions::default());
1025 }
1027 tokio::time::sleep(Duration::from_millis(50)).await;
1029 assert!(
1030 rollback_flag.load(Ordering::SeqCst),
1031 "Drop should have triggered rollback"
1032 );
1033 }
1034
1035 #[test]
1038 fn test_h8_default_max_nesting_depth_is_8() {
1039 let opts = TransactOptions::default();
1040 assert_eq!(opts.max_nesting_depth, DEFAULT_MAX_NESTING_DEPTH);
1041 assert_eq!(opts.max_nesting_depth, 8);
1042 }
1043
1044 #[test]
1045 fn test_h8_with_max_nesting_depth_builder() {
1046 let opts = TransactOptions::default().with_max_nesting_depth(3);
1047 assert_eq!(opts.max_nesting_depth, 3);
1048 }
1049
1050 #[tokio::test]
1051 async fn test_h8_savepoint_within_default_depth_succeeds() -> Result<(), TxError> {
1052 let conn = Box::new(MockConnection::new());
1053 let mut tx = Transaction::new(conn, TransactOptions::default());
1054
1055 for i in 1..=8 {
1057 let sp = tx.savepoint().await?;
1058 assert_eq!(sp, format!("sp_{}", i));
1059 }
1060 Ok(())
1061 }
1062
1063 #[tokio::test]
1064 async fn test_h8_savepoint_exceeding_default_depth_fails() -> Result<(), TxError> {
1065 let conn = Box::new(MockConnection::new());
1066 let mut tx = Transaction::new(conn, TransactOptions::default());
1067
1068 for _ in 0..8 {
1070 tx.savepoint().await?;
1071 }
1072
1073 let result = tx.savepoint().await;
1075 assert!(result.is_err());
1076 match result {
1077 Err(TxError::MaxNestingDepthExceeded {
1078 current_depth,
1079 max_depth,
1080 }) => {
1081 assert_eq!(current_depth, 9);
1082 assert_eq!(max_depth, 8);
1083 }
1084 _ => panic!("Expected MaxNestingDepthExceeded error"),
1085 }
1086 Ok(())
1087 }
1088
1089 #[tokio::test]
1090 async fn test_h8_savepoint_with_custom_depth_3() -> Result<(), TxError> {
1091 let conn = Box::new(MockConnection::new());
1092 let mut tx = Transaction::new(conn, TransactOptions::default().with_max_nesting_depth(3));
1093
1094 for i in 1..=3 {
1096 let sp = tx.savepoint().await?;
1097 assert_eq!(sp, format!("sp_{}", i));
1098 }
1099
1100 let result = tx.savepoint().await;
1102 assert!(result.is_err());
1103 match result {
1104 Err(TxError::MaxNestingDepthExceeded {
1105 current_depth,
1106 max_depth,
1107 }) => {
1108 assert_eq!(current_depth, 4);
1109 assert_eq!(max_depth, 3);
1110 }
1111 _ => panic!("Expected MaxNestingDepthExceeded error"),
1112 }
1113 Ok(())
1114 }
1115
1116 #[tokio::test]
1117 async fn test_h8_savepoint_depth_zero_disables_nesting() {
1118 let conn = Box::new(MockConnection::new());
1119 let mut tx = Transaction::new(conn, TransactOptions::default().with_max_nesting_depth(0));
1120
1121 let result = tx.savepoint().await;
1123 assert!(result.is_err());
1124 match result {
1125 Err(TxError::MaxNestingDepthExceeded {
1126 current_depth,
1127 max_depth,
1128 }) => {
1129 assert_eq!(current_depth, 1);
1130 assert_eq!(max_depth, 0);
1131 }
1132 _ => panic!("Expected MaxNestingDepthExceeded error"),
1133 }
1134 }
1135
1136 #[tokio::test]
1137 async fn test_h8_savepoint_after_rollback_to_still_respects_depth() -> Result<(), TxError> {
1138 let conn = Box::new(MockConnection::new());
1141 let mut tx = Transaction::new(conn, TransactOptions::default().with_max_nesting_depth(2));
1142
1143 let sp1 = tx.savepoint().await?;
1144 let sp2 = tx.savepoint().await?;
1145
1146 tx.rollback_to_savepoint(&sp1).await?;
1148 tx.release_savepoint(&sp2).await?;
1149
1150 let result = tx.savepoint().await;
1152 assert!(result.is_err());
1153 match result {
1154 Err(TxError::MaxNestingDepthExceeded {
1155 current_depth,
1156 max_depth,
1157 }) => {
1158 assert_eq!(current_depth, 3);
1159 assert_eq!(max_depth, 2);
1160 }
1161 _ => panic!("Expected MaxNestingDepthExceeded error"),
1162 }
1163 Ok(())
1164 }
1165
1166 #[tokio::test]
1167 async fn test_h8_max_nesting_depth_error_display() {
1168 let err = TxError::MaxNestingDepthExceeded {
1169 current_depth: 10,
1170 max_depth: 8,
1171 };
1172 let msg = format!("{}", err);
1173 assert!(msg.contains("10"));
1174 assert!(msg.contains("8"));
1175 assert!(msg.contains("exceeds"));
1176 }
1177
1178 #[test]
1181 fn test_m8_is_deadlock_error_mysql() {
1182 assert!(is_deadlock_error(
1183 "Deadlock found when trying to get lock; try restarting transaction"
1184 ));
1185 assert!(is_deadlock_error("Error 1213: Deadlock found"));
1186 assert!(is_deadlock_error("MySQL error (1213)"));
1187 }
1188
1189 #[test]
1190 fn test_m8_is_deadlock_error_postgresql() {
1191 assert!(is_deadlock_error("deadlock detected"));
1192 assert!(is_deadlock_error("ERROR: deadlock detected (40P01)"));
1193 assert!(is_deadlock_error("SQLSTATE 40P01"));
1194 }
1195
1196 #[test]
1197 fn test_m8_is_deadlock_error_sqlite() {
1198 assert!(is_deadlock_error("database is locked"));
1199 assert!(is_deadlock_error("database table is locked"));
1200 }
1201
1202 #[test]
1203 fn test_m8_is_deadlock_error_oracle() {
1204 assert!(is_deadlock_error(
1205 "ORA-00060: deadlock detected while waiting for resource"
1206 ));
1207 }
1208
1209 #[test]
1210 fn test_m8_is_deadlock_error_sql_server() {
1211 assert!(is_deadlock_error(
1212 "Transaction (Process ID 52) was deadlocked on lock resources"
1213 ));
1214 assert!(is_deadlock_error("Error 1205: Transaction was deadlocked"));
1215 }
1216
1217 #[test]
1218 fn test_m8_is_deadlock_error_non_deadlock() {
1219 assert!(!is_deadlock_error("connection refused"));
1220 assert!(!is_deadlock_error("syntax error near SELECT"));
1221 assert!(!is_deadlock_error("permission denied for table users"));
1222 assert!(!is_deadlock_error(""));
1223 }
1224
1225 #[tokio::test]
1226 async fn test_m8_retry_on_deadlock_succeeds_first_attempt() -> Result<(), TxError> {
1227 use std::sync::atomic::{AtomicU32, Ordering};
1228
1229 let counter = Arc::new(AtomicU32::new(0));
1230 let counter_clone = counter.clone();
1231
1232 let result: Result<u32, TxError> =
1233 retry_on_deadlock(3, Duration::from_millis(1), |_attempt| {
1234 let c = counter_clone.clone();
1235 async move {
1236 c.fetch_add(1, Ordering::SeqCst);
1237 Ok(42u32)
1238 }
1239 })
1240 .await;
1241
1242 assert_eq!(result?, 42);
1243 assert_eq!(counter.load(Ordering::SeqCst), 1);
1244 Ok(())
1245 }
1246
1247 #[tokio::test]
1248 async fn test_m8_retry_on_deadlock_retries_on_deadlock_error() -> Result<(), TxError> {
1249 use std::sync::atomic::{AtomicU32, Ordering};
1250
1251 let counter = Arc::new(AtomicU32::new(0));
1252 let counter_clone = counter.clone();
1253
1254 let result: Result<u32, TxError> =
1255 retry_on_deadlock(3, Duration::from_millis(1), |_attempt| {
1256 let c = counter_clone.clone();
1257 async move {
1258 let n = c.fetch_add(1, Ordering::SeqCst);
1259 if n < 2 {
1260 Err(TxError::CommitFailed(
1262 "Deadlock found when trying to get lock".to_string(),
1263 ))
1264 } else {
1265 Ok(42u32)
1266 }
1267 }
1268 })
1269 .await;
1270
1271 assert_eq!(result?, 42);
1272 assert_eq!(counter.load(Ordering::SeqCst), 3);
1273 Ok(())
1274 }
1275
1276 #[tokio::test]
1277 async fn test_m8_retry_on_deadlock_returns_error_after_max_attempts() {
1278 use std::sync::atomic::{AtomicU32, Ordering};
1279
1280 let counter = Arc::new(AtomicU32::new(0));
1281 let counter_clone = counter.clone();
1282
1283 let result: Result<u32, TxError> =
1284 retry_on_deadlock(2, Duration::from_millis(1), |_attempt| {
1285 let c = counter_clone.clone();
1286 async move {
1287 c.fetch_add(1, Ordering::SeqCst);
1288 Err(TxError::CommitFailed(
1289 "Deadlock found when trying to get lock".to_string(),
1290 ))
1291 }
1292 })
1293 .await;
1294
1295 assert!(result.is_err());
1297 assert_eq!(counter.load(Ordering::SeqCst), 2);
1298 }
1299
1300 #[tokio::test]
1301 async fn test_m8_retry_on_deadlock_does_not_retry_non_deadlock_errors() {
1302 use std::sync::atomic::{AtomicU32, Ordering};
1303
1304 let counter = Arc::new(AtomicU32::new(0));
1305 let counter_clone = counter.clone();
1306
1307 let result: Result<u32, TxError> =
1308 retry_on_deadlock(3, Duration::from_millis(1), |_attempt| {
1309 let c = counter_clone.clone();
1310 async move {
1311 c.fetch_add(1, Ordering::SeqCst);
1312 Err(TxError::CommitFailed("syntax error".to_string()))
1313 }
1314 })
1315 .await;
1316
1317 assert!(result.is_err());
1319 assert_eq!(counter.load(Ordering::SeqCst), 1);
1320 }
1321
1322 #[test]
1323 fn test_m8_deadlock_error_display() {
1324 let err = TxError::DeadlockDetected {
1325 attempt: 2,
1326 max_attempts: 3,
1327 };
1328 let msg = format!("{}", err);
1329 assert!(msg.contains("2"));
1330 assert!(msg.contains("3"));
1331 assert!(msg.contains("Deadlock"));
1332 }
1333}