duckdb

    MoonBit bindings for DuckDB on native and JavaScript targets (Node.js and browser WASM backends)

    duckdb
    database
    sql
    ffi
    wasm
    moonbit
    Download zip
    Author
    Version
    0.7.0
    License
    Apache-2.0
    Last updated
    9 hours ago
    Downloads
    1K

    Dependencies

    #f4ah6o/duckdb

    MoonBit bindings for DuckDB on native and JavaScript targets.

    Upgrading from 0.6.4? See MIGRATION.md for the 0.6.4 → 0.7.0 breaking changes (structured errors, 128-bit Decimal, typed Arrow vectors, the quack package move) with before/after examples.

    #Targets

    • Native: links against libduckdb via the DuckDB C API.
    • JavaScript: compile with the MoonBit JS target and pick a backend at runtime:
      • JsBackend::Node uses @duckdb/node-api.
      • JsBackend::Wasm uses @duckdb/duckdb-wasm in the browser.
    • MoonBit wasm/wasm-gc targets are not supported (they use stub implementations).

    #Feature Support Matrix

    The tables below are generated from the runtime BackendCapabilities table (src/duckdb_capabilities.mbt) — the same data conn.capabilities() exposes and capability-gated operations consult. Run scripts/support_matrix.sh after changing capabilities; CI regenerates the section and fails on drift.

    FeatureNativeJS (Node)JS (WASM)
    Connection & Query✅✅✅
    Prepared Statements✅✅✅
    Streaming Results✅✅✅
    Appender✅✅ (Node only)❌
    Appender DataChunk✅✅ (Node only)❌
    Arrow Integration✅✅✅
    Advanced Types⚠️⚠️⚠️

    Legend: ✅ Full support | ⚠️ Partial support | ❌ Not supported

    #Advanced Types Detailed Support

    TypeNative BindNative AppendNode BindNode AppendWASM Bind
    Decimal✅ 128-bit✅ 128-bit✅ 128-bit✅ 128-bit✅ direct string param + SQL cast
    Interval✅✅✅✅✅ direct string param + SQL cast
    Blob✅✅✅✅❌ unsupported
    List✅ VARCHAR✅ VARCHAR✅ VARCHAR (Node only)❌❌ unsupported
    Struct✅ VARCHAR✅ VARCHAR✅ VARCHAR (Node only)❌❌ unsupported
    Map✅ VARCHAR✅ VARCHAR✅ VARCHAR (Node only)❌❌ unsupported

    Notes:
    • Decimal carries the full DuckDB 128-bit scaled integer as lower : UInt64 and upper : Int64 halves (two's complement). decimal_from_hugeint builds a Decimal from halves, decimal_from_parts/decimal_to_parts convert to/from whole+fractional parts (decimal_to_parts returns BigInt values so whole parts beyond 64 bits are preserved, with a signed fractional remainder so negative sub-unit values like -0.50 round-trip), and decimal_to_double may lose precision above 2^53, matching DuckDB semantics.
    • List/Struct/Map are represented as string arrays (VARCHAR-only) and rely on DuckDB casting.
    • JS (WASM) uses direct duckdb-wasm prepared parameters only. Verified advanced prepared-statement bind support is limited to Decimal and Interval string parameters with an explicit SQL cast, for example ?::DECIMAL(10,2) or ?::INTERVAL.
    • JS (WASM) Blob, List, Struct, and Map direct prepared parameters are explicitly unsupported: the browser smoke test fails for them against @duckdb/duckdb-wasm 1.33.1-dev18.0 through 1.33.1-dev65.0.
    • Appender date/timestamp helpers are only implemented for native targets.

    #Arrow Integration

    Basic support is available on all targets:
    • Arrow query result type
    • Schema extraction
    • Columnar access via canonical typed vectors (ArrowResult::to_chunks + Vector accessors); the legacy get_column_* getters still work as deprecated forwarders — see MIGRATION.md
    • Supported types: BOOLEAN, INTEGER, VARCHAR, DOUBLE, BIGINT (plus wider ColumnType coverage through the typed vectors)

    Note: Nested columns (List, Struct, Map) decode logically on every backend — Vector::value_at(row) yields Value::List/Struct/Map, and the typed accessors (list_parts/struct_parts/map_parts) work because real VectorData::List/Struct/Map is produced. A tagged-JSON Any cell fallback remains only for genuinely unsupported types (UNION/BIT/TIME_TZ/BIGNUM).

    #CI Coverage

    GitHub Actions exercises the full public support matrix on every PR. DuckDB versions under test are pinned and printed in the job logs so failures can be attributed to a backend/version combination.

    BackendCommandRunnerDuckDB under test
    Nativemoon check --target native + moon test --target nativemacos-latest, ubuntu-latestlibduckdb 1.4.5, 1.5.6
    JS (Node)moon check --target js + moon test --target jsubuntu-latest, Node 24@duckdb/node-api 1.4.3-r.3 (minimum), 1.5.6-r.1 (pinned)
    JS (WASM)pnpm test:wasm-browser (Playwright Chromium)ubuntu-latest@duckdb/duckdb-wasm 1.33.1-dev18.0 (minimum), 1.33.1-dev65.0 (pinned)

    #Updating DuckDB dependencies

    All DuckDB versions — libduckdb, @duckdb/node-api, and @duckdb/duckdb-wasm — are pinned and bumped deliberately; CI never tests latest.

    • Verification path. A version bump lands as a PR that updates the pin in package.json (JS) or ci.yml (native) and adds the new version to the CI matrix — the matrix install step is what actually exercises a version. The oldest matrix entry stays as the minimum supported version until intentionally dropped; the newest entry matches the package.json pin. The duckdb-pins CI job (scripts/check_duckdb_pins.mjs, runnable locally via pnpm check:dep-pins) enforces this: it fails when a package.json pin does not equal the newest matrix entry, so a bump that forgets the matrix update cannot look green while the pinned version goes untested.
    • Automated bumps. Dependabot opens grouped weekly PRs for @duckdb/* npm packages and GitHub Actions. A Dependabot PR is merged only after the new version is added to the ci.yml matrix (the duckdb-pins job fails otherwise) and the full backend suite is green: moon test --target js + pnpm test:wasm-browser for JS bumps, moon test --target native for native bumps.
    • Advanced-type re-check. The browser smoke check exercises direct prepared parameters for Decimal, Interval, Blob, List, Struct, and Map. If a bump flips any of them between supported and unsupported, update the BackendCapabilities table in src/duckdb_capabilities.mbt, regenerate the Advanced Types table with scripts/support_matrix.sh, and update the expected-unsupported set in scripts/wasm_browser_smoke.mjs in the same PR.
    • A DuckDB 2.0 lane will be added once a 2.0 libduckdb build is published on the DuckDB releases page.

    #Installation

    #Native Target

    The native target links against libduckdb using the DuckDB C API.

    #Install libduckdb

    macOS (Homebrew):
    brew install duckdb

    Ubuntu/Debian:
    # Download a release that matches your platform wget https://github.com/duckdb/duckdb/releases/download/<version>/libduckdb-linux-amd64.zip unzip libduckdb-linux-amd64.zip sudo cp libduckdb.so /usr/local/lib/ sudo ldconfig

    From Source:
    git clone https://github.com/duckdb/duckdb.git cd duckdb mkdir build && cd build cmake .. make -j$(nproc) sudo make install sudo ldconfig

    #Linker Configuration

    When compiling, you may need to specify the library path:

    moon build --target-native -- -L/usr/local/lib -Wl,-rpath,/usr/local/lib -lduckdb

    Or set PKG_CONFIG_PATH if libduckdb provides a pkg-config file. The default src/moon.pkg includes common include/library search paths for both macOS and Ubuntu:

    • Include: /opt/homebrew/include, /usr/local/include, /usr/include
    • Library: /opt/homebrew/lib, /usr/local/lib, /usr/lib

    The linker still requires -lduckdb, so libduckdb must be installed on the machine.

    If you hit errors like Undefined symbols ... _duckdb_*, check:

    1. duckdb.h exists in one of the include paths above.
    2. libduckdb.dylib (macOS) or libduckdb.so (Linux) exists in one of the library paths above.
    3. moon test --target native runs in an environment where those paths are visible to the linker.

    #JavaScript Targets

    #Node.js

    The Node.js backend uses @duckdb/node-api. Install JavaScript dependencies with pnpm:

    pnpm install

    #Browser (WASM)

    The browser backend uses @duckdb/duckdb-wasm and requires browser Worker support. The package is installed by the same pnpm setup:

    pnpm install

    Run the browser smoke check to verify the local setup:

    pnpm test:wasm-browser

    If Chromium is not installed for Playwright yet, install it once:

    pnpm test:wasm-browser:install

    The smoke check serves the local @duckdb/duckdb-wasm bundle over HTTP, verifies Worker support, and exercises direct duckdb-wasm prepared parameters for Decimal, Blob, Interval, List, Struct, and Map. Decimal and Interval are expected to pass; Blob, List, Struct, and Map are expected to remain unsupported. Cross-origin isolation may be required for future pthread/SharedArrayBuffer paths, but the current smoke check uses the MVP worker bundle.

    #JavaScript Limitations

    • WASM Appender - Not supported for WASM backend (use INSERT statements instead)
    • WASM Advanced Types - Decimal and Interval prepared-statement binds are supported as direct string parameters with explicit SQL casts; Blob, List, Struct, and Map direct prepared parameters are unsupported
    • Node.js Advanced Types - Decimal, Interval, Blob are supported for bind/append; List/Struct/Map are VARCHAR-only
    • JS Appender Date/Timestamp - Not implemented (native only)

    #Quack Remote Protocol

    Quack helpers live in the f4ah6o/duckdb/quack package and are thin SQL wrappers built on the generic extension primitives (Connection::install_extension / load_extension / attach / query). They use DuckDB's quack extension instead of implementing the low-level application/duckdb wire format in MoonBit.

    Quack is experimental in DuckDB 1.5.x and is distributed from DuckDB's core_nightly extension repository. Function names, defaults, and protocol details may change before DuckDB 2.0.

    The older Connection::install_quack / load_quack / start_quack_server / stop_quack_server / quack_query / create_quack_secret / attach_quack methods remain as deprecated facades during the transition — new code should import the quack package instead.

    connect(on_ready=fn(result) {
    match result {
    Ok(conn) => {
    @quack.install(conn, on_done=fn(_) { () })
    @quack.load(conn, on_done=fn(_) { () })
    @quack.serve(
    conn,
    "quack:localhost",
    token="super_secret",
    on_done=fn(started) {
    match started {
    Ok(info) => println("quack server: \{info.rows}")
    Err(err) => println("quack serve failed: \{err}")
    }
    },
    )
    }
    Err(err) => println("connect failed: \{err}")
    }
    })

    Client helpers cover scoped secrets, stateless remote queries, and attached remote catalogs:

    @quack.create_secret(
    conn,
    "super_secret",
    scope="quack:localhost",
    on_done=fn(_) { () },
    )

    @quack.query(
    conn,
    "quack:localhost",
    "SELECT 42 AS answer",
    token="super_secret",
    on_done=fn(result) {
    match result {
    Ok(rows) => println("\{rows.rows}")
    Err(err) => println("quack query failed: \{err}")
    }
    },
    )

    @quack.attach(
    conn,
    "quack:localhost",
    "remote_db",
    token="super_secret",
    on_done=fn(_) { () },
    )

    For non-local endpoints, DuckDB's Quack client assumes HTTPS by default. Use disable_ssl=true only when a remote endpoint is intentionally served over plain HTTP, and prefer a TLS-terminating reverse proxy for exposed services.

    #Usage

    connect(on_ready=fn (result) {
    match result {
    Ok(conn) => {
    conn.query(
    "select 1 as a, NULL as b, 'duck' as c",
    on_done=fn (query_result) {
    match query_result {
    Ok(result) => {
    println("columns: \{result.columns}")
    println("rows: \{result.rows}")
    println("nulls: \{result.nulls}")
    }
    Err(err) => println("query failed: \{err}")
    }
    },
    )
    conn.close(on_done=fn (closed) {
    match closed {
    Ok(_) => ()
    Err(err) => println("close failed: \{err}")
    }
    })
    }
    Err(err) => println("connect failed: \{err}")
    }
    })

    #Typed Results

    QueryResult stores rows as strings plus a null mask. Use the typed helpers for convenience, or convert to a TypedQueryResult for repeated access:

    conn.query("select 1 as a, 2.5 as b, NULL as c", on_done=fn (query_result) {
    match query_result {
    Ok(result) => {
    let value = result.get_int(0, 0) // Some(1)
    let typed = result.to_typed()
    let b0 = typed.get_double(0, 1)
    let c0 = typed.get_string(0, 2) // None
    println("\{value} \{b0} \{c0}")
    }
    Err(err) => println("query failed: \{err}")
    }
    })

    #Streaming Results

    Use query_stream to process large datasets in chunks without materializing the full result in MoonBit memory:

    #Basic Streaming (Count Rows)

    connect(on_ready=fn (result) {
    match result {
    Ok(conn) => {
    conn.query_stream(
    "SELECT i FROM RANGE(1000000) tbl(i)",
    on_done=fn (stream_result) {
    match stream_result {
    Ok(stream) => {
    let mut total = 0
    let done = Ref::new(false)
    while !done.val {
    stream.next(on_done=fn (chunk_result) {
    match chunk_result {
    Ok(Some(chunk)) => total = total + chunk.row_count()
    Ok(None) => done.val = true
    Err(err) => {
    done.val = true
    println("stream failed: \{err}")
    }
    }
    })
    }
    stream.close(on_done=fn (_) { () })
    println("rows: \{total}")
    }
    Err(err) => println("stream failed: \{err}")
    }
    },
    )
    }
    Err(err) => println("connect failed: \{err}")
    }
    })

    #Aggregation Example

    For more advanced use cases, you can aggregate data while streaming:

    // Aggregate state to track running totals
    pub struct Aggregates {
    mut total_rows : Int
    mut sum_values : Int
    mut min_value : Int?
    mut max_value : Int?
    }

    let agg_ref = Ref::new({ total_rows: 0, sum_values: 0, min_value: None, max_value: None })
    let done_ref = Ref::new(false)

    conn.query_stream(
    "SELECT value FROM measurements",
    on_done=fn (stream_result) {
    match stream_result {
    Ok(stream) => {
    while !done_ref.val {
    stream.next(on_done=fn (chunk_result) {
    match chunk_result {
    Ok(Some(chunk)) => {
    // Process each row in the chunk
    for row = 0; row < chunk.row_count(); row = row + 1 {
    match chunk.cell(row, 0) {
    Some(v) => {
    let value = parse_int(v)
    agg_ref.val.total_rows = agg_ref.val.total_rows + 1
    agg_ref.val.sum_values = agg_ref.val.sum_values + value
    // Update min/max...
    }
    None => ()
    }
    }
    }
    Ok(None) => done_ref.val = true
    Err(err) => { done_ref.val = true; println("error: \{err}") }
    }
    })
    }
    stream.close(on_done=fn (_) {
    println("Total: \{agg_ref.val.total_rows}")
    println("Sum: \{agg_ref.val.sum_values}")
    })
    }
    Err(err) => println("stream failed: \{err}")
    }
    },
    )

    #Streaming Limitations

    • Streamed DataChunk values are strings plus a null mask, consistent with QueryResult.
    • Always call ResultStream::close when finished to release resources.

    #JS Backend Selection

    Use JsBackend::Auto (default), JsBackend::Node, or JsBackend::Wasm:

    connect(
    on_ready=fn (result) { /* ... */ },
    backend=JsBackend::Wasm,
    )

    • Auto - Detects environment (Node.js uses Node, browser uses WASM)
    • Node - Forces @duckdb/node-api
    • Wasm - Forces @duckdb/duckdb-wasm

    #Backend Capabilities

    Every handle reports which backend it runs on and what that backend supports. conn.capabilities() returns a BackendCapabilities struct (the same table that generates the Feature Support Matrix above); conn.backend() reports the resolved Backend (Native, Node, Wasm, or Unsupported).

    connect(on_ready=fn (result) {
    match result {
    Ok(conn) => {
    let caps = conn.capabilities()
    if caps.appender {
    // safe to call conn.create_appender(...)
    }
    match caps.require(BackendFeature::BlobBind) {
    Ok(_) => () // bind_blob is available
    Err(err) => println("gated: \{err.message()}")
    }
    }
    Err(err) => println("connect failed: \{err}")
    }
    })

    Operations that a backend cannot provide fail with the structured DuckDBError::Unsupported(feature~, backend~) error, so callers can match on feature/backend instead of parsing message text. Capability-gated checks run before the FFI layer, so e.g. create_appender on a WASM connection fails immediately with Unsupported(feature="appender", backend="wasm").

    #Configuration

    Configuration is only available on native/JS targets (not wasm/wasm-gc). Create a config, set options, then connect with it:

    let config = Config::create()
    match config.set("memory_limit", "1GB") {
    Ok(_) => ()
    Err(err) => println("config set failed: \{err}")
    }

    connect_with_config(
    on_ready=fn (result) { /* ... */ },
    config=Some(config),
    path=":memory:",
    )

    #Error Handling

    All bind_* methods and Config::set return Result[Unit, DuckDBError] on both native and JS targets:
    • Success: Returns Ok(())
    • Failure: Returns Err(err) where err is a structured DuckDBError variant (Query, Prepare, Bind, Append, Unsupported, Closed, InvalidArgument, Backend, DuckDB); err.message() returns the diagnostic text and err.error_type() the engine classification when one exists. See MIGRATION.md for the 0.6.4 Message migration.

    On JS targets, bind operations are synchronous and errors are properly propagated. Use pattern matching to handle errors:

    match stmt.bind_int(1, 42) {
    Ok(_) =>
    match stmt.bind_varchar(2, "hello") {
    Ok(_) => ()
    Err(e) => println("bind failed: \{e}")
    }
    Err(e) => println("bind failed: \{e}")
    }

    DuckDBError

    pub suberror DuckDBError {
    Query(String)
    Prepare(String)
    Bind(String)
    Append(String)
    Unsupported(feature~ : String, backend~ : String, message~ : String)
    Closed(resource~ : String)
    InvalidArgument(argument~ : String, reason~ : String)
    Backend(backend~ : String, message~ : String)
    DuckDB(error_type~ : String, message~ : String)
    }

    Structured error raised by the duckdb bindings.

    Match on the variants to branch on the error category without parsing strings. Every variant keeps the original diagnostic text available via DuckDBError::message.

    Categories:
    • Query / Prepare / Bind / Append: the binding detected a failure at that stage and the DuckDB engine did not supply a classified error type (allocation failures, invalid handles, host runtime errors, ...).
    • DuckDB: a failure reported by the DuckDB engine itself; error_type is the engine's own classification normalized to snake_case ("parser", "catalog", "invalid_input", ...) or "unknown".
    • Backend: a backend/runtime failure outside the query path (connect, close, configuration, host exceptions).
    • Unsupported: the feature exists but this backend cannot provide it. backend names the backend ("native", "node", "wasm", "unsupported"); message is the backend's original diagnostic verbatim when it reported one, otherwise a synthesized description; designed to compose with the BackendCapabilities work in issue #65.
    • Closed: the operation was attempted on a closed handle.
    • InvalidArgument: a caller-supplied argument failed validation.

    DuckDBError::error_type

    fn DuckDBError::error_type(self : DuckDBError) -> String?

    The DuckDB engine's error classification when one is available (the DuckDB variant), e.g. "parser", "catalog", "invalid_input" or "unknown". Returns None for binding-side categories.

    DuckDBError::message

    fn DuckDBError::message(self : DuckDBError) -> String

    The diagnostic message carried by this error.

    For errors reported by DuckDB or a backend this is the original text verbatim (an Unsupported classified from a backend diagnostic carries that diagnostic); for the remaining binding-side categories (Closed, InvalidArgument, and Unsupported without an upstream diagnostic) it is synthesized from the structured fields.

    Migration note: this replaces matching on the removed DuckDBError::Message(String) variant.

    DuckDBError::to_message

    #deprecated("DuckDBError::Message was replaced by structured variants in 0.7.0; use err.message() for the diagnostic text or match on the Query/Prepare/Bind/Append/Unsupported/Closed/InvalidArgument/Backend/DuckDB variants")
    fn DuckDBError::to_message(self : DuckDBError) -> String

    Extract the diagnostic text of an error, as matching on the removed DuckDBError::Message(String) variant used to.

    Prefer matching on the structured DuckDBError variants for categories and calling DuckDBError::message for the text.

    Appender

    pub type Appender

    Appender::append_bigint

    fn Appender::append_bigint(self : Appender, value : Int) -> Result[Unit, DuckDBError]

    Appender::append_blob

    fn Appender::append_blob(self : Appender, value : Bytes) -> Result[Unit, DuckDBError]

    Appender::append_bool

    fn Appender::append_bool(self : Appender, value : Bool) -> Result[Unit, DuckDBError]

    Appender::append_chunk

    fn Appender::append_chunk(self : Appender, chunk : VectorChunk) -> Result[Unit, DuckDBError]

    Bulk-append a canonical VectorChunk in one shot: each column's payload array is copied straight into a DuckDB data chunk and the whole chunk is appended with duckdb_append_data_chunk — no per-cell FFI calls. Inputs larger than the vector capacity (duckdb_vector_size(), normally 2048 rows) are appended as consecutive capacity-sized slices.

    Supported payloads mirror the streamed-reader set: BOOLEAN, all integer widths, DATE, TIME/TIME_NS, every TIMESTAMP variant, FLOAT/DOUBLE, VARCHAR, BLOB, DECIMAL (uniform width/scale per column), INTERVAL, and HUGEINT/UHUGEINT/UUID. LIST/STRUCT/MAP/ARRAY and Any-vector columns return Unsupported — the row-wise Appender API still covers VARCHAR-element nested columns.

    The chunk is validated up front: an empty chunk, mismatched column names/vector counts, ragged columns, or a logical type that disagrees with its payload produce InvalidArgument before any data is written.

    Appender::append_date

    fn Appender::append_date(self : Appender, days : Int) -> Result[Unit, DuckDBError]

    Appender::append_decimal

    fn Appender::append_decimal(self : Appender, value : Decimal) -> Result[Unit, DuckDBError]

    Appender::append_double

    fn Appender::append_double(self : Appender, value : Double) -> Result[Unit, DuckDBError]

    Appender::append_int

    fn Appender::append_int(self : Appender, value : Int) -> Result[Unit, DuckDBError]

    Appender::append_interval

    fn Appender::append_interval(self : Appender, value : Interval) -> Result[Unit, DuckDBError]

    Appender::append_list_varchar

    fn Appender::append_list_varchar(self : Appender, values : Array[String]) -> Result[Unit, DuckDBError]

    Append a list of string values to the appender.

    Appender::append_list_varchar_value

    fn Appender::append_list_varchar_value(self : Appender, values : Array[String]) -> Result[Unit, DuckDBError]

    Append a list literal as VARCHAR. This serializes the list and relies on DuckDB casting when appropriate.

    Appender::append_map

    fn Appender::append_map(self : Appender, map : Map) -> Result[Unit, DuckDBError]

    Append a map value to the appender.

    Appender::append_null

    fn Appender::append_null(self : Appender) -> Result[Unit, DuckDBError]

    Appender::append_struct

    fn Appender::append_struct(self : Appender, value : Struct) -> Result[Unit, DuckDBError]

    Append a struct value to the appender.

    Appender::append_timestamp

    fn Appender::append_timestamp(self : Appender, micros : Int64) -> Result[Unit, DuckDBError]

    Appender::append_varchar

    fn Appender::append_varchar(self : Appender, value : String) -> Result[Unit, DuckDBError]

    Appender::backend

    fn Appender::backend(self : Appender) -> Backend

    The backend this appender writes to.

    Appender::begin_row

    fn Appender::begin_row(self : Appender) -> Result[Unit, DuckDBError]

    Appender::capabilities

    fn Appender::capabilities(self : Appender) -> BackendCapabilities

    The capabilities of the backend self appends to.

    Appender::close

    fn Appender::close(self : Appender, on_done~ : (Result[Unit, DuckDBError]) -> Unit) -> Unit

    Appender::end_row

    fn Appender::end_row(self : Appender) -> Result[Unit, DuckDBError]

    Appender::flush

    fn Appender::flush(self : Appender) -> Result[Unit, DuckDBError]

    ArrowField

    pub struct ArrowField {
    name : String
    nullable : Bool
    type_id : String
    }

    One Arrow field of a result schema.

    ArrowHandle

    type ArrowHandle

    Owned duckdb_result retained by an ArrowResult until close.

    ArrowResult

    pub struct ArrowResult {
    // private fields
    }

    Materialized query_arrow result. Opaque to callers; the canonical VectorChunks are exposed via to_chunks, and the backend-native handle is released by close.

    ArrowResult::close

    fn ArrowResult::close(self : ArrowResult, on_done~ : (Result[Unit, DuckDBError]) -> Unit) -> Unit

    Release the backend-native result. Idempotent contract: the first call frees the handle; calling close again reports Closed instead of touching released resources.

    ArrowResult::column_count

    fn ArrowResult::column_count(self : ArrowResult) -> Int

    Number of result columns.

    ArrowResult::get_column_bool

    #deprecated("arrow getters now read canonical typed vectors; use ArrowResult::to_chunks with Vector::bools/value_at for new code")
    fn ArrowResult::get_column_bool(self : ArrowResult, col : Int) -> Array[Bool]

    Column col flattened to Bool values across all chunks (NULL rows yield false).

    ArrowResult::get_column_bool_nullable

    #deprecated("arrow getters now read canonical typed vectors; use ArrowResult::to_chunks with Vector::bools and Vector::is_null for new code")
    fn ArrowResult::get_column_bool_nullable(self : ArrowResult, col : Int) -> (Array[Bool], Array[Bool])

    Column col flattened to (values, validity) across all chunks.

    ArrowResult::get_column_double

    #deprecated("arrow getters now read canonical typed vectors; use ArrowResult::to_chunks with Vector::doubles/value_at for new code")
    fn ArrowResult::get_column_double(self : ArrowResult, col : Int) -> Array[Double]

    Column col flattened to Double values across all chunks (NULL rows yield 0.0).

    ArrowResult::get_column_double_nullable

    #deprecated("arrow getters now read canonical typed vectors; use ArrowResult::to_chunks with Vector::doubles and Vector::is_null for new code")
    fn ArrowResult::get_column_double_nullable(self : ArrowResult, col : Int) -> (Array[Double], Array[Bool])

    Column col flattened to (values, validity) across all chunks.

    ArrowResult::get_column_int32

    #deprecated("arrow getters now read canonical typed vectors; use ArrowResult::to_chunks with Vector::ints/value_at for new code")
    fn ArrowResult::get_column_int32(self : ArrowResult, col : Int) -> Array[Int]

    Column col flattened to Int values across all chunks (NULL rows yield 0).

    ArrowResult::get_column_int32_nullable

    #deprecated("arrow getters now read canonical typed vectors; use ArrowResult::to_chunks with Vector::ints and Vector::is_null for new code")
    fn ArrowResult::get_column_int32_nullable(self : ArrowResult, col : Int) -> (Array[Int], Array[Bool])

    Column col flattened to (values, validity) across all chunks.

    ArrowResult::get_column_int64

    #deprecated("arrow getters now read canonical typed vectors; use ArrowResult::to_chunks with Vector::int64s/value_at for new code")
    fn ArrowResult::get_column_int64(self : ArrowResult, col : Int) -> Array[Int64]

    Column col flattened to Int64 values across all chunks (NULL rows yield 0). Note: this getter previously returned Array[Int], which truncated BIGINT to 32 bits; it now keeps full Int64 precision.

    ArrowResult::get_column_int64_nullable

    #deprecated("arrow getters now read canonical typed vectors; use ArrowResult::to_chunks with Vector::int64s and Vector::is_null for new code")
    fn ArrowResult::get_column_int64_nullable(self : ArrowResult, col : Int) -> (Array[Int64], Array[Bool])

    Column col flattened to (values, validity) across all chunks. See get_column_int64 for the Int -> Int64 precision fix.

    ArrowResult::get_column_string

    #deprecated("arrow getters now read canonical typed vectors; use ArrowResult::to_chunks with Vector::strings/string_at for new code")
    fn ArrowResult::get_column_string(self : ArrowResult, col : Int) -> Array[String]

    Column col flattened to rendered strings across all chunks (NULL rows yield "").

    ArrowResult::get_column_string_nullable

    #deprecated("arrow getters now read canonical typed vectors; use ArrowResult::to_chunks with Vector::strings and Vector::is_null for new code")
    fn ArrowResult::get_column_string_nullable(self : ArrowResult, col : Int) -> (Array[String], Array[Bool])

    Column col flattened to (values, validity) across all chunks.

    ArrowResult::get_schema

    fn ArrowResult::get_schema(self : ArrowResult) -> Result[ArrowSchemaInfo, DuckDBError]

    Result schema: column names, nullability, and a flat type id per field. type_id keeps the legacy primitive names ("bool"/"int32"/"int64"/ "double"/"string") and covers the wider ColumnType set ("uint32", "uint64", "decimal", "date", "timestamp", "interval", "hugeint", "uuid", "list", "struct", "map", ...). Err(Closed) once closed.

    ArrowResult::row_count

    fn ArrowResult::row_count(self : ArrowResult) -> Int

    Total rows across all materialized chunks.

    ArrowResult::to_chunks

    fn ArrowResult::to_chunks(self : ArrowResult) -> Result[Array[VectorChunk], DuckDBError]

    The canonical typed-vector representation of the whole result.

    Err(Closed) once close has released the result.

    ArrowSchemaInfo

    pub struct ArrowSchemaInfo {
    fields : Array[ArrowField]
    }

    Result schema: one ArrowField per column, in column order.

    Backend

    pub(all) enum Backend {
    Native
    Node
    Wasm
    Unsupported
    } derive(Compare, Eq, Show)

    A concrete backend a Connection runs on. Unlike JsBackend this has no Auto selector — every variant names a real engine.

    Backend::all

    fn Backend::all() -> Array[Backend]

    All concrete backends, in display order.

    Backend::capabilities

    fn Backend::capabilities(self : Backend) -> BackendCapabilities

    The capabilities this backend provides.

    Backend::name

    fn Backend::name(self : Backend) -> String

    Short stable name used in DuckDBError::Unsupported(backend~) and generated documentation.

    BackendCapabilities

    pub struct BackendCapabilities {
    backend : Backend
    connect : Bool
    prepared_statements : Bool
    streaming : Bool
    appender : Bool
    appender_chunk : Bool
    appender_date_timestamp : Bool
    arrow : Bool
    persistent_storage : Bool
    quack : Bool
    decimal_bind : Bool
    decimal_append : Bool
    interval_bind : Bool
    interval_append : Bool
    blob_bind : Bool
    blob_append : Bool
    nested_bind : Bool
    nested_append : Bool
    nested_typed : Bool
    }

    What a backend supports. The bool fields map 1:1 to BackendFeature variants via supports; the struct is the single source of truth for the README feature matrix and for structured Unsupported errors.

    BackendCapabilities::require

    fn BackendCapabilities::require(self : BackendCapabilities, feature : BackendFeature) -> Result[Unit, DuckDBError]

    Ok(()) when this backend supports feature, otherwise the structured Unsupported error — used to gate operations before hitting the FFI layer.

    BackendCapabilities::supports

    fn BackendCapabilities::supports(self : BackendCapabilities, feature : BackendFeature) -> Bool

    Whether this backend supports feature.

    BackendCapabilities::unsupported

    fn BackendCapabilities::unsupported(self : BackendCapabilities, feature : String) -> DuckDBError

    A structured Unsupported error for a named operation on this backend, for sites that carry a more specific feature string than BackendFeature variants (e.g. "appender append_date").

    BackendCapabilities::unsupported_error

    fn BackendCapabilities::unsupported_error(self : BackendCapabilities, feature : BackendFeature) -> DuckDBError

    A structured Unsupported error for feature on this backend.

    BackendFeature

    pub(all) enum BackendFeature {
    Connect
    PreparedStatements
    Streaming
    Appender
    AppenderChunk
    AppenderDateTimestamp
    Arrow
    PersistentStorage
    Quack
    DecimalBind
    DecimalAppend
    IntervalBind
    IntervalAppend
    BlobBind
    BlobAppend
    ListBind
    StructBind
    MapBind
    NestedAppend
    NestedTyped
    } derive(Compare, Eq, Show)

    A single capability flag, for feature checks that need a stable name (error reporting, conformance tests, documentation generation). List/Struct/Map prepared binds share the nested_bind capability but keep distinct feature variants so error messages stay specific.

    BackendFeature::all

    All capability features, in display order.

    BackendFeature::name

    fn BackendFeature::name(self : BackendFeature) -> String

    Human-readable feature name used in DuckDBError::Unsupported(feature~).

    CheckConfig

    type CheckConfig

    CheckConfig::default

    fn CheckConfig::default() -> CheckConfig

    CheckConfig::new

    fn CheckConfig::new(cases : Int, max_size : Int, seed : Int, max_shrinks : Int, discard_ratio? : Int) -> CheckConfig

    CheckConfig::seed

    fn CheckConfig::seed(self : CheckConfig) -> Int

    CheckResult

    type CheckResult

    CheckResult::passed

    fn CheckResult::passed(self : CheckResult) -> Bool

    CheckResult::stats

    fn CheckResult::stats(self : CheckResult) -> String?

    ColumnType

    pub(all) enum ColumnType {
    Invalid
    Boolean
    TinyInt
    SmallInt
    Integer
    BigInt
    UTinyInt
    USmallInt
    UInteger
    UBigInt
    Float
    Double
    Timestamp
    Date
    Time
    Interval
    HugeInt
    UHugeInt
    Varchar
    Blob
    Decimal
    TimestampS
    TimestampMs
    TimestampNs
    Enum
    List
    Struct
    Map
    Array
    Uuid
    Union
    Bit
    TimeTz
    TimestampTz
    Any
    Bignum
    SqlNull
    StringLiteral
    IntegerLiteral
    TimeNs
    Unknown(Int)
    } derive(Eq)

    DuckDB column type identifiers.

    ColumnType::equal

    fn ColumnType::equal(ColumnType, ColumnType) -> Bool

    ColumnType::not_equal

    fn ColumnType::not_equal(x : ColumnType, y : ColumnType) -> Bool

    Config

    pub type Config

    Config::create

    fn Config::create() -> Config

    Config::set

    fn Config::set(self : Config, key : String, value : String) -> Result[Unit, DuckDBError]

    Connection

    pub type Connection

    Connection::attach

    fn Connection::attach(self : Connection, uri : String, db_alias? : String, options? : String, on_done~ : (Result[Unit, DuckDBError]) -> Unit) -> Unit

    Attach a database file or endpoint to this connection (ATTACH 'uri').

    db_alias optionally names the attached catalog; it is validated as a plain identifier and rendered double-quoted. options is rendered verbatim inside the trailing parenthesized clause — the escape hatch for extension-specific attach options (for example TYPE quack, TOKEN 'secret'). Callers building options from user input must validate and escape values themselves.

    Connection::attach_quack

    #deprecated("moved to the f4ah6o/duckdb/quack package (quack::attach); or use Connection::attach with a 'TYPE quack' options clause")
    fn Connection::attach_quack(self : Connection, uri : String, db_alias : String, token? : String, disable_ssl? : Bool, on_done~ : (Result[Unit, DuckDBError]) -> Unit) -> Unit

    Attach a Quack endpoint as a remote DuckDB catalog.

    Migration: use quack::attach(conn, uri, alias, ...) from the f4ah6o/duckdb/quack package, or the generic conn.attach primitive.

    Connection::backend

    fn Connection::backend(self : Connection) -> Backend

    The backend this connection runs on.

    Connection::capabilities

    fn Connection::capabilities(self : Connection) -> BackendCapabilities

    The capabilities of the backend self is connected to. Runtime access to the matrix: conn.capabilities() answers "can I use feature X here?" without reaching for the docs.

    Connection::close

    fn Connection::close(self : Connection, on_done~ : (Result[Unit, DuckDBError]) -> Unit) -> Unit

    Connection::create_appender

    fn Connection::create_appender(self : Connection, schema : String, table : String, on_done~ : (Result[Appender, DuckDBError]) -> Unit) -> Unit

    Connection::create_quack_secret

    #deprecated("moved to the f4ah6o/duckdb/quack package (quack::create_secret)")
    fn Connection::create_quack_secret(self : Connection, token : String, scope? : String, on_done~ : (Result[Unit, DuckDBError]) -> Unit) -> Unit

    Create a scoped or unscoped Quack secret for client authentication.

    Migration: use quack::create_secret(conn, token, ...) from the f4ah6o/duckdb/quack package.

    Connection::install_extension

    fn Connection::install_extension(self : Connection, name : String, repository? : String, force? : Bool, on_done~ : (Result[Unit, DuckDBError]) -> Unit) -> Unit

    Install a DuckDB extension by name (INSTALL / FORCE INSTALL).

    repository optionally names the extension repository to install from (for example core_nightly); force re-installs even when the extension is already installed. Names are validated as plain identifiers — anything else returns InvalidArgument without touching the database.

    Connection::install_quack

    #deprecated("moved to the f4ah6o/duckdb/quack package (quack::install); or use Connection::install_extension(\"quack\", repository=\"core_nightly\", force=true)")
    fn Connection::install_quack(self : Connection, on_done~ : (Result[Unit, DuckDBError]) -> Unit) -> Unit

    Install the experimental Quack extension from DuckDB's core_nightly repository.

    Migration: use quack::install(conn, on_done=...) from the f4ah6o/duckdb/quack package, or the generic conn.install_extension("quack", repository="core_nightly", force=true).

    Connection::load_extension

    fn Connection::load_extension(self : Connection, name : String, on_done~ : (Result[Unit, DuckDBError]) -> Unit) -> Unit

    Load an installed DuckDB extension into this connection (LOAD name).

    Connection::load_quack

    #deprecated("moved to the f4ah6o/duckdb/quack package (quack::load); or use Connection::load_extension(\"quack\")")
    fn Connection::load_quack(self : Connection, on_done~ : (Result[Unit, DuckDBError]) -> Unit) -> Unit

    Load the Quack extension in the current DuckDB connection.

    Migration: use quack::load(conn, on_done=...) from the f4ah6o/duckdb/quack package, or conn.load_extension("quack").

    Connection::prepare

    fn Connection::prepare(self : Connection, sql : String, on_done~ : (Result[PreparedStatement, DuckDBError]) -> Unit) -> Unit

    Connection::quack_query

    #deprecated("moved to the f4ah6o/duckdb/quack package (quack::query)")
    fn Connection::quack_query(self : Connection, uri : String, sql : String, token? : String, disable_ssl? : Bool, on_done~ : (Result[QueryResult, DuckDBError]) -> Unit) -> Unit

    Run a stateless query against a Quack endpoint.

    Migration: use quack::query(conn, uri, sql, ...) from the f4ah6o/duckdb/quack package.

    Connection::query

    fn Connection::query(self : Connection, sql : String, on_done~ : (Result[QueryResult, DuckDBError]) -> Unit) -> Unit

    Connection::query_arrow

    fn Connection::query_arrow(self : Connection, sql : String, on_done~ : (Result[ArrowResult, DuckDBError]) -> Unit) -> Unit

    Run sql and materialize the result as an ArrowResult backed by canonical VectorChunks.

    Connection::query_chunks

    fn Connection::query_chunks(self : Connection, sql : String, on_done~ : (Result[Array[VectorChunk], DuckDBError]) -> Unit) -> Unit

    Run sql and return the result as canonical typed VectorChunks — columnar Vectors with owned typed arrays and per-row validity, no per-cell string conversion. Consumed via Vector::value_at, Vector::ints/int64s/doubles/..., VectorChunk::get_value, or materialized to the compat QueryResult via chunks_to_query_result.

    Connection::query_stream

    fn Connection::query_stream(self : Connection, sql : String, on_done~ : (Result[ResultStream, DuckDBError]) -> Unit) -> Unit

    Connection::start_quack_server

    #deprecated("moved to the f4ah6o/duckdb/quack package (quack::serve)")
    fn Connection::start_quack_server(self : Connection, uri : String, token? : String, allow_other_hostname? : Bool, on_done~ : (Result[QueryResult, DuckDBError]) -> Unit) -> Unit

    Start a Quack server for this connection.

    Migration: use quack::serve(conn, uri, ...) from the f4ah6o/duckdb/quack package.

    Connection::stop_quack_server

    #deprecated("moved to the f4ah6o/duckdb/quack package (quack::stop)")
    fn Connection::stop_quack_server(self : Connection, uri : String, on_done~ : (Result[Unit, DuckDBError]) -> Unit) -> Unit

    Stop a Quack server started for this connection.

    Migration: use quack::stop(conn, uri, ...) from the f4ah6o/duckdb/quack package.

    DataChunk

    pub struct DataChunk {
    columns : Array[String]
    rows : Array[Array[String]]
    nulls : Array[Array[Bool]]
    }

    Chunked query data with column metadata.

    DataChunk::cell

    fn DataChunk::cell(self : DataChunk, row : Int, col : Int) -> String?

    The string value at (row, col), or None when the cell is NULL or the indices are out of bounds (bounds-checked, never panics).

    DataChunk::column_count

    fn DataChunk::column_count(self : DataChunk) -> Int

    DataChunk::row_count

    fn DataChunk::row_count(self : DataChunk) -> Int

    Decimal

    pub struct Decimal {
    width : Int
    scale : Int
    lower : UInt64
    upper : Int64
    }

    Fixed-point decimal type for financial calculations. Carries the full DuckDB 128-bit decimal payload as two 64-bit halves: upper is the signed high 64 bits, lower the unsigned low 64 bits of the scaled two's-complement integer (value * 10^scale).

    FixtureCase

    pub struct FixtureCase {
    name : String
    sql : String
    columns : Array[String]
    rows : Array[Array[String]]
    nulls : Array[Array[Bool]]
    }

    Interval

    pub struct Interval {
    months : Int
    days : Int
    micros : Int64
    }

    Date/time interval type. Represents a span of time in months, days, and microseconds.

    JsBackend

    pub(all) enum JsBackend {
    Auto
    Node
    Wasm
    }

    JS backend selection for connect.

    List

    pub struct List {
    elements : Array[String]
    }

    List/array type. Elements are stored as strings and converted on demand.

    Map

    pub struct Map {
    keys : Array[String]
    values : Array[String]
    }

    Key-value pair map type. DuckDB maps are implemented as lists of key-value structs.

    NativeChunk

    type NativeChunk

    NativeDataChunk

    #external
    pub type NativeDataChunk

    NativeLogicalType

    #external
    pub type NativeLogicalType

    A borrowed DuckDB logical type handle (native backend only).

    NativeResult

    type NativeResult

    NativeVector

    #external
    pub type NativeVector

    A borrowed DuckDB vector handle (native backend only). Only valid while the chunk it was taken from is alive; the canonical, owned column representation is Vector.

    PreparedStatement

    pub type PreparedStatement

    PreparedStatement::backend

    The backend this statement was prepared on.

    PreparedStatement::bind_bigint

    fn PreparedStatement::bind_bigint(self : PreparedStatement, index : Int, value : Int) -> Result[Unit, DuckDBError]

    PreparedStatement::bind_blob

    fn PreparedStatement::bind_blob(self : PreparedStatement, index : Int, value : Bytes) -> Result[Unit, DuckDBError]

    PreparedStatement::bind_bool

    fn PreparedStatement::bind_bool(self : PreparedStatement, index : Int, value : Bool) -> Result[Unit, DuckDBError]

    PreparedStatement::bind_date

    fn PreparedStatement::bind_date(self : PreparedStatement, index : Int, days : Int) -> Result[Unit, DuckDBError]

    PreparedStatement::bind_decimal

    fn PreparedStatement::bind_decimal(self : PreparedStatement, index : Int, value : Decimal) -> Result[Unit, DuckDBError]

    PreparedStatement::bind_double

    fn PreparedStatement::bind_double(self : PreparedStatement, index : Int, value : Double) -> Result[Unit, DuckDBError]

    PreparedStatement::bind_int

    fn PreparedStatement::bind_int(self : PreparedStatement, index : Int, value : Int) -> Result[Unit, DuckDBError]

    PreparedStatement::bind_interval

    fn PreparedStatement::bind_interval(self : PreparedStatement, index : Int, value : Interval) -> Result[Unit, DuckDBError]

    PreparedStatement::bind_list_varchar

    fn PreparedStatement::bind_list_varchar(self : PreparedStatement, index : Int, values : Array[String]) -> Result[Unit, DuckDBError]

    PreparedStatement::bind_map

    fn PreparedStatement::bind_map(self : PreparedStatement, index : Int, map : Map) -> Result[Unit, DuckDBError]

    PreparedStatement::bind_null

    fn PreparedStatement::bind_null(self : PreparedStatement, index : Int) -> Result[Unit, DuckDBError]

    PreparedStatement::bind_struct

    fn PreparedStatement::bind_struct(self : PreparedStatement, index : Int, value : Struct) -> Result[Unit, DuckDBError]

    PreparedStatement::bind_timestamp

    fn PreparedStatement::bind_timestamp(self : PreparedStatement, index : Int, micros : Int64) -> Result[Unit, DuckDBError]

    PreparedStatement::bind_varchar

    fn PreparedStatement::bind_varchar(self : PreparedStatement, index : Int, value : String) -> Result[Unit, DuckDBError]

    PreparedStatement::capabilities

    The capabilities of the backend self was prepared on.

    PreparedStatement::clear_bindings

    fn PreparedStatement::clear_bindings(self : PreparedStatement) -> Result[Unit, DuckDBError]

    PreparedStatement::close

    fn PreparedStatement::close(self : PreparedStatement, on_done~ : (Result[Unit, DuckDBError]) -> Unit) -> Unit

    PreparedStatement::execute

    fn PreparedStatement::execute(self : PreparedStatement, on_done~ : (Result[QueryResult, DuckDBError]) -> Unit) -> Unit

    PreparedStatement::execute_chunks

    fn PreparedStatement::execute_chunks(self : PreparedStatement, on_done~ : (Result[Array[VectorChunk], DuckDBError]) -> Unit) -> Unit

    Execute the prepared statement and return canonical typed VectorChunks.

    PreparedStatement::execute_stream

    fn PreparedStatement::execute_stream(self : PreparedStatement, on_done~ : (Result[ResultStream, DuckDBError]) -> Unit) -> Unit

    QueryResult

    pub struct QueryResult {
    columns : Array[String]
    column_types : Array[ColumnType]
    rows : Array[Array[String]]
    nulls : Array[Array[Bool]]
    }

    Query result data is represented as strings plus a null mask.

    QueryResult::cell

    fn QueryResult::cell(self : QueryResult, row : Int, col : Int) -> String?

    The string value at (row, col), or None when the cell is NULL or the indices are out of bounds (bounds-checked, never panics).

    QueryResult::column_count

    fn QueryResult::column_count(self : QueryResult) -> Int

    QueryResult::get_blob

    fn QueryResult::get_blob(self : QueryResult, row : Int, col : Int) -> Bytes?

    Get the blob value at the specified row and column. Returns None if the value is null or not a blob.

    QueryResult::get_bool

    fn QueryResult::get_bool(self : QueryResult, row : Int, col : Int) -> Bool?

    Get the boolean value at the specified row and column. Returns None if the value is null or not a boolean.

    QueryResult::get_date

    fn QueryResult::get_date(self : QueryResult, row : Int, col : Int) -> Int?

    Get the date value at the specified row and column. Returns None if the value is null or not a date.

    QueryResult::get_decimal

    fn QueryResult::get_decimal(self : QueryResult, row : Int, col : Int) -> Decimal?

    Get the decimal value at the specified row and column. Returns None if the value is null or not a decimal.

    QueryResult::get_double

    fn QueryResult::get_double(self : QueryResult, row : Int, col : Int) -> Double?

    Get the double value at the specified row and column. Returns None if the value is null or not a double.

    QueryResult::get_int

    fn QueryResult::get_int(self : QueryResult, row : Int, col : Int) -> Int?

    Get the integer value at the specified row and column. Returns None if the value is null or not an integer.

    QueryResult::get_string

    fn QueryResult::get_string(self : QueryResult, row : Int, col : Int) -> String?

    Get the string value at the specified row and column. Returns None if the value is null.

    QueryResult::get_timestamp

    fn QueryResult::get_timestamp(self : QueryResult, row : Int, col : Int) -> Int64?

    Get the timestamp value at the specified row and column. Returns None if the value is null or not a timestamp.

    QueryResult::get_value

    fn QueryResult::get_value(self : QueryResult, row : Int, col : Int) -> Value?

    Get the typed value at the specified row and column. Returns the Value directly without requiring to_typed() conversion.

    QueryResult::row_count

    fn QueryResult::row_count(self : QueryResult) -> Int

    QueryResult::to_typed

    fn QueryResult::to_typed(self : QueryResult) -> TypedQueryResult

    Convert a QueryResult to a TypedQueryResult by parsing string values.

    ResultStream

    pub type ResultStream

    ResultStream::close

    fn ResultStream::close(self : ResultStream, on_done~ : (Result[Unit, DuckDBError]) -> Unit) -> Unit

    ResultStream::column_count

    fn ResultStream::column_count(self : ResultStream) -> Int

    ResultStream::columns

    fn ResultStream::columns(self : ResultStream) -> Array[String]

    ResultStream::next

    fn ResultStream::next(self : ResultStream, on_done~ : (Result[DataChunk?, DuckDBError]) -> Unit) -> Unit

    ResultStream::next_chunk

    fn ResultStream::next_chunk(self : ResultStream, on_done~ : (Result[VectorChunk?, DuckDBError]) -> Unit) -> Unit

    Fetch the next chunk of the stream as a canonical typed VectorChunk, or None at end-of-stream. ResultStream::next builds its string DataChunk on top of this same path.

    Struct

    pub struct Struct {
    fields : Array[String]
    values : Array[String]
    }

    Composite type with named fields. Represents a DuckDB STRUCT type.

    TypedQueryResult

    pub struct TypedQueryResult {
    columns : Array[String]
    data : Array[Array[Value]]
    }

    Type-safe query result with typed value access. Stores data column-wise for efficient columnar access.

    TypedQueryResult::column_count

    fn TypedQueryResult::column_count(self : TypedQueryResult) -> Int

    Get the number of columns in the result.

    TypedQueryResult::get_blob

    fn TypedQueryResult::get_blob(self : TypedQueryResult, row : Int, col : Int) -> Bytes?

    Get a Blob value at the specified position.

    TypedQueryResult::get_bool

    fn TypedQueryResult::get_bool(self : TypedQueryResult, row : Int, col : Int) -> Bool?

    Get a Bool value at the specified position.

    TypedQueryResult::get_bool_column

    fn TypedQueryResult::get_bool_column(self : TypedQueryResult, col : Int) -> Array[Bool?]?

    Get an entire column as Option[Bool] values.

    TypedQueryResult::get_column

    fn TypedQueryResult::get_column(self : TypedQueryResult, col : Int) -> Array[Value]?

    Get an entire column as typed values.

    TypedQueryResult::get_date

    fn TypedQueryResult::get_date(self : TypedQueryResult, row : Int, col : Int) -> Int?

    Get a Date value (days since epoch) at the specified position.

    TypedQueryResult::get_date_column

    fn TypedQueryResult::get_date_column(self : TypedQueryResult, col : Int) -> Array[Int?]?

    Get an entire column as Option[Int] date values.

    TypedQueryResult::get_decimal

    fn TypedQueryResult::get_decimal(self : TypedQueryResult, row : Int, col : Int) -> Decimal?

    Get a Decimal value at the specified position.

    TypedQueryResult::get_decimal_column

    fn TypedQueryResult::get_decimal_column(self : TypedQueryResult, col : Int) -> Array[Decimal?]?

    Get an entire column as Option[Decimal] values.

    TypedQueryResult::get_double

    fn TypedQueryResult::get_double(self : TypedQueryResult, row : Int, col : Int) -> Double?

    Get a Double value at the specified position.

    TypedQueryResult::get_double_column

    fn TypedQueryResult::get_double_column(self : TypedQueryResult, col : Int) -> Array[Double?]?

    Get an entire column as Option[Double] values.

    TypedQueryResult::get_int

    fn TypedQueryResult::get_int(self : TypedQueryResult, row : Int, col : Int) -> Int?

    Get an Int value at the specified position.

    TypedQueryResult::get_int_column

    fn TypedQueryResult::get_int_column(self : TypedQueryResult, col : Int) -> Array[Int?]?

    Get an entire column as Option[Int] values.

    TypedQueryResult::get_string

    fn TypedQueryResult::get_string(self : TypedQueryResult, row : Int, col : Int) -> String?

    Get a String value at the specified position.

    TypedQueryResult::get_string_column

    fn TypedQueryResult::get_string_column(self : TypedQueryResult, col : Int) -> Array[String?]?

    Get an entire column as Option[String] values.

    TypedQueryResult::get_timestamp

    fn TypedQueryResult::get_timestamp(self : TypedQueryResult, row : Int, col : Int) -> Int64?

    Get a Timestamp value (microseconds since epoch) at the specified position.

    TypedQueryResult::get_timestamp_column

    fn TypedQueryResult::get_timestamp_column(self : TypedQueryResult, col : Int) -> Array[Int64?]?

    Get an entire column as Option[Int64] timestamp values.

    TypedQueryResult::get_value

    fn TypedQueryResult::get_value(self : TypedQueryResult, row : Int, col : Int) -> Value?

    Get a value at the specified row and column.

    TypedQueryResult::is_null

    fn TypedQueryResult::is_null(self : TypedQueryResult, row : Int, col : Int) -> Bool

    Check if the value at the specified position is NULL.

    TypedQueryResult::row_count

    fn TypedQueryResult::row_count(self : TypedQueryResult) -> Int

    Get the number of rows in the result.

    Value

    pub(all) enum Value {
    Int(Int)
    Int64(Int64)
    UInt64(UInt64)
    Double(Double)
    Bool(Bool)
    String(String)
    Date(Int)
    Timestamp(Int64)
    TimestampNs(Int64)
    Decimal(Decimal)
    Interval(Interval)
    HugeInt(lower~ : UInt64, upper~ : Int64)
    Blob(Bytes)
    List(Array[Value])
    Struct(fields~ : Array[String], values~ : Array[Value])
    Map(keys~ : Array[Value], values~ : Array[Value])
    Null
    }

    Typed value representing a single DuckDB cell.

    Value::as_blob

    fn Value::as_blob(self : Value) -> Bytes?

    Get the blob value if present, None otherwise.

    Value::as_bool

    fn Value::as_bool(self : Value) -> Bool?

    Get the boolean value if present, None otherwise.

    Value::as_date

    fn Value::as_date(self : Value) -> Int?

    Get the date value if present, None otherwise.

    Value::as_decimal

    fn Value::as_decimal(self : Value) -> Decimal?

    Get the decimal value if present, None otherwise.

    Value::as_double

    fn Value::as_double(self : Value) -> Double?

    Get the double value if present, None otherwise.

    Value::as_int

    fn Value::as_int(self : Value) -> Int?

    Get the integer value if present, None otherwise.

    Value::as_string

    fn Value::as_string(self : Value) -> String?

    Get the string value if present, None otherwise.

    Value::as_timestamp

    fn Value::as_timestamp(self : Value) -> Int64?

    Get the timestamp value if present, None otherwise.

    Value::as_timestamp_ns

    fn Value::as_timestamp_ns(self : Value) -> Int64?

    Get the nanosecond timestamp value if present, None otherwise.

    Value::is_null

    fn Value::is_null(self : Value) -> Bool

    Check if the value is null.

    Value::to_string

    fn Value::to_string(self : Value) -> String

    Convert a Value to its string representation.

    Vector

    pub(all) struct Vector {
    logical_type : ColumnType
    data : VectorData
    validity : FixedArray[Bool]
    }

    A canonical typed column vector: logical_type + columnar data + per-row validity (validity[i] == false means row i is NULL). validity.length() is the row count.

    Vector::all_valid

    fn Vector::all_valid(logical_type : ColumnType, data : VectorData) -> Vector

    Build a Vector from a payload with every row marked valid.

    Vector::any_cells

    fn Vector::any_cells(self : Vector) -> Array[Value]?

    Decoded per-cell values for an Any vector, or None.

    Vector::blobs

    fn Vector::blobs(self : Vector) -> Array[Bytes]?

    The Array[Bytes] payload for a Blob vector, or None.

    Vector::bools

    fn Vector::bools(self : Vector) -> FixedArray[Bool]?

    The raw FixedArray[Bool] payload for a Bool vector, or None.

    Vector::decimals

    fn Vector::decimals(self : Vector) -> FixedArray[Decimal]?

    The FixedArray[Decimal] payload for a Decimal vector, or None.

    Vector::doubles

    fn Vector::doubles(self : Vector) -> FixedArray[Double]?

    The raw FixedArray[Double] payload for a Double vector, or None. Float vectors are widened into a fresh FixedArray[Double] copy.

    Vector::int64s

    fn Vector::int64s(self : Vector) -> FixedArray[Int64]?

    The raw FixedArray[Int64] payload for an Int64 vector, or None.

    Vector::intervals

    fn Vector::intervals(self : Vector) -> FixedArray[Interval]?

    The FixedArray[Interval] payload for an Interval vector, or None.

    Vector::ints

    fn Vector::ints(self : Vector) -> FixedArray[Int]?

    The raw FixedArray[Int] payload for an Int32 vector, or None for other storage kinds. Combined with validity this exposes the column without per-cell boxing.

    Vector::is_null

    fn Vector::is_null(self : Vector, row : Int) -> Bool

    True when row row is NULL.

    Vector::len

    fn Vector::len(self : Vector) -> Int

    Number of rows in this vector.

    Vector::list_parts

    fn Vector::list_parts(self : Vector) -> (Vector, FixedArray[UInt64], FixedArray[UInt64])?

    The child vector and per-row offsets/lengths for a List vector.

    Vector::map_parts

    fn Vector::map_parts(self : Vector) -> (FixedArray[UInt64], FixedArray[UInt64], Vector, Vector)?

    Entry offsets/lengths plus key/value vectors for a Map vector.

    Vector::string_at

    fn Vector::string_at(self : Vector, row : Int) -> String

    Render row row of this vector as a DuckDB-style string (best-effort match to duckdb_value_varchar output). Used by the compat facade; NULL rows yield "".

    Vector::strings

    fn Vector::strings(self : Vector) -> Array[String]?

    The Array[String] payload for a Varchar vector, or None.

    Vector::struct_parts

    fn Vector::struct_parts(self : Vector) -> (Array[String], Array[Vector])?

    Field names and child vectors for a Struct vector.

    Vector::uint64s

    fn Vector::uint64s(self : Vector) -> FixedArray[UInt64]?

    The raw FixedArray[UInt64] payload for a UInt64 vector, or None.

    Vector::value_at

    fn Vector::value_at(self : Vector, row : Int) -> Value

    The typed Value for row row. Caller must ensure the row is valid (!is_null(row)); NULL rows return Value::Null.

    VectorChunk

    pub(all) struct VectorChunk {
    columns : Array[String]
    vectors : Array[Vector]
    }

    A canonical result chunk: column names plus one typed Vector each. Equivalent in spirit to a DuckDB DataChunk, but fully owned by MoonBit.

    VectorChunk::column

    fn VectorChunk::column(self : VectorChunk, index : Int) -> Vector?

    The i-th column vector.

    VectorChunk::column_by_name

    fn VectorChunk::column_by_name(self : VectorChunk, name : String) -> Vector?

    The column vector named name (first match).

    VectorChunk::column_count

    fn VectorChunk::column_count(self : VectorChunk) -> Int

    Number of columns in this chunk.

    VectorChunk::column_type

    fn VectorChunk::column_type(self : VectorChunk, col : Int) -> ColumnType

    Logical type of column col.

    VectorChunk::get_value

    fn VectorChunk::get_value(self : VectorChunk, row : Int, col : Int) -> Value?

    The typed value at (row, col), or None when out of bounds or the cell is NULL.

    VectorChunk::is_null

    fn VectorChunk::is_null(self : VectorChunk, row : Int, col : Int) -> Bool

    True when the cell at (row, col) is NULL.

    VectorChunk::new

    fn VectorChunk::new(columns : Array[String], vectors : Array[Vector]) -> Result[VectorChunk, DuckDBError]

    Build a validated VectorChunk from column names and vectors — every vector must carry the same row count.

    VectorChunk::row_count

    fn VectorChunk::row_count(self : VectorChunk) -> Int

    Number of rows in this chunk.

    VectorChunk::to_data_chunk

    fn VectorChunk::to_data_chunk(self : VectorChunk) -> DataChunk

    Materialize this chunk as the string-based DataChunk compatibility facade used by ResultStream::next.

    VectorChunk::to_query_result

    fn VectorChunk::to_query_result(self : VectorChunk) -> QueryResult

    Materialize this chunk as the string-based QueryResult compatibility facade (column types come from the vectors).

    VectorChunk::to_typed

    fn VectorChunk::to_typed(self : VectorChunk) -> TypedQueryResult

    Convert directly to TypedQueryResult without a string round-trip.

    VectorData

    pub(all) enum VectorData {
    Bool(FixedArray[Bool])
    Int32(FixedArray[Int])
    Int64(FixedArray[Int64])
    UInt64(FixedArray[UInt64])
    Float(FixedArray[Float])
    Double(FixedArray[Double])
    Varchar(Array[String])
    Blob(Array[Bytes])
    Decimal(FixedArray[Decimal])
    Interval(FixedArray[Interval])
    HugeInt(lower~ : FixedArray[UInt64], upper~ : FixedArray[Int64])
    List(child~ : Vector, offsets~ : FixedArray[UInt64], lengths~ : FixedArray[UInt64])
    Struct(fields~ : Array[String], children~ : Array[Vector])
    Map(offsets~ : FixedArray[UInt64], lengths~ : FixedArray[UInt64], keys~ : Vector, values~ : Vector)
    Any(Array[Value])
    }

    Backend-neutral typed column storage for one result column.

    Variants hold one owned array per column, laid out like a DuckDB vector. Logical/physical split: Int32 stores TINYINT/SMALLINT/INTEGER/ UTINYINT/USMALLINT/DATE payloads; Int64 stores BIGINT/UINTEGER/TIME/ TIMESTAMP*/TIME_NS payloads (micros/nanos/seconds normalized by Vector::logical_type); HugeInt stores HUGEINT/UHUGEINT/UUID 128-bit payloads; Any is the fallback for types not yet vectorized (UNION, BIT, TIME_TZ, BIGNUM, ...), holding per-cell Values.

    VectorData::len

    fn VectorData::len(self : VectorData) -> Int

    Row count of a VectorData payload (0 for an empty nested payload).

    array_of

    Size-driven array generator compatible with previous Gen::array_of.

    assert_check

    fn[A :
    Debug
    ] assert_check(name : String, gen :
    Gen
    [A], check : (A) -> Result[Unit, String], config? : CheckConfig, shrink? : (A) -> Iter[A]) -> Unit

    check_with_stats

    fn[A :
    Debug
    ] check_with_stats(gen :
    Gen
    [A], check : (A) -> (Result[Unit, String], String?), config? : CheckConfig) -> CheckResult

    chunks_to_query_result

    fn chunks_to_query_result(chunks : Array[VectorChunk]) -> QueryResult

    Combine multiple chunks into one QueryResult (compatibility facade).

    column_type_from_id

    fn column_type_from_id(id : Int) -> ColumnType

    Map DuckDB type id to ColumnType.

    column_type_to_id

    fn column_type_to_id(column_type : ColumnType) -> Int

    DuckDB type id for a ColumnType (inverse of column_type_from_id); Unknown(id) round-trips its payload.

    connect

    fn connect(on_ready~ : (Result[Connection, DuckDBError]) -> Unit, path? : String, backend? : JsBackend) -> Unit

    connect_with_config

    fn connect_with_config(on_ready~ : (Result[Connection, DuckDBError]) -> Unit, config : Config?, path? : String, backend? : JsBackend) -> Unit

    date_from_ymd

    fn date_from_ymd(year : Int, month : Int, day : Int) -> Int

    Convert year, month, day to days since 1970-01-01 (Unix epoch). Uses accurate Gregorian calendar calculation with leap year support.

    date_to_days

    fn date_to_days(year : Int, month : Int, day : Int) -> Int

    Convert year, month, day to days since epoch (1970-01-01).

    date_to_ymd

    fn date_to_ymd(total_days : Int) -> (Int, Int, Int)

    Convert days since 1970-01-01 to year, month, day. Uses accurate Gregorian calendar calculation with leap year support.

    days_in_month

    fn days_in_month(year : Int, month : Int) -> Int

    Get the number of days in a month.

    days_to_ymd

    fn days_to_ymd(days : Int) -> (Int, Int, Int)

    Convert days since epoch to year, month, day. Howard Hinnant's civil_from_days — correct for any day count, including pre-epoch.

    decimal_from_double

    fn decimal_from_double(value : Double, width : Int, scale : Int) -> Decimal

    Create a decimal from a floating-point value with specified precision. Note: This is approximate due to floating-point representation, and is exact only when value * 10^scale fits in an Int64. For values exceeding 64-bit range, use decimal_from_hugeint instead.

    decimal_from_hugeint

    fn decimal_from_hugeint(lower : UInt64, upper : Int64, width : Int, scale : Int) -> Decimal

    Create a decimal from 128-bit parts. lower is the unsigned low 64 bits and upper the signed high 64 bits of the scaled 128-bit value, matching duckdb_hugeint exactly.

    decimal_from_parts

    fn decimal_from_parts(whole : Int64, fractional : Int64, scale : Int) -> Decimal

    Create a decimal from integer parts. The scaled value is whole * 10^scale +/- |fractional|; the result is negative when whole or fractional is negative, so a negative fractional encodes negative sub-unit values like -0.50 and the parts contract round-trips through decimal_to_parts. Excess bits beyond 128 are truncated. |fractional| is expected to be less than 10^scale.

    decimal_to_double

    fn decimal_to_double(decimal : Decimal) -> Double

    Convert decimal to floating-point (approximate). Uses the full 128-bit payload; the result is still limited by Double precision (53-bit mantissa).

    decimal_to_parts

    Get the whole and fractional parts of a decimal. Returns the signed whole part (value / 10^scale, truncated toward zero) and the signed fractional remainder (value mod 10^scale), both as BigInt so the full 128-bit payload is preserved. The remainder keeps the sign of the value, so decimal_from_parts reconstructs the original decimal — including negative sub-unit values like -0.50 -> (0, -50).

    duckdb_message_error

    #deprecated("DuckDBError::Message was replaced by structured variants in 0.7.0; construct the matching variant (Query/Prepare/Bind/Append/Unsupported/Closed/InvalidArgument/Backend/DuckDB) instead")
    fn duckdb_message_error(message : String) -> DuckDBError

    Construct an error from a plain diagnostic message, as constructing DuckDBError::Message(message) used to.

    Prefer constructing the structured variant that fits the failure (for example DuckDBError::Query, DuckDBError::Unsupported, or DuckDBError::Backend).

    expect_fixture_case

    fn expect_fixture_case(case : FixtureCase, columns : Array[String], rows : Array[Array[String]], nulls : Array[Array[Bool]]) -> Unit raise

    expect_query_result

    fn expect_query_result(case : FixtureCase, result : QueryResult) -> Unit raise

    fixture_cases

    let fixture_cases : Array[FixtureCase]

    int_pow10

    fn int_pow10(n : Int) -> Int64

    Compute 10^n for decimal scaling.

    interval_from_days

    fn interval_from_days(days : Int) -> Interval

    Create an interval from days.

    interval_from_hours

    fn interval_from_hours(hours : Int) -> Interval

    Create an interval from hours.

    interval_from_minutes

    fn interval_from_minutes(minutes : Int) -> Interval

    Create an interval from minutes.

    interval_from_months

    fn interval_from_months(months : Int) -> Interval

    Create an interval from months.

    interval_from_parts

    fn interval_from_parts(months : Int, days : Int, micros : Int64) -> Interval

    Create an interval from months, days, and microseconds.

    interval_from_seconds

    fn interval_from_seconds(seconds : Int) -> Interval

    Create an interval from seconds.

    interval_to_micros

    fn interval_to_micros(interval : Interval) -> Int64

    Get the total microseconds equivalent of this interval. Note: This is approximate since months have varying lengths.

    is_double

    fn is_double(s : String) -> Bool

    Check if string represents a double.

    is_integer

    fn is_integer(s : String) -> Bool

    Check if string represents an integer.

    is_leap_year

    fn is_leap_year(year : Int) -> Bool

    Check if a year is a leap year.

    is_special_float_string

    fn is_special_float_string(s : String) -> Bool

    list_from_strings

    fn list_from_strings(elements : Array[String]) -> List

    Create a list from string array.

    list_get

    fn list_get(list : List, index : Int) -> String?

    Get element at index.

    list_length

    fn list_length(list : List) -> Int

    Get list length.

    map_from_arrays

    fn map_from_arrays(keys : Array[String], values : Array[String]) -> Map

    Create a map from key and value arrays.

    map_from_pairs

    fn map_from_pairs(pairs : Array[(String, String)]) -> Map

    Create a map from an array of key-value pairs.

    map_get

    fn map_get(m : Map, key : String) -> String?

    Get value by key.

    map_size

    fn map_size(map : Map) -> Int

    Get map size.

    parse_date

    fn parse_date(s : String) -> Result[Int, String]

    Parse ISO date string to days since epoch.

    parse_double

    fn parse_double(s : String) -> Double

    Parse string to Double.

    parse_fraction_to_micros

    fn parse_fraction_to_micros(s : String) -> Int

    Parse fractional seconds (up to 6 digits) into microseconds.

    parse_int

    fn parse_int(s : String) -> Int

    Parse string to Int.

    parse_time_to_micros

    fn parse_time_to_micros(s : String) -> Result[Int64, String]

    Parse time string (HH:MM:SS[.sss...]) to microseconds since midnight.

    parse_timestamp

    fn parse_timestamp(s : String) -> Result[Int64, String]

    Parse ISO timestamp string to microseconds since epoch.

    parse_value

    fn parse_value(s : String) -> Value

    Infer and parse a string value into an appropriate Value type.

    shrink_int

    fn shrink_int(value : Int) -> Iter[Int]

    Simple integer shrinker for legacy shrink_int usage.

    struct_field_count

    fn struct_field_count(s : Struct) -> Int

    Get struct field count.

    struct_from_arrays

    fn struct_from_arrays(fields : Array[String], values : Array[String]) -> Struct

    Create a struct from field names and values.

    struct_from_pairs

    fn struct_from_pairs(pairs : Array[(String, String)]) -> Struct

    Create a struct from key-value pairs.

    struct_get

    fn struct_get(s : Struct, field_name : String) -> String?

    Get field value by name.

    support_matrix_markdown

    fn support_matrix_markdown() -> String

    Render the README "Feature Support Matrix" section from BackendCapabilities. src/cmd/support_matrix prints this and scripts/support_matrix.sh splices it between the README's support-matrix markers; CI diffs the result so the docs cannot drift from the runtime capability table.

    timestamp_from_ymd_hms

    fn timestamp_from_ymd_hms(year : Int, month : Int, day : Int, hour : Int, minute : Int, second : Int) -> Int64

    Convert date components to a timestamp (microseconds since 1970-01-01). Note: This is a simplified calculation. For production use, use a proper date library.

    timestamp_to_ymd_hms

    fn timestamp_to_ymd_hms(micros : Int64) -> (Int, Int, Int, Int, Int, Int)

    Convert timestamp (microseconds since 1970-01-01) to date components. Note: This is a simplified calculation. For production use, use a proper date library.