{"id":46686142,"url":"https://github.com/tomtom215/quack-rs","last_synced_at":"2026-04-02T16:59:42.725Z","repository":{"id":342387338,"uuid":"1173660359","full_name":"tomtom215/quack-rs","owner":"tomtom215","description":"A Rust SDK for building DuckDB loadable extensions.","archived":false,"fork":false,"pushed_at":"2026-03-29T15:22:26.000Z","size":2126,"stargazers_count":10,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-03-29T16:25:34.271Z","etag":null,"topics":["c-apis","duckdb","duckdb-api","duckdb-community","duckdb-extension","ffi","ffi-wrapper","rust"],"latest_commit_sha":null,"homepage":"https://quack-rs.com/","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/tomtom215.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":"CITATION.cff","codeowners":".github/CODEOWNERS","security":"SECURITY.md","support":null,"governance":"GOVERNANCE.md","roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-03-05T16:00:30.000Z","updated_at":"2026-03-29T15:01:54.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/tomtom215/quack-rs","commit_stats":null,"previous_names":["tomtom215/quack-rs"],"tags_count":8,"template":false,"template_full_name":null,"purl":"pkg:github/tomtom215/quack-rs","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tomtom215%2Fquack-rs","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tomtom215%2Fquack-rs/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tomtom215%2Fquack-rs/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tomtom215%2Fquack-rs/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tomtom215","download_url":"https://codeload.github.com/tomtom215/quack-rs/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tomtom215%2Fquack-rs/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31310984,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-02T12:59:32.332Z","status":"ssl_error","status_checked_at":"2026-04-02T12:54:48.875Z","response_time":89,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["c-apis","duckdb","duckdb-api","duckdb-community","duckdb-extension","ffi","ffi-wrapper","rust"],"created_at":"2026-03-09T02:00:31.688Z","updated_at":"2026-04-02T16:59:42.715Z","avatar_url":"https://github.com/tomtom215.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n  \u003cpicture\u003e\n    \u003csource media=\"(prefers-color-scheme: dark)\" srcset=\"https://raw.githubusercontent.com/tomtom215/quack-rs/main/assets/logos/logo1-dark-elegant.svg\"\u003e\n    \u003cimg src=\"https://raw.githubusercontent.com/tomtom215/quack-rs/main/assets/logos/logo2-light-docs.svg\" alt=\"quack-rs — The Rust SDK for DuckDB extensions\" width=\"600\"\u003e\n  \u003c/picture\u003e\n  \u003cp\u003e\u003cem\u003e/ˈkwækərz/ \u0026nbsp;·\u0026nbsp; rhymes with \u003cem\u003ecrackers\u003c/em\u003e \u0026nbsp;·\u0026nbsp; inspired by DuckDB\u003c/em\u003e\u003c/p\u003e\n  \u003cp\u003e\n    \u003ca href=\"https://github.com/tomtom215/quack-rs/actions/workflows/ci.yml\"\u003e\u003cimg src=\"https://github.com/tomtom215/quack-rs/actions/workflows/ci.yml/badge.svg\" alt=\"CI\"\u003e\u003c/a\u003e\n    \u003ca href=\"https://crates.io/crates/quack-rs\"\u003e\u003cimg src=\"https://img.shields.io/crates/v/quack-rs.svg\" alt=\"Crates.io\"\u003e\u003c/a\u003e\n    \u003ca href=\"https://quack-rs.com/\"\u003e\u003cimg src=\"https://img.shields.io/badge/docs-book-blue.svg\" alt=\"Documentation\"\u003e\u003c/a\u003e\n    \u003ca href=\"https://opensource.org/licenses/MIT\"\u003e\u003cimg src=\"https://img.shields.io/badge/License-MIT-yellow.svg\" alt=\"License: MIT\"\u003e\u003c/a\u003e\n    \u003ca href=\"https://blog.rust-lang.org/2025/01/30/Rust-1.84.1.html\"\u003e\u003cimg src=\"https://img.shields.io/badge/MSRV-1.84.1-blue.svg\" alt=\"MSRV: 1.84.1\"\u003e\u003c/a\u003e\n  \u003c/p\u003e\n\u003c/div\u003e\n\n**The Rust SDK for building DuckDB loadable extensions — no C, no C++, no glue code.**\n\n`quack-rs` provides safe, production-grade wrappers for the [DuckDB C Extension API](https://duckdb.org/community_extensions/development), removing every known FFI pitfall so you can focus entirely on writing extension logic in pure Rust.\n\n---\n\n## Table of Contents\n\n- [Why quack-rs?](#why-quack-rs)\n- [What quack-rs Solves](#what-quack-rs-solves)\n- [Quick Start](#quick-start)\n  - [1. Add the dependency](#1-add-the-dependency)\n  - [2. Write your extension](#2-write-your-extension)\n  - [3. Scaffold a new project](#3-scaffold-a-new-project)\n- [Module Reference](#module-reference)\n- [FFI Pitfalls Reference](#ffi-pitfalls-reference)\n- [Community Extension Compliance](#community-extension-compliance)\n  - [description.yml validation](#descriptionyml-validation)\n  - [Extension naming](#extension-naming)\n  - [Platform targets](#platform-targets)\n  - [Extension versioning](#extension-versioning)\n  - [Release profile requirements](#release-profile-requirements)\n- [Architecture](#architecture)\n  - [Design principles](#design-principles)\n  - [Safety model](#safety-model)\n  - [Architecture Decision Records](#architecture-decision-records)\n- [Testing strategy](#testing-strategy)\n- [Known Limitations](#known-limitations)\n- [Changelog](#changelog)\n- [Contributing](#contributing)\n- [License](#license)\n\n---\n\n## Why quack-rs?\n\nThe [DuckDB community extensions FAQ](https://duckdb.org/community_extensions/faq#can-i-write-extensions-in-rust) states:\n\n\u003e *Writing a Rust-based DuckDB extension requires writing glue code in C++ and will\n\u003e force you to build through DuckDB's CMake \u0026 C++ based extension template. We understand\n\u003e that this is not ideal and acknowledge the fact that Rust developers prefer to work on\n\u003e pure Rust codebases.*\n\nThe DuckDB C Extension API (available since v1.1) changes this. `quack-rs` wraps that API\nand eliminates every rough edge, so you write **zero lines of C or C++**.\n\n### What extension authors face without quack-rs\n\n| Problem | Without quack-rs | With quack-rs |\n|---------|-----------------|---------------|\n| Entry point boilerplate | ~40 lines of `unsafe extern \"C\"` code | 1 macro call |\n| State init/destroy | Raw `Box::into_raw` / `Box::from_raw` | `FfiState\u003cT\u003e` handles all of it |\n| Boolean reads | UB if read as `bool` directly | `VectorReader::read_bool` uses `u8 != 0` |\n| NULL output | Silent corruption if `ensure_validity_writable` skipped | `VectorWriter::set_null` calls it automatically |\n| LogicalType memory | Leak if not freed | `LogicalType` implements `Drop` |\n| Aggregate combine | Config fields lost on segment-tree merges | Testable with `AggregateTestHarness` |\n| FFI panics | Process abort or undefined behavior | `init_extension` never panics; `scalar_callback!` / `table_scan_callback!` catch panics |\n| Table functions | ~100 lines of raw bind/init/scan callbacks | `TableFunctionBuilder` 5-method chain |\n| Replacement scans | Undocumented vtable + manual string allocation | `ReplacementScanBuilder` 4-method chain |\n| Complex types (STRUCT/LIST/MAP/ARRAY) | Manual offset arithmetic over child vectors | `StructVector`, `ListVector`, `MapVector`, `ArrayVector` helpers |\n| Complex param/return types | Raw `duckdb_create_logical_type` + manual lifecycle | `param_logical(LogicalType)` / `returns_logical(LogicalType)` on all builders |\n| Init error propagation | Forced to `panic!()` when runtime allocation fails | `ExtensionError` has `From\u003cio::Error\u003e` — use `?` directly |\n| TLS configuration | No standard way to inject custom TLS configs | `TlsConfigProvider` trait (type-erased, zero deps) |\n| Security warnings | Every extension re-invents warning infrastructure | `ExtensionWarning` + `WarningCollector` with CWE codes |\n| Secrets management | Custom in-memory secrets + DuckDB bridge per extension | `SecretsManager` trait + `SecretEntry` builder |\n| Extension naming | Rejected by DuckDB CI with no explanation | `validate_extension_name` catches issues before submission |\n| description.yml | No tooling to validate before submission | `validate_description_yml_str` validates the whole file |\n| New project setup | Hours of boilerplate + reading DuckDB internals | `generate_scaffold` produces all 11 required files |\n\n---\n\n## What quack-rs Solves\n\nBuilding a DuckDB extension in Rust — from project setup to community submission — requires navigating undocumented C API contracts, FFI memory rules, and data-encoding specifics found only in DuckDB's source code, which surface as silent corruption, process aborts, or unexplained CI rejections rather than compiler errors. `quack-rs` eliminates these barriers systematically across the complete extension lifecycle — scaffolding, function registration, type-safe data access, aggregate testing, metadata validation, and community submission readiness — with every abstraction backed by a documented, reproducible pitfall in [`LESSONS.md`](./LESSONS.md), making correct behavior automatic and incorrect behavior a compile-time error wherever the type system permits. The result is that any Rust developer can build, test, and ship a production-quality DuckDB extension without prior knowledge of DuckDB internals, covering every extension type exposed by DuckDB's public C Extension API: scalar, aggregate, table, cast, copy, replacement scan, and SQL macro functions.\n\n`quack-rs` encapsulates **16 documented FFI pitfalls** — hard-won knowledge from building\nreal DuckDB extensions in Rust:\n\n```\nL1  COMBINE must propagate ALL config fields (not just data)\nL2  State destroy double-free → FfiState\u003cT\u003e nulls pointers after free\nL3  No panics across FFI → init_extension uses Result throughout\nL4  ensure_validity_writable required before NULL output → VectorWriter handles it\nL5  Boolean reading must use u8 != 0 → VectorReader enforces this\nL6  Function set name must be set on EACH member → Set builders enforce on every member\nL7  LogicalType memory leak → LogicalType implements Drop\n\nP1  Library name must match [lib] name in Cargo.toml exactly\nP2  C API version (\"v1.2.0\") ≠ DuckDB release version (\"v1.4.4\" / \"v1.5.0\")\nP3  E2E SQLLogicTests required for community submission\nP4  extension-ci-tools submodule must be initialized\nP5  SQLLogicTest output must match DuckDB CLI output exactly\nP6  Function registration can fail silently → builders check return values\nP7  DuckDB strings use 16-byte format with inline and pointer variants\nP8  INTERVAL is { months: i32, days: i32, micros: i64 } — not a single i64\nP9  loadable-extension dispatch table uninitialised in cargo test → InMemoryDb initialises it\n```\n\nSee [`LESSONS.md`](./LESSONS.md) for full analysis of each pitfall.\n\n---\n\n## Quick Start\n\n### 1. Add the dependency\n\n```toml\n[dependencies]\nquack-rs = \"0.12\"\nlibduckdb-sys = { version = \"\u003e=1.4.4, \u003c2\", features = [\"loadable-extension\"] }\n```\n\n\u003e **DuckDB compatibility**: `quack-rs` supports DuckDB **1.4.x and 1.5.x**.\n\u003e Both releases expose the same C API version (`v1.2.0`), confirmed by E2E tests\n\u003e against DuckDB 1.4.4 and DuckDB 1.5.0. The upper bound `\u003c2` prevents silent\n\u003e adoption of a future major release that may change the C API. When the C API\n\u003e version changes, `quack-rs` will need to be updated and re-released.\n\n### 2. Write your extension\n\n```rust\n// src/lib.rs\nuse quack_rs::prelude::*;\n\n// Step 1: Define your aggregate state\n#[derive(Default)]\nstruct WordCountState {\n    count: i64,\n}\nimpl AggregateState for WordCountState {}\n\n// Step 2: Write callbacks using safe SDK helpers\nunsafe extern \"C\" fn update(\n    _info: libduckdb_sys::duckdb_function_info,\n    chunk: libduckdb_sys::duckdb_data_chunk,\n    states: *mut libduckdb_sys::duckdb_aggregate_state,\n) {\n    let reader = unsafe { VectorReader::new(chunk, 0) };\n    for row in 0..reader.row_count() {\n        if unsafe { reader.is_valid(row) } {\n            let words = unsafe { reader.read_str(row) }\n                .split_whitespace()\n                .count() as i64;\n            if let Some(state) =\n                unsafe { FfiState::\u003cWordCountState\u003e::with_state_mut(*states.add(row)) }\n            {\n                state.count += words;\n            }\n        }\n    }\n}\n\nunsafe extern \"C\" fn finalize(\n    _info: libduckdb_sys::duckdb_function_info,\n    states: *mut libduckdb_sys::duckdb_aggregate_state,\n    result: libduckdb_sys::duckdb_vector,\n    count: libduckdb_sys::idx_t,\n    offset: libduckdb_sys::idx_t,\n) {\n    let mut writer = unsafe { VectorWriter::new(result) };\n    for i in 0..count as usize {\n        let idx = i + offset as usize;\n        match unsafe { FfiState::\u003cWordCountState\u003e::with_state(*states.add(i)) } {\n            Some(state) =\u003e unsafe { writer.write_i64(idx, state.count) },\n            None =\u003e unsafe { writer.set_null(idx) },\n        }\n    }\n}\n\nunsafe extern \"C\" fn combine(\n    _info: libduckdb_sys::duckdb_function_info,\n    source: *mut libduckdb_sys::duckdb_aggregate_state,\n    target: *mut libduckdb_sys::duckdb_aggregate_state,\n    count: libduckdb_sys::idx_t,\n) {\n    for i in 0..count as usize {\n        if let (Some(src), Some(tgt)) = (\n            unsafe { FfiState::\u003cWordCountState\u003e::with_state(*source.add(i)) },\n            unsafe { FfiState::\u003cWordCountState\u003e::with_state_mut(*target.add(i)) },\n        ) {\n            tgt.count += src.count;\n        }\n    }\n}\n\n// Step 3: Register using the builder\nfn register(con: libduckdb_sys::duckdb_connection) -\u003e ExtResult\u003c()\u003e {\n    unsafe {\n        AggregateFunctionBuilder::try_new(\"word_count\")?\n            .param(TypeId::Varchar)\n            .returns(TypeId::BigInt)\n            .state_size(FfiState::\u003cWordCountState\u003e::size_callback)\n            .init(FfiState::\u003cWordCountState\u003e::init_callback)\n            .update(update)\n            .combine(combine)\n            .finalize(finalize)\n            .destructor(FfiState::\u003cWordCountState\u003e::destroy_callback)\n            .register(con)?;\n    }\n    Ok(())\n}\n\n// Step 4: One macro call generates the entry point (pass the full symbol name DuckDB expects)\nentry_point!(my_extension_init_c_api, |con| register(con));\n```\n\n### 3. Scaffold a new project\n\n```rust\nuse quack_rs::scaffold::{generate_scaffold, ScaffoldConfig};\n\nlet config = ScaffoldConfig {\n    name: \"my_extension\".to_string(),\n    description: \"Fast text analytics for DuckDB\".to_string(),\n    version: \"0.1.0\".to_string(),\n    license: \"MIT\".to_string(),\n    maintainer: \"Your Name\".to_string(),\n    github_repo: \"yourorg/duckdb-my-extension\".to_string(),\n    excluded_platforms: vec![], // or vec![\"wasm_mvp\".to_string(), ...]\n};\n\nlet files = generate_scaffold(\u0026config)?;\nfor file in \u0026files {\n    println!(\"{}\", file.path);\n    // write file.content to disk\n}\n```\n\nThis generates all 11 files required for a DuckDB community extension submission:\n\n```\nCargo.toml                          ← cdylib, pinned deps, release profile\nMakefile                            ← delegates to cargo + extension-ci-tools\nextension_config.cmake              ← required by extension-ci-tools\nsrc/lib.rs                          ← entry point template (no C++ needed)\nsrc/wasm_lib.rs                     ← WebAssembly shim\ndescription.yml                     ← community extension metadata\ntest/sql/my_extension.test          ← SQLLogicTest skeleton\n.github/workflows/extension-ci.yml ← cross-platform CI (Linux/macOS/Windows)\n.gitmodules                         ← extension-ci-tools submodule\n.gitignore\n.cargo/config.toml                  ← Windows CRT static linking\n```\n\n### 4. Append extension metadata\n\nDuckDB loadable extensions require a metadata footer appended to the `.so`/`.dylib`/`.dll`\nafter `cargo build --release`. `quack-rs` ships a native Rust binary for this step,\nreplacing the Python `append_extension_metadata.py` script from the C++ template:\n\n```shell\n# Install the binary from the published crate\ncargo install quack-rs --bin append_metadata\n\n# Append metadata to your built extension (input .so → output .duckdb_extension)\nappend_metadata target/release/libmy_extension.so \\\n    my_extension.duckdb_extension \\\n    --duckdb-version v1.2.0 \\\n    --platform linux_amd64\n```\n\n\u003e **Pitfall P2**: The `--duckdb-version` flag must be `v1.2.0` (the C API version),\n\u003e **not** the DuckDB release version (`v1.4.4` or `v1.5.0`). DuckDB 1.4.x and 1.5.x\n\u003e both use C API version `v1.2.0`. Use the `DUCKDB_API_VERSION` constant from\n\u003e `quack_rs` to avoid hard-coding the wrong value.\n\n---\n\n## Module Reference\n\n| Module | Purpose | Key types / functions |\n|--------|---------|----------------------|\n| [`entry_point`] | Extension initialization entry point | `init_extension`, `init_extension_v2`, `entry_point!`, `entry_point_v2!` |\n| [`connection`] | Version-agnostic extension registration facade | `Connection`, `Registrar` |\n| [`callback`] | Safe `extern \"C\"` callback wrapper macros | `scalar_callback!`, `table_scan_callback!` |\n| [`aggregate`] | Aggregate function registration | `AggregateFunctionBuilder`, `AggregateFunctionSetBuilder`, `AggregateFunctionInfo` |\n| [`aggregate::state`] | Generic FFI state management | `AggregateState`, `FfiState\u003cT\u003e` |\n| [`aggregate::callbacks`] | Callback type aliases | `UpdateFn`, `CombineFn`, `FinalizeFn`, … |\n| [`scalar`] | Scalar function registration | `ScalarFunctionBuilder`, `ScalarFunctionSetBuilder`, `ScalarOverloadBuilder`, `ScalarFunctionInfo`, `ScalarBindInfo`¹, `ScalarInitInfo`¹ |\n| [`cast`] | Custom type cast functions | `CastFunctionBuilder`, `CastFunctionInfo`, `CastMode` |\n| [`table`] | Table function registration (bind/init/scan) | `TableFunctionBuilder`, `BindInfo`, `FfiBindData`, `FfiInitData` |\n| [`replacement_scan`] | `SELECT * FROM 'file.xyz'` replacement scans | `ReplacementScanBuilder` |\n| [`sql_macro`] | SQL macro registration (no FFI callbacks) | `SqlMacro`, `MacroBody` |\n| [`data_chunk`] | Ergonomic wrapper for DuckDB data chunks | `DataChunk` |\n| [`chunk_writer`] | Auto-sizing chunk writer for scan callbacks | `ChunkWriter` |\n| [`value`] | RAII wrapper for DuckDB values with typed extraction | `Value` |\n| [`vector`] | Safe reading/writing of DuckDB vectors | `VectorReader`, `VectorWriter`, `ValidityBitmap`, `vector_size()` |\n| [`vector::complex`] | STRUCT / LIST / MAP / ARRAY child vector access | `StructVector`, `ListVector`, `MapVector`, `ArrayVector` |\n| [`vector::struct_writer`] | Batched typed writer for STRUCT output vectors | `StructWriter` |\n| [`vector::struct_reader`] | Batched typed reader for STRUCT input vectors | `StructReader` |\n| [`vector::string`] | 16-byte DuckDB string format | `DuckStringView`, `read_duck_string` |\n| [`types`] | DuckDB type system wrappers | `TypeId`, `LogicalType`, `NullHandling` |\n| [`interval`] | INTERVAL ↔ microseconds conversion | `DuckInterval`, `interval_to_micros` |\n| [`error`] | FFI-safe error type | `ExtensionError`, `ExtResult\u003cT\u003e` |\n| [`tls`] | Type-erased TLS config provider for HTTP-capable extensions | `TlsConfigProvider` |\n| [`warning`] | Structured security warning API | `ExtensionWarning`, `WarningSeverity`, `WarningCollector` |\n| [`secrets`] | Secrets manager bridge trait | `SecretsManager`, `SecretEntry` |\n| [`config`] | RAII wrapper for DuckDB database configuration | `DbConfig` |\n| [`validate`] | Community extension compliance | All validators below |\n| [`validate::description_yml`] | description.yml parsing and validation | `parse_description_yml`, `DescriptionYml` |\n| [`validate::extension_name`] | Extension naming rules | `validate_extension_name` |\n| [`validate::function_name`] | SQL identifier rules | `validate_function_name` |\n| [`validate::semver`] | Semantic versioning | `validate_semver`, `ExtensionStability` |\n| [`validate::spdx`] | SPDX license identifiers | `validate_spdx_license` |\n| [`validate::platform`] | DuckDB build targets | `validate_platform`, `DUCKDB_PLATFORMS` |\n| [`validate::release_profile`] | Cargo release profile | `validate_release_profile` |\n| [`scaffold`] | Project generator | `generate_scaffold`, `ScaffoldConfig` |\n| [`testing`] | Mock vectors, aggregate harness, and registrar | `AggregateTestHarness\u003cS\u003e`, `MockVectorWriter`, `MockVectorReader`, `MockRegistrar` |\n| [`prelude`] | Common re-exports | `use quack_rs::prelude::*` |\n| [`catalog`]¹ | Catalog entry lookup | `CatalogEntry`, `Catalog`, `CatalogEntryType` |\n| [`client_context`]¹ | Client context access (catalog, config, connection ID) | `ClientContext` |\n| [`config_option`]¹ | Extension-defined configuration options | `ConfigOptionBuilder`, `ConfigOptionScope` |\n| [`copy_function`]¹ | Custom `COPY TO` handlers | `CopyFunctionBuilder`, `CopyBindInfo`, `CopyGlobalInitInfo`, `CopySinkInfo`, `CopyFinalizeInfo` |\n| [`table_description`]¹ | Table metadata (column count, names, types) | `TableDescription` |\n\n\u003e ¹ Requires the `duckdb-1-5` feature flag (DuckDB 1.5.0+).\n\n[`callback`]: https://docs.rs/quack-rs/latest/quack_rs/callback/index.html\n[`chunk_writer`]: https://docs.rs/quack-rs/latest/quack_rs/chunk_writer/index.html\n[`data_chunk`]: https://docs.rs/quack-rs/latest/quack_rs/data_chunk/index.html\n[`value`]: https://docs.rs/quack-rs/latest/quack_rs/value/index.html\n[`vector::struct_writer`]: https://docs.rs/quack-rs/latest/quack_rs/vector/struct_writer/index.html\n[`vector::struct_reader`]: https://docs.rs/quack-rs/latest/quack_rs/vector/struct_reader/index.html\n[`entry_point`]: https://docs.rs/quack-rs/latest/quack_rs/entry_point/index.html\n[`connection`]: https://docs.rs/quack-rs/latest/quack_rs/connection/index.html\n[`aggregate`]: https://docs.rs/quack-rs/latest/quack_rs/aggregate/index.html\n[`aggregate::state`]: https://docs.rs/quack-rs/latest/quack_rs/aggregate/state/index.html\n[`aggregate::callbacks`]: https://docs.rs/quack-rs/latest/quack_rs/aggregate/callbacks/index.html\n[`scalar`]: https://docs.rs/quack-rs/latest/quack_rs/scalar/index.html\n[`cast`]: https://docs.rs/quack-rs/latest/quack_rs/cast/index.html\n[`table`]: https://docs.rs/quack-rs/latest/quack_rs/table/index.html\n[`replacement_scan`]: https://docs.rs/quack-rs/latest/quack_rs/replacement_scan/index.html\n[`sql_macro`]: https://docs.rs/quack-rs/latest/quack_rs/sql_macro/index.html\n[`vector`]: https://docs.rs/quack-rs/latest/quack_rs/vector/index.html\n[`vector::complex`]: https://docs.rs/quack-rs/latest/quack_rs/vector/complex/index.html\n[`vector::string`]: https://docs.rs/quack-rs/latest/quack_rs/vector/string/index.html\n[`types`]: https://docs.rs/quack-rs/latest/quack_rs/types/index.html\n[`interval`]: https://docs.rs/quack-rs/latest/quack_rs/interval/index.html\n[`error`]: https://docs.rs/quack-rs/latest/quack_rs/error/index.html\n[`tls`]: https://docs.rs/quack-rs/latest/quack_rs/tls/index.html\n[`warning`]: https://docs.rs/quack-rs/latest/quack_rs/warning/index.html\n[`secrets`]: https://docs.rs/quack-rs/latest/quack_rs/secrets/index.html\n[`config`]: https://docs.rs/quack-rs/latest/quack_rs/config/index.html\n[`validate`]: https://docs.rs/quack-rs/latest/quack_rs/validate/index.html\n[`validate::description_yml`]: https://docs.rs/quack-rs/latest/quack_rs/validate/description_yml/index.html\n[`validate::extension_name`]: https://docs.rs/quack-rs/latest/quack_rs/validate/extension_name/index.html\n[`validate::function_name`]: https://docs.rs/quack-rs/latest/quack_rs/validate/function_name/index.html\n[`validate::semver`]: https://docs.rs/quack-rs/latest/quack_rs/validate/semver/index.html\n[`validate::spdx`]: https://docs.rs/quack-rs/latest/quack_rs/validate/spdx/index.html\n[`validate::platform`]: https://docs.rs/quack-rs/latest/quack_rs/validate/platform/index.html\n[`validate::release_profile`]: https://docs.rs/quack-rs/latest/quack_rs/validate/release_profile/index.html\n[`scaffold`]: https://docs.rs/quack-rs/latest/quack_rs/scaffold/index.html\n[`testing`]: https://docs.rs/quack-rs/latest/quack_rs/testing/index.html\n[`prelude`]: https://docs.rs/quack-rs/latest/quack_rs/prelude/index.html\n[`catalog`]: https://docs.rs/quack-rs/latest/quack_rs/catalog/index.html\n[`client_context`]: https://docs.rs/quack-rs/latest/quack_rs/client_context/index.html\n[`config_option`]: https://docs.rs/quack-rs/latest/quack_rs/config_option/index.html\n[`copy_function`]: https://docs.rs/quack-rs/latest/quack_rs/copy_function/index.html\n[`table_description`]: https://docs.rs/quack-rs/latest/quack_rs/table_description/index.html\n\n---\n\n## FFI Pitfalls Reference\n\nThe following table summarizes every known DuckDB Rust FFI pitfall and how `quack-rs` addresses\nit. The full analysis — including symptoms, root cause, and minimal reproduction — is in\n[`LESSONS.md`](./LESSONS.md).\n\n### Logic Pitfalls (L)\n\n| ID | Name | Symptom | quack-rs Solution |\n|----|------|---------|-------------------|\n| **L1** | COMBINE config propagation | Aggregate returns wrong results under parallelism | Testable with `AggregateTestHarness` |\n| **L2** | Double-free in destroy | Heap corruption / SIGABRT | `FfiState\u003cT\u003e::destroy_callback` nulls pointer after free |\n| **L3** | Panic across FFI | Process abort / UB | `init_extension` propagates `Result`; `scalar_callback!` / `table_scan_callback!` catch panics with `catch_unwind` |\n| **L4** | Missing `ensure_validity_writable` | Segfault / silent NULL corruption | `VectorWriter::set_null` calls it automatically |\n| **L5** | Boolean undefined behavior | Non-deterministic bool semantics | `VectorReader::read_bool` reads `u8 != 0` |\n| **L6** | Function set name on each member | Silent registration failure | `AggregateFunctionSetBuilder` and `ScalarFunctionSetBuilder` set name on every member |\n| **L7** | `LogicalType` memory leak | RSS grows with each extension load | `LogicalType` implements `Drop` |\n\n### Practical Pitfalls (P)\n\n| ID | Name | Symptom | quack-rs Solution |\n|----|------|---------|-------------------|\n| **P1** | Library name mismatch | Extension fails to load | Documented; scaffold sets it correctly |\n| **P2** | C API version ≠ release version | Wrong `-dv` flag corrupts extension metadata | `DUCKDB_API_VERSION = \"v1.2.0\"` constant; `append_metadata` binary ships with the crate |\n| **P3** | Missing E2E tests | Community submission rejected | Scaffold generates SQLLogicTest skeleton |\n| **P4** | Uninitialized submodule | `make` fails with missing files | Documented; scaffold generates `.gitmodules` |\n| **P5** | SQLLogicTest format mismatch | Tests fail with exact-match errors | Documented with format reference |\n| **P6** | Registration failure not checked | Function silently not registered | Builders check and propagate return values |\n| **P7** | DuckDB 16-byte string format | Garbled or truncated strings | `DuckStringView`, `read_duck_string` |\n| **P8** | INTERVAL layout misunderstood | INTERVAL computed incorrectly | `DuckInterval` with `interval_to_micros` |\n| **P9** | `loadable-extension` dispatch table uninitialised in `cargo test` | `InMemoryDb::open()` panics with `\"DuckDB API not initialized\"` | `InMemoryDb::open()` calls `CreateAPIv1()` shim to populate dispatch table before opening connection |\n\n---\n\n## Community Extension Compliance\n\n`quack-rs` enforces every requirement from the\n[DuckDB community extensions development guide](https://duckdb.org/community_extensions/development).\n\n### description.yml validation\n\nEvery community extension must include a `description.yml` metadata file.\n`quack-rs` can validate the entire file before submission:\n\n```rust\nuse quack_rs::validate::description_yml::{\n    parse_description_yml, validate_rust_extension, validate_description_yml_str,\n};\n\n// Quick pass/fail check\nlet result = validate_description_yml_str(include_str!(\"description.yml\"));\nassert!(result.is_ok(), \"description.yml has errors: {}\", result.unwrap_err());\n\n// Structured access for programmatic inspection\nlet desc = parse_description_yml(include_str!(\"description.yml\"))?;\nprintln!(\"Extension: {} v{}\", desc.name, desc.version);\nprintln!(\"Maintainers: {:?}\", desc.maintainers);\n\n// Validate Rust-specific fields (language, build, toolchains)\nvalidate_rust_extension(\u0026desc)?;\n```\n\n**Validated fields:**\n\n| Field | Rule |\n|-------|------|\n| `extension.name` | `^[a-z][a-z0-9_-]*$`, max 64 chars |\n| `extension.version` | Semver or 7–40 char lowercase hex git hash |\n| `extension.license` | Recognized SPDX identifier |\n| `extension.excluded_platforms` | Semicolon-separated list of known DuckDB platforms |\n| `extension.maintainers` | At least one maintainer required |\n| `repo.github` | Must contain `/` (owner/repo format) |\n| `repo.ref` | Non-empty git ref |\n\n**Example `description.yml`:**\n\n```yaml\nextension:\n  name: my_extension\n  description: Fast text analytics for DuckDB.\n  version: 0.1.0\n  language: Rust\n  build: cargo\n  license: MIT\n  requires_toolchains: rust;python3\n  excluded_platforms: \"wasm_mvp;wasm_eh;wasm_threads\"\n  maintainers:\n    - Your Name\n\nrepo:\n  github: yourorg/duckdb-my-extension\n  ref: main\n```\n\n### Extension naming\n\nExtensions submitted to the community repository must follow strict naming rules.\nValidate before you submit:\n\n```rust\nuse quack_rs::validate::{validate_extension_name, validate_function_name};\n\n// Extension names: lowercase alphanumeric, hyphens and underscores allowed\nassert!(validate_extension_name(\"my_analytics\").is_ok());\nassert!(validate_extension_name(\"MyExt\").is_err());       // uppercase rejected\nassert!(validate_extension_name(\"my ext\").is_err());      // spaces rejected\nassert!(validate_extension_name(\"\").is_err());             // empty rejected\n\n// Function/SQL identifier names: lowercase alphanumeric and underscores only\nassert!(validate_function_name(\"word_count\").is_ok());\nassert!(validate_function_name(\"word-count\").is_err());    // hyphens not allowed in SQL\nassert!(validate_function_name(\"WordCount\").is_err());     // uppercase rejected\n```\n\n### Platform targets\n\nDuckDB community extensions must build for all 11 standard platforms or declare\nexplicit exclusions:\n\n```\nlinux_amd64         linux_amd64_gcc4    linux_arm64\nosx_amd64           osx_arm64\nwindows_amd64       windows_amd64_mingw windows_arm64\nwasm_mvp            wasm_eh             wasm_threads\n```\n\n```rust\nuse quack_rs::validate::{validate_platform, validate_excluded_platforms_str, DUCKDB_PLATFORMS};\n\n// Validate a single platform\nassert!(validate_platform(\"linux_amd64\").is_ok());\nassert!(validate_platform(\"freebsd_amd64\").is_err()); // not a DuckDB platform\n\n// Validate the excluded_platforms field from description.yml\n// (semicolon-separated, as it appears in the YAML file)\nassert!(validate_excluded_platforms_str(\"wasm_mvp;wasm_eh;wasm_threads\").is_ok());\nassert!(validate_excluded_platforms_str(\"invalid_platform\").is_err());\nassert!(validate_excluded_platforms_str(\"\").is_ok()); // empty = no exclusions\n\nprintln!(\"All DuckDB platforms: {:?}\", DUCKDB_PLATFORMS);\n```\n\n### Extension versioning\n\nDuckDB extensions use a three-tier versioning scheme:\n\n| Tier | Format | Example | Meaning |\n|------|--------|---------|---------|\n| **Unstable** | Short git hash | `690bfc5` | No stability guarantees |\n| **Pre-release** | Semver `0.y.z` | `0.1.0` | Working toward stability |\n| **Stable** | Semver `x.y.z` (x ≥ 1) | `1.0.0` | Full semver semantics |\n\n```rust\nuse quack_rs::validate::{validate_extension_version, validate_semver};\nuse quack_rs::validate::semver::{classify_extension_version, ExtensionStability};\n\n// validate_extension_version accepts both semver and git hashes\nassert!(validate_extension_version(\"1.0.0\").is_ok());\nassert!(validate_extension_version(\"0.1.0\").is_ok());\nassert!(validate_extension_version(\"690bfc5\").is_ok()); // git hash (unstable)\nassert!(validate_extension_version(\"\").is_err());\n\n// Classify the stability tier\nlet (stability, _) = classify_extension_version(\"1.0.0\").unwrap();\nassert_eq!(stability, ExtensionStability::Stable);\n\nlet (stability, _) = classify_extension_version(\"0.1.0\").unwrap();\nassert_eq!(stability, ExtensionStability::PreRelease);\n\nlet (stability, _) = classify_extension_version(\"690bfc5\").unwrap();\nassert_eq!(stability, ExtensionStability::Unstable);\n```\n\n### Release profile requirements\n\nDuckDB loadable extensions are shared libraries. The Cargo release profile must be\nconfigured correctly:\n\n```toml\n[profile.release]\npanic = \"abort\"     # REQUIRED: panics across FFI are undefined behavior\nlto = true          # Recommended: reduces binary size\ncodegen-units = 1   # Recommended: better optimization quality\nopt-level = 3       # Recommended: maximum performance\nstrip = true        # Recommended: smaller binary\n```\n\n```rust\nuse quack_rs::validate::validate_release_profile;\n\n// Validate from parsed Cargo.toml values\nlet check = validate_release_profile(\"abort\", \"true\", \"3\", \"1\").unwrap();\nassert!(check.is_fully_optimized());\nassert!(check.panic_abort);       // REQUIRED\nassert!(check.lto_enabled);       // recommended\nassert!(check.opt_level_3);       // recommended\nassert!(check.codegen_units_1);   // recommended\n\n// Missing panic=abort is rejected with a descriptive error\nlet err = validate_release_profile(\"unwind\", \"true\", \"3\", \"1\").unwrap_err();\nassert!(err.as_str().contains(\"panic\"));\n```\n\n---\n\n## Architecture\n\n### Module dependency graph\n\n```mermaid\nflowchart TB\n    Author([\"**Extension Author**\u003cbr/\u003euse quack_rs::prelude::*\"]):::author\n\n    subgraph REG [\"Registration layer\"]\n        direction LR\n        EP[\"**entry_point**\u003cbr/\u003eentry_point! · init_extension\"]\n        AGG[\"**aggregate**\u003cbr/\u003eAggregateFunctionBuilder\u003cbr/\u003eAggregateFunctionSetBuilder · FfiState\u0026lt;T\u0026gt;\"]\n        SCL[\"**scalar**\u003cbr/\u003eScalarFunctionBuilder\u003cbr/\u003eScalarFunctionSetBuilder\"]\n        TBL[\"**table**\u003cbr/\u003eTableFunctionBuilder\u003cbr/\u003eBindInfo · FfiBindData · FfiInitData\"]\n        RSC[\"**replacement_scan**\u003cbr/\u003eReplacementScanBuilder\"]\n        SM[\"**sql_macro**\u003cbr/\u003eSqlMacro · MacroBody\"]\n        CST[\"**cast**\u003cbr/\u003eCastFunctionBuilder\"]\n        CPY[\"**copy_function**¹\u003cbr/\u003eCopyFunctionBuilder\"]\n    end\n\n    subgraph DATA [\"Data layer\"]\n        direction LR\n        VEC[\"**vector**\u003cbr/\u003eVectorReader · VectorWriter\u003cbr/\u003eValidityBitmap · DuckStringView\"]\n        CMP[\"**vector::complex**\u003cbr/\u003eStructVector · ListVector · MapVector · ArrayVector\"]\n        TYP[\"**types**\u003cbr/\u003eTypeId · LogicalType\"]\n        INT[\"**interval**\u003cbr/\u003eDuckInterval · interval_to_micros\"]\n        CFG[\"**config_option**¹\u003cbr/\u003eConfigOptionBuilder\"]\n        CAT[\"**catalog**¹\u003cbr/\u003eCatalogEntry · Catalog\"]\n        CTX[\"**client_context**¹\u003cbr/\u003eClientContext\"]\n        TDS[\"**table_description**¹\u003cbr/\u003eTableDescription\"]\n    end\n\n    SYS[\"**libduckdb-sys** \u003e=1.4.4, \u0026lt;2\u003cbr/\u003eDuckDB C Extension API\u003cbr/\u003eheaders only · no linked library\"]:::ffi\n\n    RT[(\"**DuckDB**\u003cbr/\u003eRuntime\")]:::duckdb\n\n    subgraph EXT [\"Extension infrastructure\"]\n        direction LR\n        TLS[\"**tls**\u003cbr/\u003eTlsConfigProvider\"]\n        WRN[\"**warning**\u003cbr/\u003eExtensionWarning · WarningCollector\"]\n        SEC[\"**secrets**\u003cbr/\u003eSecretsManager · SecretEntry\"]\n    end\n\n    subgraph DEV [\"Dev-time utilities\"]\n        direction LR\n        ERR[\"**error**\u003cbr/\u003eExtensionError · ExtResult\u0026lt;T\u0026gt;\"]\n        TST[\"**testing**\u003cbr/\u003eAggregateTestHarness\u0026lt;S\u0026gt;\u003cbr/\u003epure Rust · no FFI\"]\n        VAL[\"**validate**\u003cbr/\u003eextension_name · semver\u003cbr/\u003espdx · platform\u003cbr/\u003edescription_yml\"]\n        SCF[\"**scaffold**\u003cbr/\u003egenerate_scaffold\u003cbr/\u003eScaffoldConfig\"]\n    end\n\n    Author --\u003e REG\n    REG    --\u003e DATA\n    DATA   --\u003e SYS\n    SYS    --\u003e RT\n    Author -.-\u003e|\"dev-time\"| DEV\n\n    classDef author fill:#1e3a5f,stroke:#5a9fd4,color:#ddf0ff\n    classDef ffi fill:#3d2406,stroke:#c87941,color:#f5dbb4\n    classDef duckdb fill:#1c3b1c,stroke:#4a9e4a,color:#c8ecc8\n```\n\n### Design principles\n\n1. **Thin wrapper mandate**: Every abstraction must pay for itself in reduced boilerplate\n   or improved safety. When in doubt, prefer simplicity over cleverness.\n\n2. **No panics across FFI**: `unwrap()` and `panic!()` are forbidden in any code path that\n   crosses the FFI boundary. All errors propagate as `Result\u003cT, ExtensionError\u003e`.\n\n3. **Bounded version range**: `libduckdb-sys = \"\u003e=1.4.4, \u003c2\"` is deliberate.\n   The C API is stable across DuckDB 1.4.x and 1.5.x (verified by E2E tests on both).\n   The upper bound prevents silent adoption of a future major release. When the C API\n   version changes, `quack-rs` will be updated.\n\n4. **Testable business logic**: Aggregate state structs have zero FFI dependencies. They\n   can be tested in isolation using `AggregateTestHarness\u003cS\u003e` without a DuckDB runtime.\n\n5. **Enforce community standards**: The `validate` module makes it impossible to accidentally\n   submit a non-compliant extension. Validation errors are caught at build time, not\n   submission time.\n\n### Safety model\n\nAll `unsafe` code within `quack-rs` is sound and documented. Extension authors must write\n`unsafe extern \"C\"` callback functions (required by DuckDB's C API), but the SDK's helpers\n(`FfiState`, `VectorReader`, `VectorWriter`) minimize the surface area of unsafe code\nwithin those callbacks. Every `unsafe` block in this crate has a `// SAFETY:` comment\nexplaining the invariants being upheld.\n\n```rust\n// Extension author code: no unsafe required\nfn register(con: duckdb_connection) -\u003e ExtResult\u003c()\u003e {\n    AggregateFunctionBuilder::try_new(\"word_count\")?\n        .param(TypeId::Varchar)\n        .returns(TypeId::BigInt)\n        // ... callbacks (which are unsafe extern \"C\" fns) ...\n        .register(con)          // unsafe is inside the SDK\n}\n```\n\n### Architecture Decision Records\n\n**ADR-1: Thin Wrapper Mandate**\n\n`quack-rs` wraps, but does not redesign, the DuckDB C API. We do not invent new abstractions\nthat would require understanding two APIs. Extension authors who want to go below the SDK can\nuse `libduckdb-sys` directly — the two libraries compose without conflict.\n\n**ADR-2: Bounded Version Range**\n\n`libduckdb-sys = \"\u003e=1.4.4, \u003c2\"` is intentional. The DuckDB C Extension API is stable\nacross 1.4.x and 1.5.x — verified by E2E tests against DuckDB 1.4.4 and DuckDB 1.5.0.\nBoth releases use C API version `v1.2.0`. The upper bound `\u003c2` prevents silent adoption\nof a future major-band release that may change the C API version or callback signatures.\nWhen a future DuckDB release bumps the C API version, `quack-rs` will need to be updated\nto match.\n\n**ADR-3: No Panics Across FFI**\n\nThe Rust reference is explicit: unwinding across an FFI boundary is undefined behavior.\n`quack-rs` enforces this architecturally: every FFI boundary in the SDK is wrapped by\n`init_extension`, which converts `Result::Err` into a DuckDB error report via `set_error`.\nNo `unwrap()`, `expect()`, or `panic!()` appears in any code path reachable from a DuckDB\ncallback.\n\n---\n\n## Testing strategy\n\n`quack-rs` uses four layers of tests:\n\n### 1. Unit tests (in every source file)\n\nEach module contains `#[cfg(test)]` unit tests that verify pure-Rust behavior without\na DuckDB runtime. These test state machine correctness, builder field storage, validation\nlogic, and the `description.yml` parser.\n\n### 2. Property-based tests (proptest)\n\nThe `interval` module and `AggregateTestHarness` include proptest-based tests:\n\n- `interval_to_micros` overflow is detected for all possible field combinations\n- `AggregateTestHarness::combine` associativity holds for sum aggregates\n- `combine` identity element property: combining with empty state is idempotent\n\n### 3. Integration tests (`tests/integration_test.rs`)\n\nPure-Rust cross-module tests that exercise the public API without a live DuckDB process.\nThese cover `DuckInterval`, `TypeId`, `FfiState\u003cT\u003e` lifecycle, `AggregateTestHarness`,\n`ExtensionError`, `VectorReader`/`VectorWriter` layout, `DuckStringView`, and `SqlMacro`.\n\n### 4. Example extension (`examples/hello-ext`)\n\nA comprehensive extension that exercises **every feature** in `quack-rs`: scalar, aggregate,\ntable, cast, replacement scan, and SQL macro functions — plus complex types (STRUCT, LIST, MAP),\n`entry_point_v2!`/`Connection`/`Registrar`, aggregate sets, scalar sets with per-overload NULL\nhandling, `DuckInterval`, `ValidityBitmap`, `named_param`, `local_init`, `implicit_cost`,\n`extra_info`, and all `VectorReader`/`VectorWriter` type variants. All 39 live SQL tests pass\nagainst both DuckDB 1.4.4 and 1.5.0.\n\n### Testing aggregate logic without DuckDB\n\nThe `AggregateTestHarness\u003cS\u003e` type lets you test aggregate state logic in isolation:\n\n```rust\nuse quack_rs::testing::AggregateTestHarness;\nuse quack_rs::aggregate::AggregateState;\n\n#[derive(Default)]\nstruct SumState { total: i64 }\nimpl AggregateState for SumState {}\n\n// Test update logic\nlet result = AggregateTestHarness::\u003cSumState\u003e::aggregate(\n    [10_i64, 20, 30],\n    |s, v| s.total += v,\n);\nassert_eq!(result.total, 60);\n\n// Test combine: verify config fields are propagated (Pitfall L1)\nlet mut source = AggregateTestHarness::\u003cSumState\u003e::new();\nsource.update(|s| s.total = 100);\n\nlet mut target = AggregateTestHarness::\u003cSumState\u003e::new();\ntarget.combine(\u0026source, |src, tgt| tgt.total += src.total);\n\nassert_eq!(target.finalize().total, 100);\n```\n\n---\n\n## Known Limitations\n\n### Window functions are not available\n\nDuckDB's **window functions** (`OVER (...)` clauses) are implemented entirely in the C++\nAPI layer and have **no counterpart in DuckDB's public C extension API**. This is not a\ngap in `quack-rs` or in `libduckdb-sys` — the symbol `duckdb_create_window_function`\ndoes not exist in the C API.\n\n\u003e **COPY format handlers** were previously in this category, but DuckDB 1.5.0 added\n\u003e `duckdb_create_copy_function` and related symbols. quack-rs wraps them in the\n\u003e [`copy_function`] module (requires the `duckdb-1-5` feature flag).\n\nIf DuckDB exposes the window function API in a future C API version, `quack-rs` will\nadd wrappers in the relevant release.\n\n### VARIANT type (Iceberg v3)\n\nDuckDB v1.5.1 introduced the `VARIANT` type for Iceberg v3 support. This type is\n**not yet exposed** in the DuckDB C Extension API (`DUCKDB_TYPE_VARIANT` does not\nexist in `libduckdb-sys` 1.10501.0). `quack-rs` will add `TypeId::Variant` when the\nC API exposes it.\n\n\u003e For the full list of resolved and open limitations, see the\n\u003e [Known Limitations](https://quack-rs.com/reference/known-limitations.html) reference page.\n\n---\n\n## Changelog\n\nSee [`CHANGELOG.md`](./CHANGELOG.md) for the full version history.\n\n**v0.12.0** (2026-03-31) — Added `tls` module (`TlsConfigProvider` trait for type-erased TLS\nclient configuration injection with CWE-coded audit), `warning` module (`ExtensionWarning`,\n`WarningSeverity`, `WarningCollector` for structured security warnings), `secrets` module\n(`SecretsManager` trait, `SecretEntry` with redacted `Debug`, volatile zeroize on `Drop`).\nAdded `From\u003cio::Error\u003e`, `From\u003cNulError\u003e`, `From\u003cfmt::Error\u003e` on `ExtensionError`.\nAdded `StructWriter::child_list_vector()` convenience alias.\n\n**v0.11.0** (2026-03-30) — Added `StructWriter::child_vector()`, `StructReader::child_vector()`\nfor nested complex types inside STRUCT fields. Added `ChunkWriter::vector()`,\n`ChunkWriter::column_count()`, `VectorWriter::set_valid()`, `StructWriter::set_valid()`.\nAdded `ReplacementScanInfo::add_parameter_raw()`, `add_i64_parameter()`, `add_bool_parameter()`.\n`table_scan_callback!` now reports panic messages to DuckDB via `duckdb_function_set_error`.\n\n**v0.10.0** (2026-03-29) — Added `StructWriter`, `StructReader`, `ChunkWriter` for batched\ntyped vector I/O. Added `scalar_callback!` / `table_scan_callback!` panic-safe callback\nwrapper macros. Added `Value` integer extraction methods. Added temporal/binary vector\nmethods (date, timestamp, time, blob, uuid). Added `DataChunk` bridge methods.\n\n**v0.9.0** (2026-03-29) — Added `Value` RAII wrapper, `DataChunk` wrapper, `MapVector`\nreader/writer helpers, `VectorWriter::write_str()`, `BindInfo::get_parameter_value()`.\n\n**v0.8.0** (2026-03-28) — Added `LogicalType` introspection (20 methods), complex type\nconstructors (decimal, array, union, enum), `TypeId::from_duckdb_type()`, callback info\nwrappers (`ScalarFunctionInfo`, `AggregateFunctionInfo`, etc.), `ArrayVector` helper.\n\n**v0.7.0** (2026-03-22) — Upgraded to DuckDB 1.5.0. Populated `duckdb-1-5` feature with\nfive new modules: `catalog`, `client_context`, `config_option`, `copy_function`,\n`table_description`. Added `TypeId::TimeNs`, scalar function `varargs()`, `volatile()`,\n`bind()`, `init()`. COPY format handlers now supported.\n\n**v0.6.0** (2026-03-12) — Fixed `InMemoryDb::open()` dispatch table initialization\n(`bundled-test` feature). Added `bundled_api_init.cpp` shim and `build.rs` compilation.\n\n**v0.5.0** (2026-03-10) — Added `param_logical(LogicalType)` and `returns_logical(LogicalType)`\non all builders for complex parameterized types. Added per-overload `null_handling()`.\n\n**v0.4.0** (2026-03-09) — Added `Connection` / `Registrar` trait, `init_extension_v2`,\n`entry_point_v2!`, `duckdb-1-5` feature flag. Broadened `libduckdb-sys` to `\u003e=1.4.4, \u003c2`.\n\n**v0.3.0** (2026-03-08) — Added `TableFunctionBuilder`, `ReplacementScanBuilder`,\n`CastFunctionBuilder`, complex vector types, `DbConfig`, `append_metadata` binary.\n\n**v0.2.0** (2026-03-07) — Added `validate::description_yml`, `prelude`, scaffold improvements,\n`ScalarFunctionBuilder`, `entry_point!` macro.\n\n**v0.1.0** (2025-05-01) — Initial release with all core modules.\n\n---\n\n## Contributing\n\nSee [`CONTRIBUTING.md`](./CONTRIBUTING.md) for the development guide, quality gates,\nand how to run the full test suite.\n\n**Quality gates (all required before merge):**\n\n```shell\ncargo test --all-targets                      # all tests pass\ncargo clippy --all-targets -- -D warnings     # no clippy warnings\ncargo fmt -- --check                          # code is formatted\ncargo doc --no-deps                           # docs compile without warnings\ncargo check                                   # MSRV check (Rust 1.84.1)\n```\n\n---\n\n## License\n\nMIT — see [`LICENSE`](./LICENSE).\n\n---\n\n*`quack-rs` is a community project. It is not affiliated with or endorsed by DuckDB Labs.*\n*Built with care for the open-source and Rust communities.*\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftomtom215%2Fquack-rs","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftomtom215%2Fquack-rs","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftomtom215%2Fquack-rs/lists"}