Skip to main content

wasi_pg_client/transaction/
savepoint.rs

1//! Savepoint (nested transaction) support.
2
3use crate::error::Result;
4use crate::query::result::{ExecuteResult, QueryResult};
5use crate::transaction::quote_identifier;
6use crate::Transaction;
7
8/// A savepoint within an active transaction.
9///
10/// Created via [`Transaction::savepoint`].  The savepoint acts as a
11/// sub-transaction: it can be released (committed) or rolled back independently
12/// of the outer transaction.
13///
14/// # Drop behaviour
15///
16/// If the savepoint is not explicitly released or rolled back before it goes
17/// out of scope, `Drop` cannot perform async I/O.  Users should always call
18/// [`release`](Self::release) or [`rollback`](Self::rollback) explicitly.
19#[non_exhaustive]
20pub struct Savepoint<'t, 'c> {
21    pub(crate) transaction: &'t mut Transaction<'c>,
22    pub(crate) name: String,
23    pub(crate) released: bool,
24}
25
26impl<'t, 'c> Savepoint<'t, 'c> {
27    /// Release the savepoint (like a commit for the nested scope).
28    #[must_use = "savepoint release errors should be checked"]
29    pub async fn release(mut self) -> Result<()> {
30        let sql = format!("RELEASE SAVEPOINT {}", quote_identifier(&self.name));
31        self.transaction.conn.execute(&sql).await?;
32        self.released = true;
33        self.transaction.savepoint_depth -= 1;
34        Ok(())
35    }
36
37    /// Roll back to the savepoint.
38    #[must_use = "savepoint rollback errors should be checked"]
39    pub async fn rollback(mut self) -> Result<()> {
40        let sql = format!("ROLLBACK TO SAVEPOINT {}", quote_identifier(&self.name));
41        self.transaction.conn.execute(&sql).await?;
42        self.released = true;
43        self.transaction.savepoint_depth -= 1;
44        Ok(())
45    }
46
47    /// Execute a query that returns rows, within the savepoint scope.
48    #[must_use = "query errors should be checked"]
49    pub async fn query(&mut self, sql: &str) -> Result<QueryResult> {
50        self.transaction.query(sql).await
51    }
52
53    /// Execute a statement that does not return rows.
54    #[must_use = "execute errors should be checked"]
55    pub async fn execute(&mut self, sql: &str) -> Result<ExecuteResult> {
56        self.transaction.execute(sql).await
57    }
58
59    /// Execute a parameterized query that returns rows.
60    #[must_use = "query errors should be checked"]
61    pub async fn query_params(
62        &mut self,
63        sql: &str,
64        params: &[&dyn crate::types::ToSql],
65    ) -> Result<QueryResult> {
66        self.transaction.query_params(sql, params).await
67    }
68
69    /// Execute a parameterized statement that does not return rows.
70    #[must_use = "execute errors should be checked"]
71    pub async fn execute_params(
72        &mut self,
73        sql: &str,
74        params: &[&dyn crate::types::ToSql],
75    ) -> Result<ExecuteResult> {
76        self.transaction.execute_params(sql, params).await
77    }
78
79    /// Prepare a statement within the savepoint scope.
80    #[must_use = "prepare errors should be checked"]
81    pub async fn prepare(&mut self, sql: &str) -> Result<crate::query::PreparedStatement> {
82        self.transaction.prepare(sql).await
83    }
84}
85
86impl<'t, 'c> Drop for Savepoint<'t, 'c> {
87    fn drop(&mut self) {
88        if self.released || std::thread::panicking() {
89            return;
90        }
91        // Drop cannot be async.  We cannot send a ROLLBACK TO SAVEPOINT here.
92        // Users must call .release().await or .rollback().await explicitly.
93        // Best-effort: the outer Transaction::drop will eventually close the
94        // connection if the transaction is left in a bad state.
95        self.transaction.savepoint_depth -= 1;
96    }
97}