sql_middleware/sqlite/
transaction.rs

1use std::sync::Arc;
2
3use crate::middleware::{
4    ConversionMode, ParamConverter, ResultSet, RowValues, SqlMiddlewareDbError,
5};
6use crate::pool::MiddlewarePoolConnection;
7use crate::tx_outcome::TxOutcome;
8
9use super::connection::SqliteConnection;
10use super::params::Params;
11
12/// Transaction handle that owns the `SQLite` connection until completion.
13pub struct Tx {
14    conn: Option<SqliteConnection>,
15    translate_placeholders: bool,
16}
17
18/// Prepared statement tied to a `SQLite` transaction.
19pub struct Prepared {
20    sql: Arc<String>,
21}
22
23/// Begin a transaction, consuming the `SQLite` connection until commit/rollback.
24///
25/// `translate_placeholders` keeps the pool's translation default attached so the
26/// connection can be rewrapped after commit/rollback.
27///
28/// # Errors
29/// Returns `SqlMiddlewareDbError` if the transaction cannot be started.
30pub async fn begin_transaction(
31    mut conn: SqliteConnection,
32    translate_placeholders: bool,
33) -> Result<Tx, SqlMiddlewareDbError> {
34    conn.begin().await?;
35    Ok(Tx {
36        conn: Some(conn),
37        translate_placeholders,
38    })
39}
40
41impl Tx {
42    fn conn_mut(&mut self) -> Result<&mut SqliteConnection, SqlMiddlewareDbError> {
43        self.conn.as_mut().ok_or_else(|| {
44            SqlMiddlewareDbError::ExecutionError("SQLite transaction already completed".into())
45        })
46    }
47
48    /// Prepare a statement within this transaction.
49    ///
50    /// # Errors
51    /// Returns `SqlMiddlewareDbError` if the transaction has already completed.
52    pub fn prepare(&self, sql: &str) -> Result<Prepared, SqlMiddlewareDbError> {
53        if self.conn.is_none() {
54            return Err(SqlMiddlewareDbError::ExecutionError(
55                "SQLite transaction already completed".into(),
56            ));
57        }
58        Ok(Prepared {
59            sql: Arc::new(sql.to_owned()),
60        })
61    }
62
63    /// Execute a prepared statement as DML within this transaction.
64    ///
65    /// # Errors
66    /// Returns `SqlMiddlewareDbError` if parameter conversion or execution fails.
67    pub async fn execute_prepared(
68        &mut self,
69        prepared: &Prepared,
70        params: &[RowValues],
71    ) -> Result<usize, SqlMiddlewareDbError> {
72        let converted =
73            <Params as ParamConverter>::convert_sql_params(params, ConversionMode::Execute)?;
74        let conn = self.conn_mut()?;
75        conn.execute_dml_in_tx(prepared.sql.as_ref(), &converted.0)
76            .await
77    }
78
79    /// Execute a prepared statement as a query within this transaction.
80    ///
81    /// # Errors
82    /// Returns `SqlMiddlewareDbError` if parameter conversion or execution fails.
83    pub async fn query_prepared(
84        &mut self,
85        prepared: &Prepared,
86        params: &[RowValues],
87    ) -> Result<ResultSet, SqlMiddlewareDbError> {
88        let converted =
89            <Params as ParamConverter>::convert_sql_params(params, ConversionMode::Query)?;
90        let conn = self.conn_mut()?;
91        conn.execute_select_in_tx(
92            prepared.sql.as_ref(),
93            &converted.0,
94            super::query::build_result_set,
95        )
96        .await
97    }
98
99    /// Execute a batch inside the open transaction.
100    ///
101    /// # Errors
102    /// Returns `SqlMiddlewareDbError` if executing the batch fails.
103    pub async fn execute_batch(&mut self, sql: &str) -> Result<(), SqlMiddlewareDbError> {
104        let conn = self.conn_mut()?;
105        conn.execute_batch_in_tx(sql).await
106    }
107
108    /// Commit the transaction and surface the restored connection.
109    ///
110    /// # Errors
111    /// Returns `SqlMiddlewareDbError` if committing the transaction fails.
112    ///
113    /// Use the returned connection (via [`TxOutcome::into_restored_connection`](TxOutcome::into_restored_connection))
114    /// for further work so the pool wrapper and placeholder-translation flag stay in sync.
115    pub async fn commit(mut self) -> Result<TxOutcome, SqlMiddlewareDbError> {
116        let mut conn = self.conn.take().ok_or_else(|| {
117            SqlMiddlewareDbError::ExecutionError("SQLite transaction already completed".into())
118        })?;
119        conn.commit().await?;
120        let restored =
121            MiddlewarePoolConnection::from_sqlite_parts(conn, self.translate_placeholders);
122        Ok(TxOutcome::with_restored_connection(restored))
123    }
124
125    /// Roll back the transaction and surface the restored connection.
126    ///
127    /// # Errors
128    /// Returns `SqlMiddlewareDbError` if rolling back fails.
129    ///
130    /// Use the returned connection (via [`TxOutcome::into_restored_connection`](TxOutcome::into_restored_connection))
131    /// for further work so the pool wrapper and placeholder-translation flag stay in sync.
132    pub async fn rollback(mut self) -> Result<TxOutcome, SqlMiddlewareDbError> {
133        let mut conn = self.conn.take().ok_or_else(|| {
134            SqlMiddlewareDbError::ExecutionError("SQLite transaction already completed".into())
135        })?;
136        conn.rollback().await?;
137        let restored =
138            MiddlewarePoolConnection::from_sqlite_parts(conn, self.translate_placeholders);
139        Ok(TxOutcome::with_restored_connection(restored))
140    }
141}
142
143impl Drop for Tx {
144    fn drop(&mut self) {
145        if let Some(mut conn) = self.conn.take()
146            && let Ok(handle) = tokio::runtime::Handle::try_current()
147        {
148            handle.spawn(async move {
149                let _ = conn.rollback().await;
150            });
151        }
152    }
153}