Skip to main content

better_duck_core/udf/scalar/
function.rs

1//! RAII builders around the `duckdb_scalar_function`/`duckdb_scalar_function_set` C API.
2
3use std::ffi::{c_void, CStr};
4
5use crate::{
6    error::{Error, Result},
7    ffi::{
8        duckdb_add_scalar_function_to_set, duckdb_bind_info, duckdb_client_context,
9        duckdb_connection, duckdb_create_scalar_function, duckdb_create_scalar_function_set,
10        duckdb_destroy_scalar_function, duckdb_destroy_scalar_function_set, duckdb_function_info,
11        duckdb_register_scalar_function_set, duckdb_scalar_function,
12        duckdb_scalar_function_add_parameter, duckdb_scalar_function_bind_get_argument,
13        duckdb_scalar_function_bind_get_argument_count, duckdb_scalar_function_bind_get_extra_info,
14        duckdb_scalar_function_bind_set_error, duckdb_scalar_function_bind_t,
15        duckdb_scalar_function_get_bind_data, duckdb_scalar_function_get_client_context,
16        duckdb_scalar_function_get_extra_info, duckdb_scalar_function_set,
17        duckdb_scalar_function_set_bind, duckdb_scalar_function_set_bind_data,
18        duckdb_scalar_function_set_error, duckdb_scalar_function_set_extra_info,
19        duckdb_scalar_function_set_function, duckdb_scalar_function_set_name,
20        duckdb_scalar_function_set_return_type, duckdb_scalar_function_set_special_handling,
21        duckdb_scalar_function_set_varargs, duckdb_scalar_function_set_volatile,
22        duckdb_scalar_function_t, idx_t,
23    },
24    raw::{client_context::ClientContext, expression::Expression},
25};
26
27use super::super::{
28    callback::{drop_boxed, CallbackErrorSink},
29    LogicalType,
30};
31
32/// An in-progress scalar function overload, built up via its setters and then
33/// added to a [`ScalarFunctionSet`].
34pub(crate) struct ScalarFunction {
35    ptr: duckdb_scalar_function,
36}
37
38impl ScalarFunction {
39    pub(crate) fn new(name: &CStr) -> Self {
40        // SAFETY: always safe to call; returns a freshly allocated, empty function.
41        let ptr = unsafe { duckdb_create_scalar_function() };
42        // SAFETY: `ptr` was just allocated above and is non-null (DuckDB does not
43        // document a null return for this constructor).
44        unsafe { duckdb_scalar_function_set_name(ptr, name.as_ptr()) };
45        Self { ptr }
46    }
47
48    pub(crate) fn add_parameter(
49        &self,
50        ty: &LogicalType,
51    ) {
52        // SAFETY: `self.ptr` is valid; `ty.as_raw()` is a valid logical type owned
53        // by `ty` for the duration of this call. This function copies it.
54        unsafe { duckdb_scalar_function_add_parameter(self.ptr, ty.as_raw()) };
55    }
56
57    pub(crate) fn set_varargs(
58        &self,
59        ty: &LogicalType,
60    ) {
61        // SAFETY: `self.ptr` is valid; `ty.as_raw()` is a valid logical type owned
62        // by `ty` for the duration of this call. This function copies it.
63        unsafe { duckdb_scalar_function_set_varargs(self.ptr, ty.as_raw()) };
64    }
65
66    pub(crate) fn set_return_type(
67        &self,
68        ty: &LogicalType,
69    ) {
70        // SAFETY: `self.ptr` is valid; `ty.as_raw()` is a valid logical type owned
71        // by `ty` for the duration of this call. This function copies it.
72        unsafe { duckdb_scalar_function_set_return_type(self.ptr, ty.as_raw()) };
73    }
74
75    pub(crate) fn set_volatile(&self) {
76        // SAFETY: `self.ptr` is valid.
77        unsafe { duckdb_scalar_function_set_volatile(self.ptr) };
78    }
79
80    pub(crate) fn set_special_handling(&self) {
81        // SAFETY: `self.ptr` is valid.
82        unsafe { duckdb_scalar_function_set_special_handling(self.ptr) };
83    }
84
85    /// Sets the function's execution callback.
86    pub(crate) fn set_function(
87        &self,
88        f: duckdb_scalar_function_t,
89    ) {
90        // SAFETY: `self.ptr` is valid; `f`, if `Some`, is a valid
91        // `extern "C"` function pointer with the expected signature.
92        unsafe { duckdb_scalar_function_set_function(self.ptr, f) };
93    }
94
95    /// Sets the function's bind callback, invoked once per query that references
96    /// the function to inspect argument expressions and produce per-query bind data.
97    pub(crate) fn set_bind(
98        &self,
99        f: duckdb_scalar_function_bind_t,
100    ) {
101        // SAFETY: `self.ptr` is valid; `f`, if `Some`, is a valid `extern "C"`
102        // function pointer with the expected bind signature.
103        unsafe { duckdb_scalar_function_set_bind(self.ptr, f) };
104    }
105
106    /// Stores `state`, retrievable inside the execution callback via
107    /// [`ScalarFunctionInfo::state`]. Freed automatically when DuckDB drops the
108    /// catalog entry.
109    pub(crate) fn set_extra_info<T: Send + Sync + 'static>(
110        &self,
111        state: T,
112    ) {
113        let ptr = Box::into_raw(Box::new(state)).cast::<c_void>();
114        // SAFETY: `ptr` was just created by `Box::into_raw::<T>` above;
115        // `drop_boxed::<T>` frees it with the matching type, exactly once, when
116        // DuckDB calls the delete callback.
117        unsafe { duckdb_scalar_function_set_extra_info(self.ptr, ptr, Some(drop_boxed::<T>)) };
118    }
119
120    pub(crate) fn as_raw(&self) -> duckdb_scalar_function {
121        self.ptr
122    }
123}
124
125impl Drop for ScalarFunction {
126    fn drop(&mut self) {
127        if !self.ptr.is_null() {
128            // SAFETY: `self.ptr` is a valid, non-null scalar function allocated by
129            // `duckdb_create_scalar_function` and not yet destroyed; destroyed
130            // exactly once here. Adding it to a set (`duckdb_add_scalar_function_to_set`)
131            // copies it, so destroying our own handle afterward is correct.
132            unsafe { duckdb_destroy_scalar_function(&mut self.ptr) };
133        }
134    }
135}
136
137/// A named collection of scalar function overloads, registered together.
138pub(crate) struct ScalarFunctionSet {
139    ptr: duckdb_scalar_function_set,
140}
141
142impl ScalarFunctionSet {
143    pub(crate) fn new(name: &CStr) -> Self {
144        // SAFETY: `name` is a valid, NUL-terminated C string for the duration of
145        // this call.
146        let ptr = unsafe { duckdb_create_scalar_function_set(name.as_ptr()) };
147        Self { ptr }
148    }
149
150    /// Adds `function` as a new overload.
151    ///
152    /// # Errors
153    ///
154    /// Returns an error if the overload conflicts with one already in the set.
155    pub(crate) fn add_function(
156        &self,
157        function: &ScalarFunction,
158    ) -> Result<()> {
159        // SAFETY: `self.ptr` and `function.as_raw()` are both valid; this
160        // function copies the scalar function into the set.
161        let rc = unsafe { duckdb_add_scalar_function_to_set(self.ptr, function.as_raw()) };
162        if rc != crate::ffi::DuckDBSuccess {
163            return Err(Error::DuckDBFailure(
164                crate::ffi::Error::new(rc),
165                Some(
166                    "failed to add overload to scalar function set (conflicting signature?)"
167                        .to_owned(),
168                ),
169            ));
170        }
171        Ok(())
172    }
173
174    /// Registers this set with `con`.
175    ///
176    /// # Errors
177    ///
178    /// Returns an error if registration fails, e.g. a name conflict.
179    pub(crate) fn register(
180        &self,
181        con: duckdb_connection,
182        name: &str,
183    ) -> Result<()> {
184        // SAFETY: `con` is a valid connection handle; `self.ptr` is a valid,
185        // non-empty function set (the caller adds overloads before calling this).
186        let rc = unsafe { duckdb_register_scalar_function_set(con, self.ptr) };
187        if rc != crate::ffi::DuckDBSuccess {
188            return Err(Error::DuckDBFailure(
189                crate::ffi::Error::new(rc),
190                Some(format!(
191                    "failed to register scalar function `{name}` (name conflict, or invalid signature)"
192                )),
193            ));
194        }
195        Ok(())
196    }
197}
198
199impl Drop for ScalarFunctionSet {
200    fn drop(&mut self) {
201        if !self.ptr.is_null() {
202            // SAFETY: `self.ptr` is a valid, non-null function set allocated by
203            // `duckdb_create_scalar_function_set` and not yet destroyed; destroyed
204            // exactly once here.
205            unsafe { duckdb_destroy_scalar_function_set(&mut self.ptr) };
206        }
207    }
208}
209
210/// The execution-time handle passed to [`super::VScalar::invoke`]'s trampoline,
211/// used to retrieve `State` and to report an error back to DuckDB.
212pub(crate) struct ScalarFunctionInfo {
213    ptr: duckdb_function_info,
214}
215
216impl ScalarFunctionInfo {
217    pub(crate) fn from(ptr: duckdb_function_info) -> Self {
218        Self { ptr }
219    }
220
221    /// Retrieves the state stored via [`ScalarFunction::set_extra_info`].
222    ///
223    /// # Safety
224    ///
225    /// `T` must be the same type that was passed to `set_extra_info` when this
226    /// function was registered.
227    pub(crate) unsafe fn state<T>(&self) -> &T {
228        // SAFETY: `self.ptr` is valid for the duration of the callback; the
229        // caller guarantees `T` matches the type stored at registration time,
230        // and that stored value outlives every invocation (DuckDB frees it only
231        // when the catalog entry itself is dropped).
232        let raw = unsafe { duckdb_scalar_function_get_extra_info(self.ptr) };
233        // SAFETY: `raw` was produced by `Box::into_raw::<T>` in `set_extra_info`
234        // and has not been freed (guaranteed by the caller per the above).
235        unsafe { &*raw.cast::<T>() }
236    }
237
238    /// Retrieves the per-query bind data stored via
239    /// [`ScalarBindInfo::set_bind_data`].
240    ///
241    /// # Safety
242    ///
243    /// `T` must be the same type the function's bind callback stored, and the bind
244    /// callback must have run for this query (DuckDB guarantees bind precedes
245    /// execution).
246    pub(crate) unsafe fn bind_data<T>(&self) -> &T {
247        // SAFETY: `self.ptr` is valid for the callback; bind ran first and stored a
248        // `Box<T>`, which DuckDB keeps alive for every invocation of this query.
249        let raw = unsafe { duckdb_scalar_function_get_bind_data(self.ptr) };
250        // SAFETY: `raw` came from `Box::into_raw::<T>` in `set_bind_data` and is live.
251        unsafe { &*raw.cast::<T>() }
252    }
253}
254
255/// The bind-time handle passed to [`super::VScalar::bind`]: reads the call's
256/// argument expressions and stores per-query bind data.
257pub struct ScalarBindInfo {
258    ptr: duckdb_bind_info,
259}
260
261impl ScalarBindInfo {
262    pub(crate) fn from(ptr: duckdb_bind_info) -> Self {
263        Self { ptr }
264    }
265
266    /// The number of argument expressions passed to this call.
267    #[must_use]
268    pub fn argument_count(&self) -> u64 {
269        // SAFETY: `self.ptr` is a valid bind info for the duration of the callback.
270        unsafe { duckdb_scalar_function_bind_get_argument_count(self.ptr) as u64 }
271    }
272
273    /// The argument [`Expression`] at `index`, or `None` if out of range.
274    #[must_use]
275    pub fn argument(
276        &self,
277        index: u64,
278    ) -> Option<Expression> {
279        // SAFETY: `self.ptr` is valid; the returned expression is owned (destroy
280        // once) and wrapped in RAII. Out-of-range → null → None.
281        unsafe {
282            Expression::from_raw(duckdb_scalar_function_bind_get_argument(self.ptr, index as idx_t))
283        }
284    }
285
286    /// The registration-time state stored via `set_extra_info`, readable at bind
287    /// time (the same value `ScalarFunctionInfo::state` exposes during execution).
288    ///
289    /// # Safety
290    ///
291    /// `T` must match the type stored at registration time (the function's
292    /// `VScalar::State`).
293    pub unsafe fn extra_info<T>(&self) -> &T {
294        // SAFETY: `self.ptr` is valid for the callback; the caller guarantees `T`
295        // matches the registered type, which outlives the bind call.
296        let raw = unsafe { duckdb_scalar_function_bind_get_extra_info(self.ptr) };
297        // SAFETY: `raw` came from `Box::into_raw::<T>` in `set_extra_info`.
298        unsafe { &*raw.cast::<T>() }
299    }
300
301    /// The client context of the connection binding this call, for folding
302    /// constant argument expressions ([`Expression::fold`]). `None` if unavailable.
303    #[must_use]
304    pub fn client_context(&self) -> Option<ClientContext<'_>> {
305        let mut ctx: duckdb_client_context = std::ptr::null_mut();
306        // SAFETY: `self.ptr` is valid; `ctx` is a valid out-pointer DuckDB writes an
307        // owned client-context handle into.
308        unsafe { duckdb_scalar_function_get_client_context(self.ptr, &mut ctx) };
309        // SAFETY: `ctx` is null or an owned handle whose owner (this bind call)
310        // outlives the returned borrow.
311        unsafe { ClientContext::from_raw(ctx) }
312    }
313
314    /// Stores per-query bind data, retrievable in the execution callback via
315    /// [`ScalarFunctionInfo::bind_data`]. Freed automatically when DuckDB drops the
316    /// query's bind data.
317    pub(crate) fn set_bind_data<T: Send + Sync + 'static>(
318        &self,
319        data: T,
320    ) {
321        let ptr = Box::into_raw(Box::new(data)).cast::<c_void>();
322        // SAFETY: `self.ptr` is valid; `ptr` was just created by `Box::into_raw::<T>`,
323        // and `drop_boxed::<T>` frees it with the matching type exactly once when
324        // DuckDB drops the bind data. Bind data is read-only and shared, so no copy
325        // callback (`duckdb_scalar_function_set_bind_data_copy`) is needed.
326        unsafe { duckdb_scalar_function_set_bind_data(self.ptr, ptr, Some(drop_boxed::<T>)) };
327    }
328}
329
330impl CallbackErrorSink for ScalarBindInfo {
331    fn set_c_error(
332        &self,
333        error: &CStr,
334    ) {
335        // SAFETY: `self.ptr` is a valid bind-info handle for the duration of the
336        // callback; `error` is a valid, NUL-terminated C string.
337        unsafe { duckdb_scalar_function_bind_set_error(self.ptr, error.as_ptr()) };
338    }
339}
340
341impl CallbackErrorSink for ScalarFunctionInfo {
342    fn set_c_error(
343        &self,
344        error: &CStr,
345    ) {
346        // SAFETY: `self.ptr` is a valid function-info handle for the duration of
347        // the callback; `error` is a valid, NUL-terminated C string.
348        unsafe { duckdb_scalar_function_set_error(self.ptr, error.as_ptr()) };
349    }
350}