rsfbclient_diesel/fb/
transaction.rs

1//! Firebird transaction
2
3use super::connection::FbConnection;
4use diesel::connection::TransactionManagerStatus;
5use diesel::result::DatabaseErrorKind;
6use diesel::result::Error::DatabaseError;
7use diesel::QueryResult;
8use diesel::{connection::*, RunQueryDsl};
9
10/// Firebird transaction manager
11pub struct FbTransactionManager {
12    status: TransactionManagerStatus,
13}
14
15impl FbTransactionManager {
16    pub fn new() -> Self {
17        FbTransactionManager {
18            status: Default::default(),
19        }
20    }
21
22    fn get_transaction_state(
23        conn: &mut FbConnection,
24    ) -> QueryResult<&mut ValidTransactionManagerStatus> {
25        match FbTransactionManager::transaction_manager_status_mut(conn) {
26            TransactionManagerStatus::Valid(v) => Ok(v),
27            TransactionManagerStatus::InError => {
28                Err(diesel::result::Error::BrokenTransactionManager)
29            }
30        }
31    }
32}
33
34impl Default for FbTransactionManager {
35    fn default() -> Self {
36        FbTransactionManager::new()
37    }
38}
39
40impl TransactionManager<FbConnection> for FbTransactionManager {
41    type TransactionStateData = Self;
42
43    fn begin_transaction(conn: &mut FbConnection) -> QueryResult<()> {
44        let state = Self::transaction_manager_status_mut(conn);
45        let depth = state.transaction_depth()?;
46        if let Some(depth) = depth {
47            // Firebird does not support nested transactions, so
48            // let's simulate this using save points
49            diesel::sql_query(&format!("savepoint sp_diesel_{}", u32::from(depth) + 1))
50                .execute(conn)?;
51        } else {
52            conn.raw
53                .begin_transaction()
54                .map_err(|e| DatabaseError(DatabaseErrorKind::Unknown, Box::new(e.to_string())))?;
55        }
56        if let TransactionManagerStatus::Valid(s) = Self::transaction_manager_status_mut(conn) {
57            s.change_transaction_depth(TransactionDepthChange::IncreaseDepth)?;
58        }
59        Ok(())
60    }
61
62    fn rollback_transaction(conn: &mut FbConnection) -> QueryResult<()> {
63        let transaction_state = Self::get_transaction_state(conn)?;
64
65        let rollback_result = match transaction_state.transaction_depth().map(|d| d.get()) {
66            Some(1) => conn
67                .raw
68                .rollback()
69                .map_err(|e| DatabaseError(DatabaseErrorKind::Unknown, Box::new(e.to_string()))),
70            Some(depth_gt1) => {
71                diesel::sql_query(&format!("rollback to savepoint sp_diesel_{}", depth_gt1))
72                    .execute(conn)
73                    .map(|_| ())
74            }
75            None => return Err(diesel::result::Error::NotInTransaction),
76        };
77
78        match rollback_result {
79            Ok(()) => {
80                Self::get_transaction_state(conn)?
81                    .change_transaction_depth(TransactionDepthChange::DecreaseDepth)?;
82                Ok(())
83            }
84            Err(rollback_error) => {
85                let tm_status = Self::transaction_manager_status_mut(conn);
86                tm_status.set_in_error();
87                Err(rollback_error)
88            }
89        }
90    }
91
92    fn commit_transaction(conn: &mut FbConnection) -> QueryResult<()> {
93        let transaction_state = Self::get_transaction_state(conn)?;
94        let transaction_depth = transaction_state.transaction_depth();
95        let commit_result = match transaction_depth {
96            None => return Err(diesel::result::Error::NotInTransaction),
97            Some(transaction_depth) if transaction_depth.get() == 1 => conn
98                .raw
99                .commit()
100                .map_err(|e| DatabaseError(DatabaseErrorKind::Unknown, Box::new(e.to_string()))),
101            Some(_transaction_depth) => Ok(()),
102        };
103        match commit_result {
104            Ok(()) => {
105                Self::get_transaction_state(conn)?
106                    .change_transaction_depth(TransactionDepthChange::DecreaseDepth)?;
107                Ok(())
108            }
109            Err(commit_error) => {
110                if let TransactionManagerStatus::Valid(ref mut s) = conn.transaction_state().status
111                {
112                    match s.transaction_depth().map(|p| p.get()) {
113                        Some(1) => match Self::rollback_transaction(conn) {
114                            Ok(()) => {}
115                            Err(rollback_error) => {
116                                conn.transaction_state().status.set_in_error();
117                                return Err(diesel::result::Error::RollbackErrorOnCommit {
118                                    rollback_error: Box::new(rollback_error),
119                                    commit_error: Box::new(commit_error),
120                                });
121                            }
122                        },
123                        Some(_depth_gt1) => {
124                            // There's no point in *actually* rolling back this one
125                            // because we won't be able to do anything until top-level
126                            // is rolled back.
127
128                            // To make it easier on the user (that they don't have to really look
129                            // at actual transaction depth and can just rely on the number of
130                            // times they have called begin/commit/rollback) we don't mark the
131                            // transaction manager as out of the savepoints as soon as we
132                            // realize there is that issue, but instead we still decrement here:
133                            s.change_transaction_depth(TransactionDepthChange::DecreaseDepth)?;
134                        }
135                        None => {}
136                    }
137                }
138                Err(commit_error)
139            }
140        }
141    }
142
143    fn transaction_manager_status_mut(conn: &mut FbConnection) -> &mut TransactionManagerStatus {
144        &mut conn.transaction_state().status
145    }
146}