Values & Parameter Extraction

When a table function receives bind-time parameters, DuckDB passes them as duckdb_value handles. These handles are heap-allocated and must be destroyed after use. The Value wrapper handles this automatically via RAII.


The problem

Without Value, every parameter extraction requires three raw FFI calls and careful manual cleanup:

#![allow(unused)]
fn main() {
// Before: raw FFI — easy to leak memory
let param = duckdb_bind_get_parameter(info, 0);
let n = duckdb_get_int64(param);
duckdb_destroy_value(&mut { param });  // forget this → memory leak
}

The solution: Value

Value wraps a duckdb_value handle and calls duckdb_destroy_value on drop:

#![allow(unused)]
fn main() {
use quack_rs::table::BindInfo;

unsafe extern "C" fn my_bind(info: duckdb_bind_info) {
    let bind_info = unsafe { BindInfo::new(info) };

    // Value is RAII — automatically destroyed when dropped
    let n = unsafe { bind_info.get_parameter_value(0) }.as_i64();

    // Named parameters work the same way
    let path = unsafe { bind_info.get_named_parameter_value("path") }
        .as_str()
        .unwrap_or_default();
}
}

Typed extraction methods

MethodDuckDB typeRust type
as_str()VARCHARResult<String, ExtensionError>
as_i32()INTEGERi32
as_i64()BIGINTi64
as_f32()FLOATf32
as_f64()DOUBLEf64
as_bool()BOOLEANbool

DuckDB will attempt to cast the value to the requested type. If the cast fails, numeric methods return 0 / 0.0 / false; as_str() returns an error.

Checking for NULL

#![allow(unused)]
fn main() {
let val = unsafe { bind_info.get_parameter_value(0) };
if val.is_null() {
    // parameter was NULL or not provided
}
}

Escape hatch

If you need the raw handle for an API not yet wrapped:

#![allow(unused)]
fn main() {
let val = unsafe { bind_info.get_parameter_value(0) };
let raw: duckdb_value = val.into_raw();  // takes ownership, no auto-destroy
// ... use raw handle ...
// caller must call duckdb_destroy_value manually
}

DataChunk

Scan callbacks receive a duckdb_data_chunk for output. The DataChunk wrapper provides ergonomic access:

#![allow(unused)]
fn main() {
use quack_rs::data_chunk::DataChunk;

unsafe extern "C" fn my_scan(info: duckdb_function_info, output: duckdb_data_chunk) {
    let chunk = unsafe { DataChunk::from_raw(output) };

    // Get a writer for column 0
    let mut writer = unsafe { chunk.writer(0) };
    unsafe { writer.write_i64(0, 42) };

    // Set the output row count (0 = end of stream)
    unsafe { chunk.set_size(1) };
}
}

Methods

MethodDescription
size()Current row count
set_size(n)Set row count (0 signals end of stream)
column_count()Number of columns
vector(col)Raw duckdb_vector handle
writer(col)VectorWriter for a column
reader(col)VectorReader for a column

DataChunk is non-owning — it does not destroy the chunk on drop. DuckDB manages the chunk's lifetime.