Skip to main content

orbit_orm/
transaction.rs

1#[cfg(feature = "mysql")]
2use sqlx::MySql;
3#[cfg(feature = "mysql")]
4use sqlx::MySqlConnection;
5#[cfg(feature = "postgres")]
6use sqlx::Postgres;
7#[cfg(feature = "sqlite")]
8use sqlx::Sqlite;
9
10use crate::database::Pool;
11use crate::{Database, Driver, Error};
12
13/// An open transaction, given to the closure of `Database::transaction`. Pass it to queries as
14/// `&mut *tx`. It commits or rolls back when that closure returns.
15pub struct Transaction {
16    pub(crate) inner: TxInner,
17}
18
19/// The open transaction on the connection of one driver.
20#[doc(hidden)]
21pub enum TxInner {
22    #[cfg(feature = "mysql")]
23    MySql(sqlx::Transaction<'static, MySql>),
24    #[cfg(feature = "postgres")]
25    Postgres(sqlx::Transaction<'static, Postgres>),
26    #[cfg(feature = "sqlite")]
27    Sqlite(sqlx::Transaction<'static, Sqlite>),
28}
29
30impl TxInner {
31    pub(crate) fn driver(&self) -> Driver {
32        match self {
33            #[cfg(feature = "mysql")]
34            Self::MySql(_) => Driver::MySql,
35            #[cfg(feature = "postgres")]
36            Self::Postgres(_) => Driver::Postgres,
37            #[cfg(feature = "sqlite")]
38            Self::Sqlite(_) => Driver::Sqlite,
39        }
40    }
41}
42
43impl Transaction {
44    pub(crate) async fn begin(pool: &Pool) -> Result<Self, Error> {
45        let inner = match pool {
46            #[cfg(feature = "mysql")]
47            Pool::MySql(pool) => TxInner::MySql(pool.begin().await?),
48            #[cfg(feature = "postgres")]
49            Pool::Postgres(pool) => TxInner::Postgres(pool.begin().await?),
50            #[cfg(feature = "sqlite")]
51            Pool::Sqlite(pool) => TxInner::Sqlite(pool.begin().await?),
52        };
53        Ok(Self { inner })
54    }
55
56    pub(crate) async fn commit(self) -> Result<(), Error> {
57        match self.inner {
58            #[cfg(feature = "mysql")]
59            TxInner::MySql(tx) => Ok(tx.commit().await?),
60            #[cfg(feature = "postgres")]
61            TxInner::Postgres(tx) => Ok(tx.commit().await?),
62            #[cfg(feature = "sqlite")]
63            TxInner::Sqlite(tx) => Ok(tx.commit().await?),
64        }
65    }
66
67    pub(crate) async fn rollback(self) -> Result<(), Error> {
68        match self.inner {
69            #[cfg(feature = "mysql")]
70            TxInner::MySql(tx) => Ok(tx.rollback().await?),
71            #[cfg(feature = "postgres")]
72            TxInner::Postgres(tx) => Ok(tx.rollback().await?),
73            #[cfg(feature = "sqlite")]
74            TxInner::Sqlite(tx) => Ok(tx.rollback().await?),
75        }
76    }
77
78    /// The database this transaction runs on.
79    pub fn driver(&self) -> Driver {
80        self.inner.driver()
81    }
82
83    /// The MySQL connection of this transaction, for queries that Orbit does not wrap yet.
84    ///
85    /// # Panics
86    ///
87    /// Panics when the database is not MySQL. Check `driver()` first.
88    #[cfg(feature = "mysql")]
89    pub fn connection(&mut self) -> &mut MySqlConnection {
90        #[cfg(any(feature = "postgres", feature = "sqlite"))]
91        let driver = self.inner.driver();
92        match &mut self.inner {
93            TxInner::MySql(tx) => tx,
94            #[cfg(any(feature = "postgres", feature = "sqlite"))]
95            _ => panic!(
96                "`connection` is for MySQL; this transaction is on {}",
97                driver.name()
98            ),
99        }
100    }
101}
102
103/// Where a query runs: a `&Database`, on a pooled connection, or a `&mut Transaction`, on the
104/// transaction's connection. Terminal methods such as `get` take either.
105pub trait Executor<'a> {
106    #[doc(hidden)]
107    fn target(self) -> Target<'a>;
108}
109
110/// The connection a query runs on.
111#[doc(hidden)]
112pub enum Target<'a> {
113    Pool(&'a Pool),
114    Tx(&'a mut TxInner),
115}
116
117impl Target<'_> {
118    /// The database the query runs on.
119    pub(crate) fn driver(&self) -> Driver {
120        match self {
121            Self::Pool(pool) => pool.driver(),
122            Self::Tx(tx) => tx.driver(),
123        }
124    }
125}
126
127impl<'a> Executor<'a> for &'a Database {
128    fn target(self) -> Target<'a> {
129        Target::Pool(&self.pool)
130    }
131}
132
133impl<'a> Executor<'a> for &'a mut Transaction {
134    fn target(self) -> Target<'a> {
135        Target::Tx(&mut self.inner)
136    }
137}