Skip to main content
Requires feature = "async". AsyncConnection and AsyncDatabase re-export at the crate root.
The async facade wraps a Connection so DuckDB never blocks the async executor: every call dispatches to tokio::task::spawn_blocking. Because a borrowing DuckResult is not Send, the async methods materialize — they return an owned ResultSet.
AsyncConnection is Clone + Send + Sync; clones share one connection serialized by an internal mutex.
These methods panic if called outside a Tokio runtime — the same contract as tokio::task::spawn_blocking.

Methods

Open on a blocking thread.
Run ;-separated statements, discarding results.
Run one statement, materialize the result.
Positional-parameterized execute. Binds are owned DuckValues so they are Send.
The primitive every other method builds on — run anything (a transaction, a prepared statement) on the blocking thread.
Step the query one DuckDB task per dispatch, yielding to the runtime between tasks — see below.
Run a closure against a bulk-insert Appender; the appender holds raw FFI pointers, so it never leaves the closure.
Wrap an existing Connection; recover it if this is the last clone (Err(self) otherwise).
Mutex-free control — works while a query holds the connection mutex on a blocking thread.

Running a transaction

A transaction must not span an .await. Keep the whole unit inside with_connection, which runs on one blocking dispatch:

execute_pending

execute_pending is the async form of pending execution: rather than run the whole query in one blocking call, each execute_task runs on its own blocking dispatch, so a long query neither monopolizes a blocking thread nor blocks cancellation.

Cancellation (cancel-on-drop)

The query futures request interruption of the native query if the future is dropped. Because spawn_blocking tasks cannot be aborted, the drop signals DuckDB to interrupt and lets the blocking task wind down — it does not stop native work instantly. The connection becomes usable again once that task releases the mutex. query_control() / interrupt() / progress() go through a mutex-free control source, so they work while a query holds the connection mutex on a blocking thread. Generation scoping makes a late interrupt safe — a finished query’s control is stale.

AsyncDatabase

AsyncDatabase mirrors Database for sharing one database across async connections:
from_database(db) / database() bridge to and from a sync Database. Like AsyncConnection, these methods panic outside a Tokio runtime.

See also