Skip to main content
DuckDB has no dedicated transaction entry point in its C API, so better-duck-core drives transactions with plain SQL (BEGIN TRANSACTION / COMMIT / ROLLBACK) through execute_batch. Three ergonomic styles are offered on Connection, each a different trade-off. transaction<T, F: FnOnce(&mut Connection) -> Result<T>>(&mut self, f) -> Result<T> runs a closure inside a transaction: BEGIN, then COMMIT on Ok or ROLLBACK on Err.
The original error is preserved — a rollback failure never masks it.

RAII guard

begin_transaction(&mut self) -> Result<Transaction<'_>> returns a better_duck_core::Transaction guard that derefs to the Connection and rolls back on drop unless committed. This is what makes the ?-early-return path safe without a closure.
  • commit(self) -> Result<()> and rollback(self) -> Result<()> consume the guard.
  • Dropping the guard without either does a best-effort rollback.

Low-level verbs

begin(), commit(), and rollback() on Connection issue the raw statements directly when you need manual control.
A transaction must not span an .await point. On the async facade run the whole unit inside with_connection (or AsyncPool::with) so BEGIN/COMMIT stay on one blocking dispatch. See async transactions.

The transaction! macro

With the derive feature, transaction!(conn, { … }) is a terser scope that commits on Ok and rolls back on Err or panic. It works for both the core Connection and a Diesel DuckDbConnection (both expose transaction(|c| -> Result<_>)):
See helper macros.

No savepoints

DuckDB supports only BEGIN / COMMIT / ROLLBACK / ABORT — not SAVEPOINT / RELEASE / ROLLBACK TO. There is deliberately no savepoint API.

See also