Skip to main content

floz_orm/db/
tx.rs

1//! `Tx` — polymorphic database transaction.
2//!
3//! Wraps a pooled connection with an active transaction.
4//! If `commit()` is not called before the `Tx` is dropped,
5//! the transaction is automatically rolled back when the connection
6//! returns to the pool.
7
8use tokio::sync::Mutex;
9
10use crate::error::FlozError;
11use super::placeholder::replace_pg_placeholders;
12
13/// Inner transaction enum — one variant per enabled backend.
14pub(crate) enum TxInner {
15    #[cfg(feature = "postgres")]
16    Postgres(Mutex<sqlx::pool::PoolConnection<sqlx::Postgres>>),
17    #[cfg(feature = "sqlite")]
18    Sqlite(Mutex<sqlx::pool::PoolConnection<sqlx::Sqlite>>),
19}
20
21/// A database transaction.
22pub struct Tx {
23    pub(crate) inner: TxInner,
24    pub(crate) committed: bool,
25}
26
27impl Tx {
28    /// Commit the transaction.
29    #[allow(unreachable_patterns)]
30    pub async fn commit(mut self) -> Result<(), FlozError> {
31        match &self.inner {
32            #[cfg(feature = "postgres")]
33            TxInner::Postgres(conn) => {
34                let mut c = conn.lock().await;
35                sqlx::query("COMMIT").execute(&mut **c).await?;
36            }
37            #[cfg(feature = "sqlite")]
38            TxInner::Sqlite(conn) => {
39                let mut c = conn.lock().await;
40                sqlx::query("COMMIT").execute(&mut **c).await?;
41            }
42            _ => unreachable!("no database backend enabled"),
43        }
44        self.committed = true;
45        Ok(())
46    }
47
48    /// Explicitly rollback the transaction.
49    #[allow(unreachable_patterns)]
50    pub async fn rollback(self) -> Result<(), FlozError> {
51        match &self.inner {
52            #[cfg(feature = "postgres")]
53            TxInner::Postgres(conn) => {
54                let mut c = conn.lock().await;
55                sqlx::query("ROLLBACK").execute(&mut **c).await?;
56            }
57            #[cfg(feature = "sqlite")]
58            TxInner::Sqlite(conn) => {
59                let mut c = conn.lock().await;
60                sqlx::query("ROLLBACK").execute(&mut **c).await?;
61            }
62            _ => unreachable!("no database backend enabled"),
63        }
64        Ok(())
65    }
66
67    /// Adjust SQL placeholder syntax for the current backend.
68    #[allow(unreachable_patterns)]
69    pub(crate) fn adjust_sql(&self, sql: &str) -> String {
70        match &self.inner {
71            #[cfg(feature = "postgres")]
72            TxInner::Postgres(_) => sql.to_string(),
73            #[cfg(feature = "sqlite")]
74            TxInner::Sqlite(_) => replace_pg_placeholders(sql),
75            _ => unreachable!("no database backend enabled"),
76        }
77    }
78}
79
80impl std::fmt::Debug for Tx {
81    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
82        f.debug_struct("Tx")
83            .field("committed", &self.committed)
84            .finish()
85    }
86}