Skip to main content

quack_rs/
lib.rs

1// SPDX-License-Identifier: MIT
2// Copyright 2026 Tom F. <https://github.com/tomtom215/>
3// My way of giving something small back to the open source community
4// and encouraging more Rust development!
5
6//! # quack-rs
7//!
8//! A production-grade Rust SDK for building `DuckDB` loadable extensions.
9//!
10//! ## Overview
11//!
12//! `quack-rs` encapsulates the hard-won FFI knowledge required to build `DuckDB`
13//! community extensions in Rust. It provides:
14//!
15//! - A correct, panic-free entry point helper via the [`entry_point`](mod@entry_point) module
16//! - Type-safe builders for registering aggregate functions ([`aggregate`])
17//! - Safe vector reading and writing helpers ([`vector`])
18//! - A generic [`FfiState<T>`][aggregate::state::FfiState] that eliminates raw pointer management
19//! - Documented solutions to every known `DuckDB` Rust FFI pitfall
20//!
21//! ## Quick Start
22//!
23//! ```rust,no_run
24//! // In your extension's src/lib.rs, write the entry point manually:
25//! use quack_rs::entry_point::init_extension;
26//!
27//! #[no_mangle]
28//! pub unsafe extern "C" fn my_extension_init_c_api(
29//!     info: libduckdb_sys::duckdb_extension_info,
30//!     access: *const libduckdb_sys::duckdb_extension_access,
31//! ) -> bool {
32//!     unsafe {
33//!         init_extension(info, access, quack_rs::DUCKDB_API_VERSION, |con| {
34//!             // register_my_function(con)?;
35//!             Ok(())
36//!         })
37//!     }
38//! }
39//! ```
40//!
41//! ## Architecture
42//!
43//! The SDK is organized into focused modules:
44//!
45//! | Module | Purpose |
46//! |--------|---------|
47//! | [`callback`] | Safe `extern "C"` callback wrapper macros (`scalar_callback!`, `table_scan_callback!`) |
48//! | [`chunk_writer`] | Auto-sizing chunk writer for table scan callbacks (auto `set_size` on drop) |
49//! | [`data_chunk`] | Ergonomic wrapper for `DuckDB` data chunks |
50//! | [`entry_point`](mod@entry_point) | Helper for the correct `{name}_init_c_api` C entry point |
51//! | [`connection`] | `Connection` facade + `Registrar` trait for version-agnostic registration |
52//! | [`aggregate`] | Builders for aggregate function registration |
53//! | [`scalar`] | Builder for scalar function registration |
54//! | [`cast`] | Builder for custom type cast functions |
55//! | [`table`] | Builder for table function registration |
56//! | [`replacement_scan`] | `SELECT * FROM 'file.xyz'` replacement scan registration |
57//! | [`sql_macro`] | SQL macro registration (`CREATE MACRO`) — no FFI callbacks |
58//! | [`vector`] | Safe helpers for reading/writing `DuckDB` data vectors |
59//! | [`vector::complex`] | STRUCT / LIST / MAP / ARRAY vector access (child vectors, offsets) |
60//! | [`vector::struct_reader`] | Batched [`StructReader`][vector::StructReader] for STRUCT input vectors |
61//! | [`vector::struct_writer`] | Batched [`StructWriter`][vector::StructWriter] for STRUCT output vectors |
62//! | [`types`] | `DuckDB` type system wrappers (`TypeId`, `LogicalType`) |
63//! | [`interval`] | `INTERVAL` → microseconds conversion with overflow checking |
64//! | [`error`] | `ExtensionError` for FFI error propagation |
65//! | [`config`] | RAII wrapper for `DuckDB` database configuration |
66//! | [`value`] | RAII wrapper for `DuckDB` values with typed extraction |
67//! | [`validate`] | Community extension compliance validators |
68//! | [`validate::description_yml`] | Parse and validate `description.yml` metadata |
69//! | [`scaffold`] | Project generator for new extensions (no C++ glue needed) |
70//! | [`testing`] | Test harness for aggregate state logic |
71//! | [`prelude`] | Convenience re-exports of the most commonly used items |
72//! | `catalog` | Catalog entry lookup (`duckdb-1-5` feature) |
73//! | `client_context` | Client context access (`duckdb-1-5` feature) |
74//! | `config_option` | Extension-defined configuration options (`duckdb-1-5` feature) |
75//! | `copy_function` | Custom `COPY TO` handlers (`duckdb-1-5` feature) |
76//! | `table_description` | Table metadata queries (`duckdb-1-5` feature) |
77//!
78//! ## Safety
79//!
80//! All `unsafe` code within this SDK is sound and documented. Extension authors
81//! must write `unsafe extern "C"` callback functions (required by `DuckDB`'s C API),
82//! but the SDK's helpers minimize the surface area of unsafe code within those
83//! callbacks. Every `unsafe` block inside this crate has a `// SAFETY:` comment
84//! explaining the invariants being upheld.
85//!
86//! ## Design Principles
87//!
88//! 1. **Thin wrapper**: every abstraction must pay for itself in reduced boilerplate
89//!    or improved safety. When in doubt, prefer simplicity.
90//! 2. **No panics across FFI**: `unwrap()` is forbidden in FFI callbacks and entry points.
91//! 3. **Bounded version range**: `libduckdb-sys` uses `>=1.4.4, <2` to support `DuckDB` 1.4.x
92//!    and 1.5.x (including v1.5.1) while preventing silent adoption of breaking changes in
93//!    future major releases.
94//! 4. **Testable business logic**: state structs have zero FFI dependencies.
95//!
96//! ## Pitfalls
97//!
98//! See [`LESSONS.md`](https://github.com/tomtom215/quack-rs/blob/main/LESSONS.md)
99//! for all 16 known `DuckDB` Rust FFI pitfalls, including symptoms, root causes, and fixes.
100//!
101//! ## Pitfall L1: COMBINE must propagate config fields
102//!
103//! `DuckDB`'s segment tree creates fresh zero-initialized target states via
104//! `state_init`, then calls `combine` to merge source into them. This means
105//! your `combine` callback MUST copy ALL configuration fields from source to
106//! target — not just accumulated data. Any field that defaults to zero will
107//! be wrong at finalize time, producing silently incorrect results.
108//!
109//! See [`aggregate::callbacks::CombineFn`] for details.
110
111#![deny(unsafe_op_in_unsafe_fn)]
112#![warn(missing_docs)]
113
114// DuckDB's C API and duckdb_string_t layout assume 64-bit pointers.
115#[cfg(not(target_pointer_width = "64"))]
116compile_error!("quack-rs requires a 64-bit target (DuckDB does not support 32-bit platforms).");
117
118pub mod aggregate;
119pub mod callback;
120pub mod cast;
121pub mod chunk_writer;
122pub mod config;
123pub mod connection;
124pub mod data_chunk;
125pub mod entry_point;
126pub mod error;
127pub mod interval;
128pub mod prelude;
129pub mod replacement_scan;
130pub mod scaffold;
131pub mod scalar;
132pub mod sql_macro;
133pub mod table;
134pub mod testing;
135pub mod types;
136pub mod validate;
137pub mod value;
138pub mod vector;
139
140// DuckDB 1.5.0+ modules — gated behind the `duckdb-1-5` feature flag.
141#[cfg(feature = "duckdb-1-5")]
142pub mod catalog;
143#[cfg(feature = "duckdb-1-5")]
144pub mod client_context;
145#[cfg(feature = "duckdb-1-5")]
146pub mod config_option;
147#[cfg(feature = "duckdb-1-5")]
148pub mod copy_function;
149#[cfg(feature = "duckdb-1-5")]
150pub mod table_description;
151
152/// The `DuckDB` C API version string required by [`duckdb_rs_extension_api_init`][libduckdb_sys::duckdb_rs_extension_api_init].
153///
154/// This constant corresponds to `DuckDB` releases v1.4.x, v1.5.0, and v1.5.1.
155/// The C API version did **not** change between v1.5.0 and v1.5.1. If you are
156/// targeting a different `DuckDB` release, consult the `DuckDB` changelog for the
157/// C API version.
158///
159/// # Pitfall P2: C API version ≠ `DuckDB` release version
160///
161/// The `-dv` flag passed to `append_extension_metadata.py` must be this value
162/// (`"v1.2.0"`), **not** the `DuckDB` release version (`"v1.4.4"` / `"v1.5.0"` /
163/// `"v1.5.1"`). Using the wrong value causes the metadata script to fail silently
164/// or produce incorrect metadata.
165///
166/// See `LESSONS.md` → Pitfall P2 for full details.
167pub const DUCKDB_API_VERSION: &str = "v1.2.0";