Skip to main content

quack_rs/
connection.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//! [`Connection`] — version-agnostic extension registration facade.
7//!
8//! [`Connection`] wraps the `duckdb_connection` and `duckdb_database` handles
9//! provided to your extension during initialization. It implements the
10//! [`Registrar`] trait, which provides a uniform API for registering all
11//! extension components that works identically across `DuckDB` 1.4.x and 1.5.x.
12//!
13//! # Obtaining a `Connection`
14//!
15//! Use [`init_extension_v2`][crate::entry_point::init_extension_v2] or the
16//! [`entry_point_v2!`][crate::entry_point_v2] macro. These pass a `&Connection`
17//! to your registration callback instead of the raw `duckdb_connection`.
18//!
19//! ```rust,no_run
20//! use quack_rs::connection::{Connection, Registrar};
21//! use quack_rs::error::ExtensionError;
22//! use quack_rs::scalar::ScalarFunctionBuilder;
23//! use quack_rs::types::TypeId;
24//!
25//! unsafe fn register_all(reg: &impl Registrar) -> Result<(), ExtensionError> {
26//!     let builder = ScalarFunctionBuilder::try_new("my_fn")?
27//!         .returns(TypeId::BigInt);
28//!     unsafe { reg.register_scalar(builder) }
29//! }
30//!
31//! quack_rs::entry_point_v2!(my_extension_init_c_api, |con| {
32//!     unsafe { register_all(con) }
33//! });
34//! ```
35//!
36//! # `DuckDB` version compatibility
37//!
38//! [`Connection`] and [`Registrar`] provide a stable API across `DuckDB` 1.4.x
39//! and 1.5.x. The underlying C API version string (`"v1.2.0"`) is unchanged
40//! across both releases, confirmed by E2E tests against both `DuckDB` 1.4.4 and
41//! `DuckDB` 1.5.0.
42//!
43//! When a future `DuckDB` release changes the C API version or adds new
44//! registration surface, additional methods will be added to [`Connection`]
45//! behind a version-specific feature flag (e.g. `duckdb-1-5`).
46
47use core::ffi::c_void;
48
49use libduckdb_sys::{duckdb_connection, duckdb_database, duckdb_delete_callback_t};
50
51use crate::aggregate::{AggregateFunctionBuilder, AggregateFunctionSetBuilder};
52use crate::cast::CastFunctionBuilder;
53#[cfg(feature = "duckdb-1-5")]
54use crate::copy_function::CopyFunctionBuilder;
55use crate::error::ExtensionError;
56use crate::replacement_scan::{ReplacementScanBuilder, ReplacementScanFn};
57use crate::scalar::{ScalarFunctionBuilder, ScalarFunctionSetBuilder};
58use crate::sql_macro::SqlMacro;
59use crate::table::TableFunctionBuilder;
60
61/// Version-agnostic trait for registering `DuckDB` extension components.
62///
63/// Implemented by [`Connection`]. Writing registration code against this trait
64/// means the same code compiles and runs on `DuckDB` 1.4.x and 1.5.x without
65/// modification.
66///
67/// # Example
68///
69/// ```rust,no_run
70/// use quack_rs::connection::Registrar;
71/// use quack_rs::error::ExtensionError;
72/// use quack_rs::scalar::ScalarFunctionBuilder;
73/// use quack_rs::types::TypeId;
74///
75/// /// Register all functions for this extension.
76/// ///
77/// /// # Safety
78/// ///
79/// /// `reg` must provide a valid `DuckDB` connection for the duration of this call.
80/// unsafe fn register_all(reg: &impl Registrar) -> Result<(), ExtensionError> {
81///     let builder = ScalarFunctionBuilder::try_new("my_fn")?
82///         .returns(TypeId::BigInt);
83///     unsafe { reg.register_scalar(builder) }
84/// }
85/// ```
86pub trait Registrar {
87    /// Register a scalar function.
88    ///
89    /// # Safety
90    ///
91    /// The underlying connection must be valid for the duration of this call.
92    unsafe fn register_scalar(&self, builder: ScalarFunctionBuilder) -> Result<(), ExtensionError>;
93
94    /// Register a scalar function set (multiple overloads under one name).
95    ///
96    /// # Safety
97    ///
98    /// The underlying connection must be valid for the duration of this call.
99    unsafe fn register_scalar_set(
100        &self,
101        builder: ScalarFunctionSetBuilder,
102    ) -> Result<(), ExtensionError>;
103
104    /// Register an aggregate function.
105    ///
106    /// # Safety
107    ///
108    /// The underlying connection must be valid for the duration of this call.
109    unsafe fn register_aggregate(
110        &self,
111        builder: AggregateFunctionBuilder,
112    ) -> Result<(), ExtensionError>;
113
114    /// Register an aggregate function set (multiple overloads under one name).
115    ///
116    /// # Safety
117    ///
118    /// The underlying connection must be valid for the duration of this call.
119    unsafe fn register_aggregate_set(
120        &self,
121        builder: AggregateFunctionSetBuilder,
122    ) -> Result<(), ExtensionError>;
123
124    /// Register a table function.
125    ///
126    /// # Safety
127    ///
128    /// The underlying connection must be valid for the duration of this call.
129    unsafe fn register_table(&self, builder: TableFunctionBuilder) -> Result<(), ExtensionError>;
130
131    /// Register a `SQL` macro (scalar or table-returning).
132    ///
133    /// # Safety
134    ///
135    /// The underlying connection must be valid for the duration of this call.
136    unsafe fn register_sql_macro(&self, sql_macro: SqlMacro) -> Result<(), ExtensionError>;
137
138    /// Register a custom type cast function.
139    ///
140    /// # Safety
141    ///
142    /// The underlying connection must be valid for the duration of this call.
143    unsafe fn register_cast(&self, builder: CastFunctionBuilder) -> Result<(), ExtensionError>;
144
145    /// Register a custom `COPY TO` function (`DuckDB` 1.5.0+).
146    ///
147    /// # Safety
148    ///
149    /// The underlying connection must be valid for the duration of this call.
150    #[cfg(feature = "duckdb-1-5")]
151    unsafe fn register_copy_function(
152        &self,
153        builder: CopyFunctionBuilder,
154    ) -> Result<(), ExtensionError>;
155}
156
157/// Wraps the `duckdb_connection` and `duckdb_database` provided to your
158/// extension at load time.
159///
160/// `Connection` implements [`Registrar`], offering a single, uniform API for
161/// registering all extension components. It also exposes
162/// [`register_replacement_scan`][Self::register_replacement_scan] and
163/// [`register_replacement_scan_with_data`][Self::register_replacement_scan_with_data],
164/// which require the `duckdb_database` handle and therefore cannot be part of
165/// the `Registrar` trait.
166///
167/// # Obtaining a `Connection`
168///
169/// Use [`init_extension_v2`][crate::entry_point::init_extension_v2] (or the
170/// [`entry_point_v2!`][crate::entry_point_v2] macro). Both pass a `&Connection`
171/// to your registration callback.
172///
173/// # Version compatibility
174///
175/// `Connection` provides a uniform API across `DuckDB` 1.4.x and 1.5.x.
176/// When future `DuckDB` releases add new C API surface, additional methods will
177/// be gated on the corresponding feature flag.
178pub struct Connection {
179    con: duckdb_connection,
180    db: duckdb_database,
181}
182
183impl Connection {
184    /// Create a `Connection` from raw `DuckDB` handles.
185    ///
186    /// # Safety
187    ///
188    /// Both `con` and `db` must be valid, non-null handles for the duration of
189    /// the `Connection`'s lifetime. Intended for internal use by
190    /// [`init_extension_v2`][crate::entry_point::init_extension_v2].
191    #[inline]
192    pub(crate) const unsafe fn from_raw(con: duckdb_connection, db: duckdb_database) -> Self {
193        Self { con, db }
194    }
195
196    /// Return the raw `duckdb_connection` handle.
197    ///
198    /// Use this to call C API functions that `quack-rs` does not yet wrap.
199    #[inline]
200    pub const fn as_raw_connection(&self) -> duckdb_connection {
201        self.con
202    }
203
204    /// Return the raw `duckdb_database` handle.
205    ///
206    /// Use this to call C API functions that require the database handle, such
207    /// as replacement scan registration or (with `duckdb-1-5`) config option
208    /// registration.
209    #[inline]
210    pub const fn as_raw_database(&self) -> duckdb_database {
211        self.db
212    }
213
214    /// Register a replacement scan backed by a raw function pointer and extra
215    /// data.
216    ///
217    /// For the ergonomic owned-data variant, see
218    /// [`register_replacement_scan_with_data`][Self::register_replacement_scan_with_data].
219    ///
220    /// # Safety
221    ///
222    /// - The underlying `duckdb_database` must be valid.
223    /// - `extra_data` must remain valid until `delete_callback` is called (or
224    ///   until the database is closed if `delete_callback` is `None`).
225    pub unsafe fn register_replacement_scan(
226        &self,
227        callback: ReplacementScanFn,
228        extra_data: *mut c_void,
229        delete_callback: duckdb_delete_callback_t,
230    ) {
231        // SAFETY: self.db is valid per Connection invariant.
232        unsafe {
233            ReplacementScanBuilder::register(self.db, callback, extra_data, delete_callback);
234        }
235    }
236
237    /// Register a replacement scan with owned extra data.
238    ///
239    /// Boxes `data` and registers a drop destructor automatically. This is the
240    /// safe, ergonomic alternative to
241    /// [`register_replacement_scan`][Self::register_replacement_scan].
242    ///
243    /// # Safety
244    ///
245    /// The underlying `duckdb_database` must be valid.
246    pub unsafe fn register_replacement_scan_with_data<T: 'static>(
247        &self,
248        callback: ReplacementScanFn,
249        data: T,
250    ) {
251        // SAFETY: self.db is valid per Connection invariant.
252        unsafe {
253            ReplacementScanBuilder::register_with_data(self.db, callback, data);
254        }
255    }
256}
257
258impl Registrar for Connection {
259    unsafe fn register_scalar(&self, builder: ScalarFunctionBuilder) -> Result<(), ExtensionError> {
260        // SAFETY: self.con is valid per Connection invariant; caller upholds builder contract.
261        unsafe { builder.register(self.con) }
262    }
263
264    unsafe fn register_scalar_set(
265        &self,
266        builder: ScalarFunctionSetBuilder,
267    ) -> Result<(), ExtensionError> {
268        // SAFETY: self.con is valid per Connection invariant.
269        unsafe { builder.register(self.con) }
270    }
271
272    unsafe fn register_aggregate(
273        &self,
274        builder: AggregateFunctionBuilder,
275    ) -> Result<(), ExtensionError> {
276        // SAFETY: self.con is valid per Connection invariant.
277        unsafe { builder.register(self.con) }
278    }
279
280    unsafe fn register_aggregate_set(
281        &self,
282        builder: AggregateFunctionSetBuilder,
283    ) -> Result<(), ExtensionError> {
284        // SAFETY: self.con is valid per Connection invariant.
285        unsafe { builder.register(self.con) }
286    }
287
288    unsafe fn register_table(&self, builder: TableFunctionBuilder) -> Result<(), ExtensionError> {
289        // SAFETY: self.con is valid per Connection invariant.
290        unsafe { builder.register(self.con) }
291    }
292
293    unsafe fn register_sql_macro(&self, sql_macro: SqlMacro) -> Result<(), ExtensionError> {
294        // SAFETY: self.con is valid per Connection invariant.
295        unsafe { sql_macro.register(self.con) }
296    }
297
298    unsafe fn register_cast(&self, builder: CastFunctionBuilder) -> Result<(), ExtensionError> {
299        // SAFETY: self.con is valid per Connection invariant.
300        unsafe { builder.register(self.con) }
301    }
302
303    #[cfg(feature = "duckdb-1-5")]
304    unsafe fn register_copy_function(
305        &self,
306        builder: CopyFunctionBuilder,
307    ) -> Result<(), ExtensionError> {
308        // SAFETY: self.con is valid per Connection invariant.
309        unsafe { builder.register(self.con) }
310    }
311}