Summary
Connection exposes two parallel transaction APIs with different safety properties. The &self free-function trio (begin_transaction / commit / rollback) bypasses every compile-time guarantee that the RAII Transaction<'conn> guard provides. This is a footgun the documentation itself argues against. Proposing to remove the free-function trio.
Current state
hyperdb-api/src/connection.rs:
Style 1 — free-function (&self, no RAII):
pub fn begin_transaction(&self) -> Result<()> // line 1907
pub fn commit(&self) -> Result<()> // line 1918
pub fn rollback(&self) -> Result<()> // line 1929
Takes &self, so nothing prevents other code from issuing SQL on the same Connection while believing it is outside the transaction. Forgetting to call commit/rollback silently leaks the transaction until the next statement on the session.
Style 2 — RAII guard (&mut self):
pub fn transaction(&mut self) -> Result<crate::Transaction<'_>> // line 1960
Holds &'conn mut Connection, which the borrow checker uses to forbid any other use of the connection while the transaction is active. Transaction::commit(self) and rollback(self) consume the guard, so double-commit is a compile error. If the guard is dropped without an explicit decision, Drop issues a ROLLBACK.
docs/TRANSACTIONS.md explicitly promotes Style 2 as a core safety property of this API:
The protection is enforced at compile time with zero runtime cost. … You cannot accidentally commit twice, rollback after commit, or execute queries on a finished transaction.
Style 1 silently undoes all of that. Original gap analysis: §10 of docs/RUST_API_GAP_ANALYSIS.md (predecessor repo) — "Two parallel transaction APIs … the free-function style bypasses the RAII guard."
Proposed work
Migration
Mechanical, one-to-one. Before:
conn.begin_transaction()?;
conn.execute_command("INSERT INTO users VALUES (1, 'Alice')")?;
conn.commit()?;
After:
let txn = conn.transaction()?;
txn.execute_command("INSERT INTO users VALUES (1, 'Alice')")?;
txn.commit()?;
The &mut requirement on transaction() may force callers to re-shape some borrows, but that's the point — those are the call sites that were silently unsafe.
Backwards compatibility
Breaking change. Any caller using begin_transaction / commit / rollback directly will fail to compile until they migrate. Released as feat!: per Conventional Commits with a major version bump.
Alternative considered (not proposed)
Keeping the free-function trio behind a low-level cargo feature, off by default. Rejected because:
- The current methods have no use case the RAII guard can't cover.
- Cargo features fragment the public API — downstream code that flips the feature on poisons the dependency graph.
- The whole point is to remove the footgun, not hide it from search results.
Open questions
- Is there any internal caller (within the
hyperdb-api crate, in a test helper, or in the MCP server) that genuinely needs &self transaction control? If yes, the methods should be moved to pub(crate) rather than removed outright.
Summary
Connectionexposes two parallel transaction APIs with different safety properties. The&selffree-function trio (begin_transaction/commit/rollback) bypasses every compile-time guarantee that the RAIITransaction<'conn>guard provides. This is a footgun the documentation itself argues against. Proposing to remove the free-function trio.Current state
hyperdb-api/src/connection.rs:Style 1 — free-function (
&self, no RAII):Takes
&self, so nothing prevents other code from issuing SQL on the sameConnectionwhile believing it is outside the transaction. Forgetting to callcommit/rollbacksilently leaks the transaction until the next statement on the session.Style 2 — RAII guard (
&mut self):Holds
&'conn mut Connection, which the borrow checker uses to forbid any other use of the connection while the transaction is active.Transaction::commit(self)androllback(self)consume the guard, so double-commit is a compile error. If the guard is dropped without an explicit decision,Dropissues aROLLBACK.docs/TRANSACTIONS.mdexplicitly promotes Style 2 as a core safety property of this API:Style 1 silently undoes all of that. Original gap analysis: §10 of
docs/RUST_API_GAP_ANALYSIS.md(predecessor repo) — "Two parallel transaction APIs … the free-function style bypasses the RAII guard."Proposed work
begin_transaction/commit/rollbackfrom the public surface ofConnection(and the equivalents onAsyncConnectionif they exist).conn.transaction()instead.docs/TRANSACTIONS.mdto remove any references to the free-function form.begin_transaction/commit/rollbackcall site rewrites tolet txn = conn.transaction()?; … txn.commit()?;).Migration
Mechanical, one-to-one. Before:
After:
The
&mutrequirement ontransaction()may force callers to re-shape some borrows, but that's the point — those are the call sites that were silently unsafe.Backwards compatibility
Breaking change. Any caller using
begin_transaction/commit/rollbackdirectly will fail to compile until they migrate. Released asfeat!:per Conventional Commits with a major version bump.Alternative considered (not proposed)
Keeping the free-function trio behind a
low-levelcargo feature, off by default. Rejected because:Open questions
hyperdb-apicrate, in a test helper, or in the MCP server) that genuinely needs&selftransaction control? If yes, the methods should be moved topub(crate)rather than removed outright.