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//! | [`data_chunk`] | Ergonomic wrapper for `DuckDB` data chunks |
48//! | [`entry_point`](mod@entry_point) | Helper for the correct `{name}_init_c_api` C entry point |
49//! | [`connection`] | `Connection` facade + `Registrar` trait for version-agnostic registration |
50//! | [`aggregate`] | Builders for aggregate function registration |
51//! | [`scalar`] | Builder for scalar function registration |
52//! | [`cast`] | Builder for custom type cast functions |
53//! | [`table`] | Builder for table function registration |
54//! | [`replacement_scan`] | `SELECT * FROM 'file.xyz'` replacement scan registration |
55//! | [`sql_macro`] | SQL macro registration (`CREATE MACRO`) — no FFI callbacks |
56//! | [`vector`] | Safe helpers for reading/writing `DuckDB` data vectors |
57//! | [`vector::complex`] | STRUCT / LIST / MAP / ARRAY vector access (child vectors, offsets) |
58//! | [`types`] | `DuckDB` type system wrappers (`TypeId`, `LogicalType`) |
59//! | [`interval`] | `INTERVAL` → microseconds conversion with overflow checking |
60//! | [`error`] | `ExtensionError` for FFI error propagation |
61//! | [`config`] | RAII wrapper for `DuckDB` database configuration |
62//! | [`value`] | RAII wrapper for `DuckDB` values with typed extraction |
63//! | [`validate`] | Community extension compliance validators |
64//! | [`validate::description_yml`] | Parse and validate `description.yml` metadata |
65//! | [`scaffold`] | Project generator for new extensions (no C++ glue needed) |
66//! | [`testing`] | Test harness for aggregate state logic |
67//! | [`prelude`] | Convenience re-exports of the most commonly used items |
68//! | `catalog` | Catalog entry lookup (`duckdb-1-5` feature) |
69//! | `client_context` | Client context access (`duckdb-1-5` feature) |
70//! | `config_option` | Extension-defined configuration options (`duckdb-1-5` feature) |
71//! | `copy_function` | Custom `COPY TO` handlers (`duckdb-1-5` feature) |
72//! | `table_description` | Table metadata queries (`duckdb-1-5` feature) |
73//!
74//! ## Safety
75//!
76//! All `unsafe` code within this SDK is sound and documented. Extension authors
77//! must write `unsafe extern "C"` callback functions (required by `DuckDB`'s C API),
78//! but the SDK's helpers minimize the surface area of unsafe code within those
79//! callbacks. Every `unsafe` block inside this crate has a `// SAFETY:` comment
80//! explaining the invariants being upheld.
81//!
82//! ## Design Principles
83//!
84//! 1. **Thin wrapper**: every abstraction must pay for itself in reduced boilerplate
85//! or improved safety. When in doubt, prefer simplicity.
86//! 2. **No panics across FFI**: `unwrap()` is forbidden in FFI callbacks and entry points.
87//! 3. **Bounded version range**: `libduckdb-sys` uses `>=1.4.4, <2` to support `DuckDB` 1.4.x
88//! and 1.5.x (including v1.5.1) while preventing silent adoption of breaking changes in
89//! future major releases.
90//! 4. **Testable business logic**: state structs have zero FFI dependencies.
91//!
92//! ## Pitfalls
93//!
94//! See [`LESSONS.md`](https://github.com/tomtom215/quack-rs/blob/main/LESSONS.md)
95//! for all 16 known `DuckDB` Rust FFI pitfalls, including symptoms, root causes, and fixes.
96//!
97//! ## Pitfall L1: COMBINE must propagate config fields
98//!
99//! `DuckDB`'s segment tree creates fresh zero-initialized target states via
100//! `state_init`, then calls `combine` to merge source into them. This means
101//! your `combine` callback MUST copy ALL configuration fields from source to
102//! target — not just accumulated data. Any field that defaults to zero will
103//! be wrong at finalize time, producing silently incorrect results.
104//!
105//! See [`aggregate::callbacks::CombineFn`] for details.
106
107#![deny(unsafe_op_in_unsafe_fn)]
108#![warn(missing_docs)]
109
110// DuckDB's C API and duckdb_string_t layout assume 64-bit pointers.
111#[cfg(not(target_pointer_width = "64"))]
112compile_error!("quack-rs requires a 64-bit target (DuckDB does not support 32-bit platforms).");
113
114pub mod aggregate;
115pub mod cast;
116pub mod config;
117pub mod connection;
118pub mod data_chunk;
119pub mod entry_point;
120pub mod error;
121pub mod interval;
122pub mod prelude;
123pub mod replacement_scan;
124pub mod scaffold;
125pub mod scalar;
126pub mod sql_macro;
127pub mod table;
128pub mod testing;
129pub mod types;
130pub mod validate;
131pub mod value;
132pub mod vector;
133
134// DuckDB 1.5.0+ modules — gated behind the `duckdb-1-5` feature flag.
135#[cfg(feature = "duckdb-1-5")]
136pub mod catalog;
137#[cfg(feature = "duckdb-1-5")]
138pub mod client_context;
139#[cfg(feature = "duckdb-1-5")]
140pub mod config_option;
141#[cfg(feature = "duckdb-1-5")]
142pub mod copy_function;
143#[cfg(feature = "duckdb-1-5")]
144pub mod table_description;
145
146/// The `DuckDB` C API version string required by [`duckdb_rs_extension_api_init`][libduckdb_sys::duckdb_rs_extension_api_init].
147///
148/// This constant corresponds to `DuckDB` releases v1.4.x, v1.5.0, and v1.5.1.
149/// The C API version did **not** change between v1.5.0 and v1.5.1. If you are
150/// targeting a different `DuckDB` release, consult the `DuckDB` changelog for the
151/// C API version.
152///
153/// # Pitfall P2: C API version ≠ `DuckDB` release version
154///
155/// The `-dv` flag passed to `append_extension_metadata.py` must be this value
156/// (`"v1.2.0"`), **not** the `DuckDB` release version (`"v1.4.4"` / `"v1.5.0"` /
157/// `"v1.5.1"`). Using the wrong value causes the metadata script to fail silently
158/// or produce incorrect metadata.
159///
160/// See `LESSONS.md` → Pitfall P2 for full details.
161pub const DUCKDB_API_VERSION: &str = "v1.2.0";