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        // Use the crate-internal `_raw` family. The matching `pub`
40        // methods on `Connection` are `#[deprecated]` for downstream
41        // consumers; this guard is the recommended replacement.
42        connection.begin_transaction_raw()?;
43        Ok(Self {
44            connection,
45            completed: false,
46        })
47    }
48
49    /// Commits the transaction.
50    ///
51    /// # Errors
52    ///
53    /// Returns the error from the server's `COMMIT`. The transaction is
54    /// marked completed regardless, so the drop guard will not re-issue a
55    /// rollback.
56    pub fn commit(mut self) -> Result<()> {
57        self.completed = true;
58        self.connection.commit_raw()
59    }
60
61    /// Rolls back the transaction explicitly.
62    ///
63    /// # Errors
64    ///
65    /// Returns the error from the server's `ROLLBACK`. The transaction
66    /// is marked completed regardless.
67    pub fn rollback(mut self) -> Result<()> {
68        self.completed = true;
69        self.connection.rollback_raw()
70    }
71
72    /// Returns a reference to the underlying connection.
73    #[must_use]
74    pub fn connection(&self) -> &Connection {
75        self.connection
76    }
77
78    // =========================================================================
79    // Delegated execution methods
80    // =========================================================================
81
82    /// Executes a SQL command within this transaction.
83    ///
84    /// # Errors
85    ///
86    /// Forwards the error from [`Connection::execute_command`].
87    pub fn execute_command(&self, sql: &str) -> Result<u64> {
88        self.connection.execute_command(sql)
89    }
90
91    /// Executes a query and returns streaming results within this transaction.
92    ///
93    /// # Errors
94    ///
95    /// Forwards the error from [`Connection::execute_query`].
96    pub fn execute_query(&self, query: &str) -> Result<crate::Rowset<'_>> {
97        self.connection.execute_query(query)
98    }
99
100    /// Fetches a single row from a query.
101    ///
102    /// # Errors
103    ///
104    /// Forwards the error from [`Connection::fetch_one`].
105    pub fn fetch_one<Q: AsRef<str>>(&self, query: Q) -> Result<crate::Row> {
106        self.connection.fetch_one(query)
107    }
108
109    /// Fetches an optional single row from a query.
110    ///
111    /// # Errors
112    ///
113    /// Forwards the error from [`Connection::fetch_optional`].
114    pub fn fetch_optional<Q: AsRef<str>>(&self, query: Q) -> Result<Option<crate::Row>> {
115        self.connection.fetch_optional(query)
116    }
117
118    /// Fetches all rows from a query.
119    ///
120    /// # Errors
121    ///
122    /// Forwards the error from [`Connection::fetch_all`].
123    pub fn fetch_all<Q: AsRef<str>>(&self, query: Q) -> Result<Vec<crate::Row>> {
124        self.connection.fetch_all(query)
125    }
126
127    /// Fetches a single scalar value from a query.
128    ///
129    /// # Errors
130    ///
131    /// Forwards the error from [`Connection::fetch_scalar`].
132    pub fn fetch_scalar<T, Q>(&self, query: Q) -> Result<T>
133    where
134        T: ScalarValue + crate::result::RowValue,
135        Q: AsRef<str>,
136    {
137        self.connection.fetch_scalar(query)
138    }
139
140    /// Fetches an optional scalar value from a query.
141    ///
142    /// # Errors
143    ///
144    /// Forwards the error from [`Connection::fetch_optional_scalar`].
145    pub fn fetch_optional_scalar<T, Q>(&self, query: Q) -> Result<Option<T>>
146    where
147        T: ScalarValue + crate::result::RowValue,
148        Q: AsRef<str>,
149    {
150        self.connection.fetch_optional_scalar(query)
151    }
152
153    /// Queries for a count value, defaulting to 0 if NULL.
154    ///
155    /// # Errors
156    ///
157    /// Forwards the error from [`Connection::query_count`].
158    pub fn query_count(&self, query: &str) -> Result<i64> {
159        self.connection.query_count(query)
160    }
161}
162
163impl Drop for Transaction<'_> {
164    fn drop(&mut self) {
165        if !self.completed {
166            // Best-effort rollback; ignore errors during drop.
167            // Hyper produces a WARNING (not error) if no active transaction.
168            let _ = self.connection.rollback_raw();
169        }
170    }
171}