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}