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}