Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/nimdeveloper/better-duck/llms.txt

Use this file to discover all available pages before exploring further.

The community duckdb crate is a mature DuckDB binding for Rust, but it requires Arrow for column I/O, depends on libduckdb-sys (which historically required a separately managed system library or a large bundle step), and does not have a Diesel ORM backend. better-duck-core takes a different path: DuckDB is compiled from vendored source unconditionally, Arrow is never a dependency, and better-duck-diesel provides a full Diesel 2.3 backend on top. If you are migrating an existing project, most of the common operations map directly — the surface-level differences are small.
The CHANGELOG contains version-by-version migration notes, including breaking changes between beta releases. Always check it before upgrading between pre-1.0 versions.

Operation Comparison

The table below shows the most common operations side by side. Rows marked as identical work without any code changes.
Operationduckdb cratebetter-duck-core
Open in-memoryConnection::open_in_memory()?Connection::open_in_memory()?
Execute DDLconn.execute_batch(sql)?conn.execute_batch(sql)?
Insert / DMLconn.execute(sql, [])?conn.execute(sql)?.changes()
SELECT rowsconn.prepare(sql)?.query([])conn.execute(sql)? (is an Iterator)
Parameterizedconn.execute(sql, params![v])?conn.execute_with(sql, &mut [&mut v])?
Bulk insertconn.appender(table)?conn.appender(table, schema)?

Key Differences

1. execute returns an Iterator directly

In the duckdb crate, reading rows requires a separate prepare + query call:
// duckdb crate
let mut stmt = conn.prepare("SELECT id, label FROM events WHERE score > 8.0")?;
let mut rows = stmt.query([])?;
while let Some(row) = rows.next()? {
    let id: i32 = row.get(0)?;
}
In better-duck-core, execute returns a DuckResult which is already an Iterator over Result<DuckRow>:
// better-duck-core
let result = conn.execute("SELECT id, label FROM events WHERE score > 8.0")?;
for row in result {
    let row = row?;
    let id = row.get("id");   // returns Option<&DuckValue>
}
For DML statements, call .changes() on the returned DuckResult to get the number of affected rows:
let changes = conn.execute("INSERT INTO events VALUES (3, 'gamma', 6.1)")?.changes();

2. Parameters use &mut [&mut dyn AppendAble] instead of params![]

The duckdb crate uses a params![] macro backed by ToSql:
// duckdb crate
conn.execute("INSERT INTO t VALUES (?1, ?2)", params![42i32, "hello"])?;
In better-duck-core, parameters are $1, $2, … (1-based) and passed as a mutable slice of AppendAble references:
// better-duck-core
use better_duck_core::types::value::DuckValue;

let mut id    = DuckValue::Int(42);
let mut label = DuckValue::Text("hello".into());

conn.execute_with(
    "INSERT INTO t VALUES ($1, $2)",
    &mut [&mut id, &mut label],
)?;
Any type that implements AppendAble can be used directly — you are not limited to DuckValue. See the types::appendable module for the trait definition.

3. appender requires an explicit schema name

The duckdb crate’s appender method takes only the table name:
// duckdb crate
let mut app = conn.appender("my_table")?;
In better-duck-core, you must also provide the schema. Use "main" for the default schema:
// better-duck-core
let mut app = conn.appender("my_table", "main")?;

4. DuckValue instead of Arrow-backed types

The duckdb crate represents column values as Arrow arrays when the Arrow feature is enabled, or as generic duckdb::types::Value variants. better-duck-core uses DuckValue, a non-exhaustive enum covering the full DuckDB type hierarchy without any Arrow dependency:
use better_duck_core::types::value::DuckValue;

match value {
    DuckValue::Int(n)    => println!("integer: {n}"),
    DuckValue::Text(s)   => println!("text: {s}"),
    DuckValue::Double(f) => println!("float: {f}"),
    DuckValue::Null      => println!("null"),
    _                    => println!("other: {value:?}"),
}
DuckValue is #[non_exhaustive] — always include a _ wildcard arm.

5. No Arrow dependency

better-duck-core has no dependency on arrow, arrow2, or any Arrow-ecosystem crate. If your migration path requires Arrow interop, you will need to convert DuckValue rows to Arrow arrays manually (or keep a thin adapter layer) — this is intentional by design to avoid pulling Arrow into embedded and Tauri applications.

Removed Features

As of beta.4, the bundled feature flag no longer exists. DuckDB is compiled from vendored source unconditionally via the new better-duck-sys crate — there is no “system library” build mode. If your Cargo.toml references features = ["bundled"], remove it:
# Before (beta.2 / beta.3)
better-duck-core = { version = "0.1.0-beta.3", features = ["bundled"] }

# After (beta.4+)
better-duck-core = "0.1.0-beta.4"
The buildtime_bindgen feature is still present but is only needed by maintainers to regenerate the pre-checked-in FFI bindings using LLVM/clang. Ordinary consumers never need it.

Build docs developers (and LLMs) love