better_duck_core::connection::Connection is the main handle you run SQL through. DuckDB is
compiled into the crate (vendored via better-duck-sys), so there is no system install to
manage — opening a connection is self-contained.
Pin the exact pre-release version — Cargo’s default version requirement excludes
-beta.N:Opening
Opens (or creates) an on-disk database. A path containing an interior NUL byte is rejected as
Error::NulError before touching DuckDB.Opens a fresh, private in-memory database.
#[must_use] — the returned connection should be used or explicitly
dropped. Each opens its own database.
Thread-safety at a glance
Connection is Send (safe to move between threads) but not Sync. All SQL methods take
&mut self, so a single connection is used by one thread at a time. For genuine concurrency use
try_clone, a Database, the
async facade, or a pool.
Cloning a connection
try_clone() -> Result<Connection>opens a second, independent connection to the same database (oneduckdb_connect, no file I/O). Both observe the same data but have separate transaction scopes. Use it to read while anAppenderor a long query holds the original.database() -> Databasereturns a shareableDatabasehandle for the backing database, so you canconnect()more connections later.
Closing
AConnection closes automatically on drop. For explicit control:
close(self) -> Result<()>(#[must_use]) consumes the connection and closes it. It errors and stays open if appenders created from it are still alive; the connection then closes once the last appender drops.is_open(&self) -> boolalways returnstrue:closeconsumes the value, so aConnectioncan only ever name an open connection. It is retained for source compatibility.
Interrupting a running query
query_control() -> QueryControl mints a QueryControl (also at better_duck_core::QueryControl)
before a query starts. It is Clone + Send + Sync and exposes only read/signal operations, so
another thread can cancel or observe the query without becoming a second concurrent user of the
connection.
interrupt() -> bool— requests cancellation. Idempotent; returnstrueif the signal reached DuckDB,falseif skipped as stale.progress() -> Option<QueryProgress>— a snapshot{ percentage, rows_processed, total_rows_to_process };percentageis-1.0when DuckDB cannot estimate.is_active() -> bool— whether the query it was minted for is still current.
Generation scoping. A control captures the connection’s query generation when minted. Once
that query finishes, the control goes stale —
is_active() turns false, progress() returns
None, and interrupt() becomes a no-op — so a delayed cancellation can never hit a later,
unrelated query. The async facade builds cancel-on-drop on exactly this.Running inside a loadable extension
unsafe fn open_from_raw(db: ffi::duckdb_database) -> Result<Connection> wraps a borrowed
duckdb_database owned by a host process — for code running inside a loadable
.duckdb_extension. The returned connection never closes that database. See
#[duckdb_entrypoint] and loadable extensions.
See also
- Configuration — tune the engine at open time with
Config. - Sharing a database — one database, many connections.
- Executing SQL — run your first statements.
- Feature flags —
chrono,async,pool,udf, and more.