mysql_async/queryable/
transaction.rs

1// Copyright (c) 2017 Anatoly Ikorsky
2//
3// Licensed under the Apache License, Version 2.0
4// <LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0> or the MIT
5// license <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
6// option. All files in the project carrying such notice may not be copied,
7// modified, or distributed except according to those terms.
8
9use std::{fmt, ops::Deref};
10
11use crate::{connection_like::Connection, error::*, queryable::Queryable, Conn};
12
13/// Transaction status.
14#[derive(Debug, Copy, Clone, Eq, PartialEq)]
15#[repr(u8)]
16pub enum TxStatus {
17    /// Connection is in transaction at the moment.
18    InTransaction,
19    /// `Transaction` was dropped without explicit call to `commit` or `rollback`.
20    RequiresRollback,
21    /// Connection is not in transaction at the moment.
22    None,
23}
24
25impl Conn {
26    /// Starts a transaction.
27    pub async fn start_transaction(&mut self, options: TxOpts) -> Result<Transaction<'_>> {
28        Transaction::new(self, options).await
29    }
30}
31
32/// Transaction options.
33///
34/// Example:
35///
36/// ```
37/// # use mysql_async::*;
38/// # fn main() -> Result<()> {
39/// let tx_opts = TxOpts::default()
40///     .with_consistent_snapshot(true)
41///     .with_isolation_level(IsolationLevel::RepeatableRead);
42/// # Ok(()) }
43/// ```
44#[derive(Eq, PartialEq, Debug, Hash, Clone, Default)]
45pub struct TxOpts {
46    consistent_snapshot: bool,
47    isolation_level: Option<IsolationLevel>,
48    readonly: Option<bool>,
49}
50
51impl TxOpts {
52    /// Creates a default instance.
53    pub fn new() -> TxOpts {
54        TxOpts::default()
55    }
56
57    /// See [`TxOpts::consistent_snapshot`].
58    pub fn with_consistent_snapshot(&mut self, value: bool) -> &mut Self {
59        self.consistent_snapshot = value;
60        self
61    }
62
63    /// See [`TxOpts::isolation_level`].
64    pub fn with_isolation_level<T>(&mut self, value: T) -> &mut Self
65    where
66        T: Into<Option<IsolationLevel>>,
67    {
68        self.isolation_level = value.into();
69        self
70    }
71
72    /// See [`TxOpts::readonly`].
73    pub fn with_readonly<T>(&mut self, value: T) -> &mut Self
74    where
75        T: Into<Option<bool>>,
76    {
77        self.readonly = value.into();
78        self
79    }
80
81    /// If true, then `START TRANSACTION WITH CONSISTENT SNAPSHOT` will be performed.
82    /// Defaults to `false`.
83    pub fn consistent_snapshot(&self) -> bool {
84        self.consistent_snapshot
85    }
86
87    /// If not `None`, then `SET TRANSACTION ISOLATION LEVEL ..` will be performed.
88    /// Defaults to `None`.
89    pub fn isolation_level(&self) -> Option<IsolationLevel> {
90        self.isolation_level
91    }
92
93    /// If not `None`, then `SET TRANSACTION READ ONLY|WRITE` will be performed.
94    /// Defaults to `None`.
95    ///
96    /// Only available since MySql 5.6.5.
97    pub fn readonly(&self) -> Option<bool> {
98        self.readonly
99    }
100}
101
102/// Transaction isolation level.
103#[derive(PartialEq, Eq, Clone, Copy, Debug, Hash)]
104pub enum IsolationLevel {
105    ReadUncommitted,
106    ReadCommitted,
107    RepeatableRead,
108    Serializable,
109}
110
111impl fmt::Display for IsolationLevel {
112    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
113        match *self {
114            IsolationLevel::ReadUncommitted => write!(f, "READ UNCOMMITTED"),
115            IsolationLevel::ReadCommitted => write!(f, "READ COMMITTED"),
116            IsolationLevel::RepeatableRead => write!(f, "REPEATABLE READ"),
117            IsolationLevel::Serializable => write!(f, "SERIALIZABLE"),
118        }
119    }
120}
121
122/// This struct represents MySql transaction.
123///
124/// `Transaction` is just a sugar for `START TRANSACTION`, `ROLLBACK` and `COMMIT` queries,
125/// so please note, that it is easy to mess things up calling this queries manually.
126///
127/// You should always call either `commit` or `rollback`, otherwise transaction will be rolled
128/// back implicitly when corresponding connection is dropped or queried.
129#[must_use = "transaction object must be committed or rolled back explicitly"]
130#[derive(Debug)]
131pub struct Transaction<'a>(pub(crate) Connection<'a, 'static>);
132
133impl<'a> Transaction<'a> {
134    pub(crate) async fn new<T: Into<Connection<'a, 'static>>>(
135        conn: T,
136        options: TxOpts,
137    ) -> Result<Transaction<'a>> {
138        let TxOpts {
139            consistent_snapshot,
140            isolation_level,
141            readonly,
142        } = options;
143
144        let mut conn = conn.into();
145
146        if conn.get_tx_status() != TxStatus::None {
147            return Err(DriverError::NestedTransaction.into());
148        }
149
150        if readonly.is_some() && conn.server_version() < (5, 6, 5) {
151            return Err(DriverError::ReadOnlyTransNotSupported.into());
152        }
153
154        if let Some(isolation_level) = isolation_level {
155            let query = format!("SET TRANSACTION ISOLATION LEVEL {}", isolation_level);
156            conn.query_drop(query).await?;
157        }
158
159        if let Some(readonly) = readonly {
160            if readonly {
161                conn.query_drop("SET TRANSACTION READ ONLY").await?;
162            } else {
163                conn.query_drop("SET TRANSACTION READ WRITE").await?;
164            }
165        }
166
167        if consistent_snapshot {
168            conn.query_drop("START TRANSACTION WITH CONSISTENT SNAPSHOT")
169                .await?
170        } else {
171            conn.query_drop("START TRANSACTION").await?
172        };
173
174        conn.set_tx_status(TxStatus::InTransaction);
175        Ok(Transaction(conn))
176    }
177
178    /// Performs `COMMIT` query.
179    pub async fn commit(mut self) -> Result<()> {
180        let result = self.0.query_iter("COMMIT").await?;
181        result.drop_result().await?;
182        self.0.set_tx_status(TxStatus::None);
183        Ok(())
184    }
185
186    /// Performs `ROLLBACK` query.
187    pub async fn rollback(mut self) -> Result<()> {
188        let result = self.0.query_iter("ROLLBACK").await?;
189        result.drop_result().await?;
190        self.0.set_tx_status(TxStatus::None);
191        Ok(())
192    }
193}
194
195impl Deref for Transaction<'_> {
196    type Target = Conn;
197
198    fn deref(&self) -> &Self::Target {
199        &self.0
200    }
201}
202
203impl Drop for Transaction<'_> {
204    fn drop(&mut self) {
205        if self.0.get_tx_status() == TxStatus::InTransaction {
206            self.0.set_tx_status(TxStatus::RequiresRollback);
207        }
208    }
209}