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}