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}