1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
use super::connection::FbConnection;
use diesel::connection::TransactionManagerStatus;
use diesel::result::DatabaseErrorKind;
use diesel::result::Error::DatabaseError;
use diesel::QueryResult;
use diesel::{connection::*, RunQueryDsl};
pub struct FbTransactionManager {
status: TransactionManagerStatus,
}
impl FbTransactionManager {
pub fn new() -> Self {
FbTransactionManager {
status: Default::default(),
}
}
fn get_transaction_state(
conn: &mut FbConnection,
) -> QueryResult<&mut ValidTransactionManagerStatus> {
match FbTransactionManager::transaction_manager_status_mut(conn) {
TransactionManagerStatus::Valid(v) => Ok(v),
TransactionManagerStatus::InError => {
Err(diesel::result::Error::BrokenTransactionManager)
}
}
}
}
impl Default for FbTransactionManager {
fn default() -> Self {
FbTransactionManager::new()
}
}
impl TransactionManager<FbConnection> for FbTransactionManager {
type TransactionStateData = Self;
fn begin_transaction(conn: &mut FbConnection) -> QueryResult<()> {
let state = Self::transaction_manager_status_mut(conn);
let depth = state.transaction_depth()?;
if let Some(depth) = depth {
diesel::sql_query(&format!("savepoint sp_diesel_{}", u32::from(depth) + 1))
.execute(conn)?;
} else {
conn.raw
.begin_transaction()
.map_err(|e| DatabaseError(DatabaseErrorKind::Unknown, Box::new(e.to_string())))?;
}
if let TransactionManagerStatus::Valid(s) = Self::transaction_manager_status_mut(conn) {
s.change_transaction_depth(TransactionDepthChange::IncreaseDepth)?;
}
Ok(())
}
fn rollback_transaction(conn: &mut FbConnection) -> QueryResult<()> {
let transaction_state = Self::get_transaction_state(conn)?;
let rollback_result = match transaction_state.transaction_depth().map(|d| d.get()) {
Some(1) => conn
.raw
.rollback()
.map_err(|e| DatabaseError(DatabaseErrorKind::Unknown, Box::new(e.to_string()))),
Some(depth_gt1) => {
diesel::sql_query(&format!("rollback to savepoint sp_diesel_{}", depth_gt1))
.execute(conn)
.map(|_| ())
}
None => return Err(diesel::result::Error::NotInTransaction),
};
match rollback_result {
Ok(()) => {
Self::get_transaction_state(conn)?
.change_transaction_depth(TransactionDepthChange::DecreaseDepth)?;
Ok(())
}
Err(rollback_error) => {
let tm_status = Self::transaction_manager_status_mut(conn);
tm_status.set_in_error();
Err(rollback_error)
}
}
}
fn commit_transaction(conn: &mut FbConnection) -> QueryResult<()> {
let transaction_state = Self::get_transaction_state(conn)?;
let transaction_depth = transaction_state.transaction_depth();
let commit_result = match transaction_depth {
None => return Err(diesel::result::Error::NotInTransaction),
Some(transaction_depth) if transaction_depth.get() == 1 => conn
.raw
.commit()
.map_err(|e| DatabaseError(DatabaseErrorKind::Unknown, Box::new(e.to_string()))),
Some(_transaction_depth) => Ok(()),
};
match commit_result {
Ok(()) => {
Self::get_transaction_state(conn)?
.change_transaction_depth(TransactionDepthChange::DecreaseDepth)?;
Ok(())
}
Err(commit_error) => {
if let TransactionManagerStatus::Valid(ref mut s) = conn.transaction_state().status
{
match s.transaction_depth().map(|p| p.get()) {
Some(1) => match Self::rollback_transaction(conn) {
Ok(()) => {}
Err(rollback_error) => {
conn.transaction_state().status.set_in_error();
return Err(diesel::result::Error::RollbackErrorOnCommit {
rollback_error: Box::new(rollback_error),
commit_error: Box::new(commit_error),
});
}
},
Some(_depth_gt1) => {
s.change_transaction_depth(TransactionDepthChange::DecreaseDepth)?;
}
None => {}
}
}
Err(commit_error)
}
}
}
fn transaction_manager_status_mut(conn: &mut FbConnection) -> &mut TransactionManagerStatus {
&mut conn.transaction_state().status
}
}