Summary
Finish the transaction-API migration by moving hyperdb-mcp::Engine::execute_in_transaction (and its ~7 closure call sites) off the deprecated raw Connection::begin_transaction / commit / rollback methods and onto the RAII Transaction guard. This is the remaining hold-out from #69 (PR #71).
Background
#69 retired the &self raw transaction trio from hyperdb-api's public surface (deprecated, hidden from rustdoc) and migrated MCP's four async pool-path ingest functions to the RAII guard. The sync path through Engine::execute_in_transaction was deliberately not migrated because it's a structural cascade rather than a mechanical one.
Engine today holds the connection by value and exposes operations as &self methods so its containing Arc<RwLock<Engine>> lock model can support concurrent read-only reuse. The RAII guard requires &mut self on Connection::transaction, so any path that closes over Engine while holding a transaction can't use the guard without one of two structural changes.
The deprecated call sites
hyperdb-mcp/src/engine.rs:725-770 — Engine::execute_in_transaction(&self, f: F) annotated with #[allow(deprecated, reason = "Engine borrows &self; the RAII guard requires &mut. Migration tracked as a follow-up.")]. Body uses:
self.connection.begin_transaction() — line 727
self.connection.commit() — line 744
self.connection.rollback() — line 749 (error path)
self.connection.rollback() — line 769 (panic recovery path)
8 closure callers across the workspace, each passing |engine| { ... engine.execute_command(...) ... }:
hyperdb-mcp/src/ingest.rs:652, 789, 883
hyperdb-mcp/src/ingest_arrow.rs:318, 611
hyperdb-mcp/src/lakehouse.rs:196
hyperdb-mcp/src/server.rs:2270
- Test/integration sites in
hyperdb-mcp/tests/
Two implementation paths
Path A — Engine::transaction(&mut self) -> Result<EngineTransaction<'_>>
Add an analogue of hyperdb-api::Transaction at the Engine level. The guard delegates the same query/insert/catalog helpers &Engine exposes today, but its existence borrows &mut Engine exclusively (matching Transaction's &mut Connection borrow).
Closure call sites rewrite:
// before
engine.execute_in_transaction(|engine| {
engine.create_table_in(...)?;
engine.execute_command(&sql)?;
Ok(row_count)
})?;
// after
let txn = engine.transaction()?;
txn.create_table_in(...)?;
txn.execute_command(&sql)?;
let row_count = ...;
txn.commit()?;
Pro: mirrors the Connection / Transaction pattern symmetrically; compile-time exclusivity at the engine boundary.
Con: requires every closure callsite to be flattened (no more closure scope). EngineTransaction needs to expose the subset of Engine methods that callers actually use inside transactions — non-trivial surface to design.
Path B — Mutex<Connection> inside Engine
Keep Engine's &self external API, but wrap connection so a &self method can transiently take &mut. execute_in_transaction becomes:
pub fn execute_in_transaction<F, T>(&self, f: F) -> Result<T, McpError>
where F: FnOnce(&Engine) -> Result<T, McpError>
{
let mut conn = self.connection.lock()?; // or tokio::Mutex for async paths
let txn = conn.transaction()?;
// ... work ...
}
Pro: preserves all existing closure call sites unchanged.
Con: introduces runtime locking where there was none. Every &self method that accesses the connection now competes for the same mutex. Possibly fine (the connection was already serialized by Connection's wire protocol), but it's a real model change. Also, the panic-safety story (std::panic::catch_unwind already in execute_in_transaction) needs to be re-validated under lock-poisoning semantics.
Recommendation
Path A is the conceptually cleaner end state. Path B is a smaller diff. Pick after measuring the surface area EngineTransaction would need to expose — if more than ~6 methods, Path B is probably the pragmatic call.
Acceptance criteria
Related
Estimate
Path A: ~1 day (designing EngineTransaction API surface + rewriting 8 call sites + tests).
Path B: ~2 hours (wrap connection in Mutex, update execute_in_transaction, re-verify lock interaction).
Summary
Finish the transaction-API migration by moving
hyperdb-mcp::Engine::execute_in_transaction(and its ~7 closure call sites) off the deprecated rawConnection::begin_transaction/commit/rollbackmethods and onto the RAIITransactionguard. This is the remaining hold-out from #69 (PR #71).Background
#69 retired the
&selfraw transaction trio fromhyperdb-api's public surface (deprecated, hidden from rustdoc) and migrated MCP's four async pool-path ingest functions to the RAII guard. The sync path throughEngine::execute_in_transactionwas deliberately not migrated because it's a structural cascade rather than a mechanical one.Enginetoday holds the connection by value and exposes operations as&selfmethods so its containingArc<RwLock<Engine>>lock model can support concurrent read-only reuse. The RAII guard requires&mut selfonConnection::transaction, so any path that closes overEnginewhile holding a transaction can't use the guard without one of two structural changes.The deprecated call sites
hyperdb-mcp/src/engine.rs:725-770—Engine::execute_in_transaction(&self, f: F)annotated with#[allow(deprecated, reason = "Engine borrows &self; the RAII guard requires &mut. Migration tracked as a follow-up.")]. Body uses:self.connection.begin_transaction()— line 727self.connection.commit()— line 744self.connection.rollback()— line 749 (error path)self.connection.rollback()— line 769 (panic recovery path)8 closure callers across the workspace, each passing
|engine| { ... engine.execute_command(...) ... }:hyperdb-mcp/src/ingest.rs:652, 789, 883hyperdb-mcp/src/ingest_arrow.rs:318, 611hyperdb-mcp/src/lakehouse.rs:196hyperdb-mcp/src/server.rs:2270hyperdb-mcp/tests/Two implementation paths
Path A —
Engine::transaction(&mut self) -> Result<EngineTransaction<'_>>Add an analogue of
hyperdb-api::Transactionat theEnginelevel. The guard delegates the same query/insert/catalog helpers&Engineexposes today, but its existence borrows&mut Engineexclusively (matchingTransaction's&mut Connectionborrow).Closure call sites rewrite:
Pro: mirrors the
Connection/Transactionpattern symmetrically; compile-time exclusivity at the engine boundary.Con: requires every closure callsite to be flattened (no more closure scope).
EngineTransactionneeds to expose the subset ofEnginemethods that callers actually use inside transactions — non-trivial surface to design.Path B —
Mutex<Connection>insideEngineKeep
Engine's&selfexternal API, but wrapconnectionso a&selfmethod can transiently take&mut.execute_in_transactionbecomes:Pro: preserves all existing closure call sites unchanged.
Con: introduces runtime locking where there was none. Every
&selfmethod that accesses the connection now competes for the same mutex. Possibly fine (the connection was already serialized byConnection's wire protocol), but it's a real model change. Also, the panic-safety story (std::panic::catch_unwindalready inexecute_in_transaction) needs to be re-validated under lock-poisoning semantics.Recommendation
Path A is the conceptually cleaner end state. Path B is a smaller diff. Pick after measuring the surface area
EngineTransactionwould need to expose — if more than ~6 methods, Path B is probably the pragmatic call.Acceptance criteria
Engine::execute_in_transactionno longer calls the deprecatedConnection::begin_transaction/commit/rollback. The#[allow(deprecated, reason = "...")]annotation can be removed.std::panic::catch_unwind+ best-effort rollback story atengine.rs:740-771continues to work.Mutex-related deadlock risks; if Path B is chosen, document the lock ordering with respect toHyperMcpServer's outerArc<RwLock<Engine>>(or whatever wrapsEngine).pub fn begin_transaction/commit/rollbackonConnectionandAsyncConnectionhave zero in-tree callers (verify withgrep). At that point a follow-up PR can delete the deprecated wrappers and the_rawrename, completing the API consolidation.Related
MIGRATING-0.3.md— describes the deprecation strategy and references this follow-up.docs/TRANSACTIONS.md— same.Estimate
Path A: ~1 day (designing
EngineTransactionAPI surface + rewriting 8 call sites + tests).Path B: ~2 hours (wrap
connectioninMutex, updateexecute_in_transaction, re-verify lock interaction).