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#[derive(Debug)]
130pub struct Transaction<'a>(pub(crate) Connection<'a, 'static>);
131
132impl<'a> Transaction<'a> {
133    pub(crate) async fn new<T: Into<Connection<'a, 'static>>>(
134        conn: T,
135        options: TxOpts,
136    ) -> Result<Transaction<'a>> {
137        let TxOpts {
138            consistent_snapshot,
139            isolation_level,
140            readonly,
141        } = options;
142
143        let mut conn = conn.into();
144
145        if conn.get_tx_status() != TxStatus::None {
146            return Err(DriverError::NestedTransaction.into());
147        }
148
149        if readonly.is_some() && conn.server_version() < (5, 6, 5) {
150            return Err(DriverError::ReadOnlyTransNotSupported.into());
151        }
152
153        if let Some(isolation_level) = isolation_level {
154            let query = format!("SET TRANSACTION ISOLATION LEVEL {}", isolation_level);
155            conn.query_drop(query).await?;
156        }
157
158        if let Some(readonly) = readonly {
159            if readonly {
160                conn.query_drop("SET TRANSACTION READ ONLY").await?;
161            } else {
162                conn.query_drop("SET TRANSACTION READ WRITE").await?;
163            }
164        }
165
166        if consistent_snapshot {
167            conn.query_drop("START TRANSACTION WITH CONSISTENT SNAPSHOT")
168                .await?
169        } else {
170            conn.query_drop("START TRANSACTION").await?
171        };
172
173        conn.set_tx_status(TxStatus::InTransaction);
174        Ok(Transaction(conn))
175    }
176
177    /// Performs `COMMIT` query.
178    pub async fn commit(mut self) -> Result<()> {
179        let result = self.0.query_iter("COMMIT").await?;
180        result.drop_result().await?;
181        self.0.set_tx_status(TxStatus::None);
182        Ok(())
183    }
184
185    /// Performs `ROLLBACK` query.
186    pub async fn rollback(mut self) -> Result<()> {
187        let result = self.0.query_iter("ROLLBACK").await?;
188        result.drop_result().await?;
189        self.0.set_tx_status(TxStatus::None);
190        Ok(())
191    }
192}
193
194impl Deref for Transaction<'_> {
195    type Target = Conn;
196
197    fn deref(&self) -> &Self::Target {
198        &self.0
199    }
200}
201
202impl Drop for Transaction<'_> {
203    fn drop(&mut self) {
204        if self.0.get_tx_status() == TxStatus::InTransaction {
205            self.0.set_tx_status(TxStatus::RequiresRollback);
206        }
207    }
208}