Skip to main content

hyperdb_api/
transaction.rs

1// Copyright (c) 2026, Salesforce, Inc. All rights reserved.
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3
4//! RAII transaction guard for synchronous connections.
5
6use crate::connection::{Connection, ScalarValue};
7use crate::error::Result;
8
9/// An RAII transaction guard that automatically rolls back on drop if not committed.
10///
11/// Created via [`Connection::transaction()`]. This **exclusively borrows** the
12/// connection for the lifetime of the transaction, preventing any other code from
13/// using the raw connection while the transaction is active. This is enforced at
14/// compile time by Rust's borrow checker.
15///
16/// # Example
17///
18/// ```no_run
19/// # use hyperdb_api::{Connection, CreateMode, Result};
20/// # fn main() -> Result<()> {
21/// # let mut conn = Connection::connect("localhost:7483", "test.hyper", CreateMode::DoNotCreate)?;
22/// // Transaction auto-rolls back if commit() is not called
23/// let txn = conn.transaction()?;
24/// txn.execute_command("INSERT INTO users VALUES (1, 'Alice')")?;
25/// txn.execute_command("INSERT INTO users VALUES (2, 'Bob')")?;
26/// txn.commit()?;
27/// # Ok(())
28/// # }
29/// ```
30#[derive(Debug)]
31pub struct Transaction<'conn> {
32    connection: &'conn mut Connection,
33    completed: bool,
34}
35
36impl<'conn> Transaction<'conn> {
37    /// Creates a new transaction by issuing `BEGIN TRANSACTION`.
38    pub(crate) fn new(connection: &'conn mut Connection) -> Result<Self> {
39        connection.begin_transaction()?;
40        Ok(Self {
41            connection,
42            completed: false,
43        })
44    }
45
46    /// Commits the transaction.
47    ///
48    /// # Errors
49    ///
50    /// Returns the error from [`Connection::commit`]. The transaction is
51    /// marked completed regardless, so the drop guard will not re-issue a
52    /// rollback.
53    pub fn commit(mut self) -> Result<()> {
54        self.completed = true;
55        self.connection.commit()
56    }
57
58    /// Rolls back the transaction explicitly.
59    ///
60    /// # Errors
61    ///
62    /// Returns the error from [`Connection::rollback`]. The transaction is
63    /// marked completed regardless.
64    pub fn rollback(mut self) -> Result<()> {
65        self.completed = true;
66        self.connection.rollback()
67    }
68
69    /// Returns a reference to the underlying connection.
70    #[must_use]
71    pub fn connection(&self) -> &Connection {
72        self.connection
73    }
74
75    // =========================================================================
76    // Delegated execution methods
77    // =========================================================================
78
79    /// Executes a SQL command within this transaction.
80    ///
81    /// # Errors
82    ///
83    /// Forwards the error from [`Connection::execute_command`].
84    pub fn execute_command(&self, sql: &str) -> Result<u64> {
85        self.connection.execute_command(sql)
86    }
87
88    /// Executes a query and returns streaming results within this transaction.
89    ///
90    /// # Errors
91    ///
92    /// Forwards the error from [`Connection::execute_query`].
93    pub fn execute_query(&self, query: &str) -> Result<crate::Rowset<'_>> {
94        self.connection.execute_query(query)
95    }
96
97    /// Fetches a single row from a query.
98    ///
99    /// # Errors
100    ///
101    /// Forwards the error from [`Connection::fetch_one`].
102    pub fn fetch_one<Q: AsRef<str>>(&self, query: Q) -> Result<crate::Row> {
103        self.connection.fetch_one(query)
104    }
105
106    /// Fetches an optional single row from a query.
107    ///
108    /// # Errors
109    ///
110    /// Forwards the error from [`Connection::fetch_optional`].
111    pub fn fetch_optional<Q: AsRef<str>>(&self, query: Q) -> Result<Option<crate::Row>> {
112        self.connection.fetch_optional(query)
113    }
114
115    /// Fetches all rows from a query.
116    ///
117    /// # Errors
118    ///
119    /// Forwards the error from [`Connection::fetch_all`].
120    pub fn fetch_all<Q: AsRef<str>>(&self, query: Q) -> Result<Vec<crate::Row>> {
121        self.connection.fetch_all(query)
122    }
123
124    /// Fetches a single scalar value from a query.
125    ///
126    /// # Errors
127    ///
128    /// Forwards the error from [`Connection::fetch_scalar`].
129    pub fn fetch_scalar<T, Q>(&self, query: Q) -> Result<T>
130    where
131        T: ScalarValue + crate::result::RowValue,
132        Q: AsRef<str>,
133    {
134        self.connection.fetch_scalar(query)
135    }
136
137    /// Fetches an optional scalar value from a query.
138    ///
139    /// # Errors
140    ///
141    /// Forwards the error from [`Connection::fetch_optional_scalar`].
142    pub fn fetch_optional_scalar<T, Q>(&self, query: Q) -> Result<Option<T>>
143    where
144        T: ScalarValue + crate::result::RowValue,
145        Q: AsRef<str>,
146    {
147        self.connection.fetch_optional_scalar(query)
148    }
149
150    /// Queries for a count value, defaulting to 0 if NULL.
151    ///
152    /// # Errors
153    ///
154    /// Forwards the error from [`Connection::query_count`].
155    pub fn query_count(&self, query: &str) -> Result<i64> {
156        self.connection.query_count(query)
157    }
158}
159
160impl Drop for Transaction<'_> {
161    fn drop(&mut self) {
162        if !self.completed {
163            // Best-effort rollback; ignore errors during drop.
164            // Hyper produces a WARNING (not error) if no active transaction.
165            let _ = self.connection.rollback();
166        }
167    }
168}