Skip to main content

better_duck_core/udf/table/
function.rs

1//! RAII builders and callback-info wrappers around the `duckdb_table_function`/
2//! `duckdb_bind_info`/`duckdb_init_info`/`duckdb_function_info` C API.
3
4use std::{
5    ffi::{CStr, CString},
6    marker::PhantomData,
7};
8
9use crate::{
10    error::{Error, Result},
11    ffi::{
12        duckdb_bind_add_result_column, duckdb_bind_get_extra_info, duckdb_bind_get_named_parameter,
13        duckdb_bind_get_parameter, duckdb_bind_get_parameter_count, duckdb_bind_info,
14        duckdb_bind_set_bind_data, duckdb_bind_set_cardinality, duckdb_bind_set_error,
15        duckdb_connection, duckdb_create_table_function, duckdb_destroy_table_function,
16        duckdb_destroy_value, duckdb_function_get_bind_data, duckdb_function_get_extra_info,
17        duckdb_function_get_init_data, duckdb_function_get_local_init_data, duckdb_function_info,
18        duckdb_function_set_error, duckdb_init_get_bind_data, duckdb_init_get_column_count,
19        duckdb_init_get_column_index, duckdb_init_get_extra_info, duckdb_init_info,
20        duckdb_init_set_error, duckdb_init_set_init_data, duckdb_init_set_max_threads,
21        duckdb_register_table_function, duckdb_table_function,
22        duckdb_table_function_add_named_parameter, duckdb_table_function_add_parameter,
23        duckdb_table_function_bind_t, duckdb_table_function_get_client_context,
24        duckdb_table_function_init_t, duckdb_table_function_set_bind,
25        duckdb_table_function_set_extra_info, duckdb_table_function_set_function,
26        duckdb_table_function_set_init, duckdb_table_function_set_local_init,
27        duckdb_table_function_set_name, duckdb_table_function_supports_projection_pushdown,
28        duckdb_table_function_t,
29    },
30    types::DuckDialect,
31};
32
33use super::super::{
34    callback::{drop_boxed, CallbackErrorSink},
35    LogicalType,
36};
37use super::VTab;
38
39/// An in-progress table function, built up via its setters and registered.
40pub(crate) struct TableFunction {
41    ptr: duckdb_table_function,
42}
43
44impl TableFunction {
45    pub(crate) fn new(name: &CStr) -> Self {
46        // SAFETY: always safe to call; returns a freshly allocated, empty function.
47        let ptr = unsafe { duckdb_create_table_function() };
48        // SAFETY: `ptr` was just allocated above.
49        unsafe { duckdb_table_function_set_name(ptr, name.as_ptr()) };
50        Self { ptr }
51    }
52
53    pub(crate) fn add_parameter(
54        &self,
55        ty: &LogicalType,
56    ) {
57        // SAFETY: `self.ptr` is valid; `ty.as_raw()` is a valid logical type owned
58        // by `ty` for the duration of this call, which copies it.
59        unsafe { duckdb_table_function_add_parameter(self.ptr, ty.as_raw()) };
60    }
61
62    pub(crate) fn set_bind(
63        &self,
64        f: duckdb_table_function_bind_t,
65    ) {
66        // SAFETY: `self.ptr` is valid; `f`, if `Some`, is a valid `extern "C"`
67        // function pointer with the expected signature.
68        unsafe { duckdb_table_function_set_bind(self.ptr, f) };
69    }
70
71    pub(crate) fn set_init(
72        &self,
73        f: duckdb_table_function_init_t,
74    ) {
75        // SAFETY: `self.ptr` is valid; `f`, if `Some`, is a valid `extern "C"`
76        // function pointer with the expected signature.
77        unsafe { duckdb_table_function_set_init(self.ptr, f) };
78    }
79
80    pub(crate) fn set_function(
81        &self,
82        f: duckdb_table_function_t,
83    ) {
84        // SAFETY: `self.ptr` is valid; `f`, if `Some`, is a valid `extern "C"`
85        // function pointer with the expected signature.
86        unsafe { duckdb_table_function_set_function(self.ptr, f) };
87    }
88
89    /// Registers the thread-local ("local") init callback, invoked once per
90    /// worker thread that executes this table function, in addition to the
91    /// single global [`set_init`](Self::set_init). Its result is retrievable
92    /// via [`TableFunctionInfo::local_init_data`] from that same thread.
93    pub(crate) fn set_local_init(
94        &self,
95        f: duckdb_table_function_init_t,
96    ) {
97        // SAFETY: `self.ptr` is valid; `f`, if `Some`, is a valid `extern "C"`
98        // function pointer with the expected signature (the C API reuses
99        // `duckdb_table_function_init_t` for both the global and local init
100        // callbacks).
101        unsafe { duckdb_table_function_set_local_init(self.ptr, f) };
102    }
103
104    /// Declares a named (keyword) parameter in addition to the positional ones
105    /// added via [`add_parameter`](Self::add_parameter).
106    ///
107    /// # Errors
108    ///
109    /// Returns an error if `name` contains a NUL byte.
110    pub(crate) fn add_named_parameter(
111        &self,
112        name: &str,
113        ty: &LogicalType,
114    ) -> Result<()> {
115        let c_name = CString::new(name)?;
116        // SAFETY: `self.ptr` is valid; `c_name` is a valid NUL-terminated C
117        // string for the duration of this call; `ty.as_raw()` is valid and
118        // copied by this function.
119        unsafe {
120            duckdb_table_function_add_named_parameter(self.ptr, c_name.as_ptr(), ty.as_raw())
121        };
122        Ok(())
123    }
124
125    /// Declares whether this function can consume a projected column list
126    /// (i.e. honors [`InitInfo::column_indices`] to skip writing columns the
127    /// query doesn't need). Defaults to `false`.
128    pub(crate) fn set_supports_projection_pushdown(
129        &self,
130        supports: bool,
131    ) {
132        // SAFETY: `self.ptr` is valid.
133        unsafe { duckdb_table_function_supports_projection_pushdown(self.ptr, supports) };
134    }
135
136    /// Stores `info`, retrievable via [`BindInfo::extra_info`]/
137    /// [`InitInfo::extra_info`]/[`TableFunctionInfo::extra_info`]. Freed
138    /// automatically when DuckDB drops the catalog entry.
139    pub(crate) fn set_extra_info<T: Send + Sync + 'static>(
140        &self,
141        info: T,
142    ) {
143        let ptr = Box::into_raw(Box::new(info)).cast::<std::ffi::c_void>();
144        // SAFETY: `ptr` was just created by `Box::into_raw::<T>` above;
145        // `drop_boxed::<T>` frees it with the matching type, exactly once, when
146        // DuckDB calls the delete callback.
147        unsafe { duckdb_table_function_set_extra_info(self.ptr, ptr, Some(drop_boxed::<T>)) };
148    }
149
150    /// Registers this function with `con`.
151    ///
152    /// # Errors
153    ///
154    /// Returns an error if registration fails, e.g. a name conflict, or if the
155    /// function is missing a required piece (bind/init/function callback).
156    pub(crate) fn register(
157        &self,
158        con: duckdb_connection,
159        name: &str,
160    ) -> Result<()> {
161        // SAFETY: `con` is a valid connection handle; `self.ptr` is a valid,
162        // fully configured table function.
163        let rc = unsafe { duckdb_register_table_function(con, self.ptr) };
164        if rc != crate::ffi::DuckDBSuccess {
165            return Err(Error::DuckDBFailure(
166                crate::ffi::Error::new(rc),
167                Some(format!(
168                    "failed to register table function `{name}` (name conflict, or invalid signature)"
169                )),
170            ));
171        }
172        Ok(())
173    }
174}
175
176impl Drop for TableFunction {
177    fn drop(&mut self) {
178        if !self.ptr.is_null() {
179            // SAFETY: `self.ptr` is a valid, non-null table function allocated by
180            // `duckdb_create_table_function` and not yet destroyed; destroyed
181            // exactly once here. `duckdb_register_table_function` copies it, so
182            // destroying our own handle afterward is correct.
183            unsafe { duckdb_destroy_table_function(&mut self.ptr) };
184        }
185    }
186}
187
188/// An interface to store and retrieve data during the table function's bind stage.
189pub struct BindInfo {
190    ptr: duckdb_bind_info,
191}
192
193impl BindInfo {
194    pub(crate) fn from(ptr: duckdb_bind_info) -> Self {
195        Self { ptr }
196    }
197
198    /// Adds a result column to the output schema of the table function.
199    ///
200    /// # Errors
201    ///
202    /// Returns an error if `column_name` contains a NUL byte.
203    pub fn add_result_column(
204        &self,
205        column_name: &str,
206        column_type: &LogicalType,
207    ) -> Result<()> {
208        let c_name = CString::new(column_name)?;
209        // SAFETY: `self.ptr` is valid; `c_name` is a valid, NUL-terminated C
210        // string for the duration of this call; `column_type.as_raw()` is valid
211        // and copied by this function.
212        unsafe { duckdb_bind_add_result_column(self.ptr, c_name.as_ptr(), column_type.as_raw()) };
213        Ok(())
214    }
215
216    /// The number of positional parameters passed to this call of the function.
217    pub fn parameter_count(&self) -> u64 {
218        // SAFETY: `self.ptr` is valid.
219        unsafe { duckdb_bind_get_parameter_count(self.ptr) }
220    }
221
222    /// The [`ClientContext`](crate::ClientContext) of the connection executing this bind, exposing its
223    /// stable connection id. The returned context borrows `self`, so it cannot
224    /// outlive the bind call. Returns `None` if no context is available.
225    #[must_use]
226    pub fn client_context(&self) -> Option<crate::raw::client_context::ClientContext<'_>> {
227        let mut ctx: crate::ffi::duckdb_client_context = std::ptr::null_mut();
228        // SAFETY: `self.ptr` is a valid bind info; `ctx` is a valid out-pointer DuckDB
229        // writes an owned client-context handle into.
230        unsafe { duckdb_table_function_get_client_context(self.ptr, &mut ctx) };
231        // SAFETY: `ctx` is null or an owned handle whose owner (this bind call)
232        // outlives the returned borrow.
233        unsafe { crate::raw::client_context::ClientContext::from_raw(ctx) }
234    }
235
236    /// Reads the positional parameter at `index` as `T`.
237    ///
238    /// # Errors
239    ///
240    /// Returns an error if `index` is out of range or the parameter cannot be
241    /// converted to `T`.
242    pub fn get_parameter<T: DuckDialect>(
243        &self,
244        index: u64,
245    ) -> Result<T> {
246        // SAFETY: `self.ptr` is valid; `index` is caller-guaranteed to be within
247        // `[0, parameter_count())`. The returned value is owned by us and
248        // destroyed below.
249        let mut value = unsafe { duckdb_bind_get_parameter(self.ptr, index) };
250        if value.is_null() {
251            return Err(Error::InvalidColumnIndex(index as usize));
252        }
253        let result = T::from_duck(value).map_err(Error::ConversionError);
254        // SAFETY: `value` was allocated by `duckdb_bind_get_parameter` above and
255        // is destroyed exactly once here.
256        unsafe { duckdb_destroy_value(&mut value) };
257        result
258    }
259
260    /// Reads the named (keyword) parameter `name` as `T`.
261    ///
262    /// Unlike [`get_parameter`](Self::get_parameter), a missing named
263    /// parameter is not an error — named parameters are optional by nature —
264    /// so this returns `Ok(None)` when `name` wasn't passed by the caller.
265    ///
266    /// # Errors
267    ///
268    /// Returns an error if `name` contains a NUL byte, or if the parameter was
269    /// provided but cannot be converted to `T`.
270    pub fn get_named_parameter<T: DuckDialect>(
271        &self,
272        name: &str,
273    ) -> Result<Option<T>> {
274        let c_name = CString::new(name)?;
275        // SAFETY: `self.ptr` is valid; `c_name` is a valid, NUL-terminated C
276        // string for the duration of this call. The returned value, if
277        // non-null, is owned by us and destroyed below.
278        let mut value = unsafe { duckdb_bind_get_named_parameter(self.ptr, c_name.as_ptr()) };
279        if value.is_null() {
280            return Ok(None);
281        }
282        let result = T::from_duck(value).map_err(Error::ConversionError).map(Some);
283        // SAFETY: `value` was allocated by `duckdb_bind_get_named_parameter`
284        // above (checked non-null) and is destroyed exactly once here.
285        unsafe { duckdb_destroy_value(&mut value) };
286        result
287    }
288
289    /// The registration-time "extra info" set via a table function's
290    /// `set_extra_info`, if any, shared read-only across every call to
291    /// `bind`/`init`/`func` for this function.
292    ///
293    /// Returns `None` if no extra info was configured for this function.
294    ///
295    /// # Safety
296    ///
297    /// The caller must request the same `T` that was stored at registration
298    /// time; there is no runtime type check.
299    pub fn extra_info<T>(&self) -> Option<&T> {
300        // SAFETY: `self.ptr` is valid for the duration of the callback.
301        let raw = unsafe { duckdb_bind_get_extra_info(self.ptr) };
302        if raw.is_null() {
303            return None;
304        }
305        // SAFETY: `raw`, if non-null, was produced by
306        // `TableFunction::set_extra_info::<T>` for this same registration (the
307        // only place that sets it), and is kept alive by DuckDB until the
308        // catalog entry is dropped, which outlives every call using it. The
309        // caller is responsible for requesting the matching `T`.
310        Some(unsafe { &*raw.cast::<T>() })
311    }
312
313    /// Sets the estimated (or exact) number of rows this call will produce, used
314    /// by the query optimizer.
315    pub fn set_cardinality(
316        &self,
317        cardinality: u64,
318        is_exact: bool,
319    ) {
320        // SAFETY: `self.ptr` is valid.
321        unsafe { duckdb_bind_set_cardinality(self.ptr, cardinality, is_exact) };
322    }
323
324    /// Stores `data`, retrievable during init/execution via
325    /// [`TableFunctionInfo::bind_data`] and [`InitInfo::bind_data`].
326    pub(crate) fn set_bind_data<T: Send + Sync>(
327        &self,
328        data: T,
329    ) {
330        let ptr = Box::into_raw(Box::new(data)).cast::<std::ffi::c_void>();
331        // SAFETY: `ptr` was just created by `Box::into_raw::<T>` above;
332        // `drop_boxed::<T>` frees it with the matching type, exactly once, when
333        // DuckDB destroys the bind data (at the end of the query, or on bind
334        // failure).
335        unsafe { duckdb_bind_set_bind_data(self.ptr, ptr, Some(drop_boxed::<T>)) };
336    }
337}
338
339impl CallbackErrorSink for BindInfo {
340    fn set_c_error(
341        &self,
342        error: &CStr,
343    ) {
344        // SAFETY: `self.ptr` is valid for the duration of the callback; `error`
345        // is a valid, NUL-terminated C string.
346        unsafe { duckdb_bind_set_error(self.ptr, error.as_ptr()) };
347    }
348}
349
350/// An interface to store and retrieve data during the table function's init stage.
351pub struct InitInfo<V: VTab> {
352    ptr: duckdb_init_info,
353    _marker: PhantomData<fn() -> V>,
354}
355
356impl<V: VTab> InitInfo<V> {
357    pub(crate) fn from(ptr: duckdb_init_info) -> Self {
358        Self { ptr, _marker: PhantomData }
359    }
360
361    /// The bind data produced by [`VTab::bind`] for this call.
362    ///
363    /// Read-only: for tracking state across calls to the execution callback,
364    /// store it in the init data instead.
365    pub fn bind_data(&self) -> &V::BindData {
366        // SAFETY: `self.ptr` is valid for the duration of the callback.
367        let raw = unsafe { duckdb_init_get_bind_data(self.ptr) };
368        // SAFETY: `raw` was produced by `BindInfo::set_bind_data::<V::BindData>`
369        // for this same registration (the only place that calls it), and has not
370        // been freed (guaranteed by DuckDB: it outlives every init/execute call
371        // for this query).
372        unsafe { &*raw.cast::<V::BindData>() }
373    }
374
375    /// Stores `data`, retrievable during execution via [`TableFunctionInfo::init_data`].
376    pub(crate) fn set_init_data(
377        &self,
378        data: V::InitData,
379    ) {
380        let ptr = Box::into_raw(Box::new(data)).cast::<std::ffi::c_void>();
381        // SAFETY: `ptr` was just created by `Box::into_raw::<V::InitData>`
382        // above; `drop_boxed::<V::InitData>` frees it with the matching type,
383        // exactly once, when DuckDB destroys the init data.
384        unsafe { duckdb_init_set_init_data(self.ptr, ptr, Some(drop_boxed::<V::InitData>)) };
385    }
386
387    /// Stores `data` as this worker thread's *local* init data, retrievable
388    /// during execution on the same thread via
389    /// [`TableFunctionInfo::local_init_data`].
390    ///
391    /// Only meaningful when called from the local-init callback registered via
392    /// `VTabLocalInit` — DuckDB itself distinguishes the global vs. local init
393    /// data by which callback set it, both through this same C entry point.
394    pub(crate) fn set_local_init_data<T: Send + Sync>(
395        &self,
396        data: T,
397    ) {
398        let ptr = Box::into_raw(Box::new(data)).cast::<std::ffi::c_void>();
399        // SAFETY: `ptr` was just created by `Box::into_raw::<T>` above;
400        // `drop_boxed::<T>` frees it with the matching type, exactly once, when
401        // DuckDB destroys this thread's local init data.
402        unsafe { duckdb_init_set_init_data(self.ptr, ptr, Some(drop_boxed::<T>)) };
403    }
404
405    /// Sets how many threads may call the execution callback for this query in
406    /// parallel. Defaults to `1` (single-threaded execution).
407    pub fn set_max_threads(
408        &self,
409        max_threads: u64,
410    ) {
411        // SAFETY: `self.ptr` is valid.
412        unsafe { duckdb_init_set_max_threads(self.ptr, max_threads) };
413    }
414
415    /// The 0-based indices, into the function's full result schema, of the
416    /// columns the query actually needs — only meaningful when the function
417    /// declared `supports_projection_pushdown()`. Empty if pushdown wasn't
418    /// requested or the function didn't opt in, in which case every column
419    /// should be written.
420    pub fn column_indices(&self) -> Vec<usize> {
421        // SAFETY: `self.ptr` is valid for the duration of the callback.
422        let count = unsafe { duckdb_init_get_column_count(self.ptr) };
423        (0..count)
424            .map(|i| {
425                // SAFETY: `self.ptr` is valid; `i` is within `[0, count)`.
426                unsafe { duckdb_init_get_column_index(self.ptr, i) as usize }
427            })
428            .collect()
429    }
430
431    /// The registration-time "extra info", if any — see [`BindInfo::extra_info`].
432    ///
433    /// # Safety
434    ///
435    /// The caller must request the same `T` that was stored at registration
436    /// time; there is no runtime type check.
437    pub fn extra_info<T>(&self) -> Option<&T> {
438        // SAFETY: `self.ptr` is valid for the duration of the callback.
439        let raw = unsafe { duckdb_init_get_extra_info(self.ptr) };
440        if raw.is_null() {
441            return None;
442        }
443        // SAFETY: see `BindInfo::extra_info` — same invariants, same source.
444        Some(unsafe { &*raw.cast::<T>() })
445    }
446}
447
448impl<V: VTab> CallbackErrorSink for InitInfo<V> {
449    fn set_c_error(
450        &self,
451        error: &CStr,
452    ) {
453        // SAFETY: `self.ptr` is valid for the duration of the callback; `error`
454        // is a valid, NUL-terminated C string.
455        unsafe { duckdb_init_set_error(self.ptr, error.as_ptr()) };
456    }
457}
458
459/// An interface to store and retrieve data during the table function's
460/// execution stage.
461pub struct TableFunctionInfo<V: VTab> {
462    ptr: duckdb_function_info,
463    _marker: PhantomData<fn() -> V>,
464}
465
466impl<V: VTab> TableFunctionInfo<V> {
467    pub(crate) fn from(ptr: duckdb_function_info) -> Self {
468        Self { ptr, _marker: PhantomData }
469    }
470
471    /// The bind data produced by [`VTab::bind`] for this call. Read-only.
472    pub fn bind_data(&self) -> &V::BindData {
473        // SAFETY: `self.ptr` is valid for the duration of the callback.
474        let raw = unsafe { duckdb_function_get_bind_data(self.ptr) };
475        // SAFETY: `raw` was produced by `BindInfo::set_bind_data::<V::BindData>`
476        // for this same registration, and has not been freed (guaranteed by
477        // DuckDB: it outlives every execute call for this query).
478        unsafe { &*raw.cast::<V::BindData>() }
479    }
480
481    /// The init data produced by [`VTab::init`] for this call. Shared across
482    /// every worker thread executing this query, so any interior mutation must
483    /// be synchronized.
484    pub fn init_data(&self) -> &V::InitData {
485        // SAFETY: `self.ptr` is valid for the duration of the callback.
486        let raw = unsafe { duckdb_function_get_init_data(self.ptr) };
487        // SAFETY: `raw` was produced by `InitInfo::set_init_data::<V::InitData>`
488        // for this same registration, and has not been freed (guaranteed by
489        // DuckDB: it outlives every execute call for this query).
490        unsafe { &*raw.cast::<V::InitData>() }
491    }
492
493    /// The data produced by `VTabLocalInit::local_init` for *this worker
494    /// thread*, if the function registered a local-init callback. Returns
495    /// `None` if no local-init callback was registered.
496    ///
497    /// # Safety
498    ///
499    /// The caller must request the same `T` that was stored by the local-init
500    /// callback; there is no runtime type check.
501    pub fn local_init_data<T>(&self) -> Option<&T> {
502        // SAFETY: `self.ptr` is valid for the duration of the callback.
503        let raw = unsafe { duckdb_function_get_local_init_data(self.ptr) };
504        if raw.is_null() {
505            return None;
506        }
507        // SAFETY: `raw`, if non-null, was produced by
508        // `InitInfo::set_local_init_data::<T>` on this same worker thread, and
509        // has not been freed (guaranteed by DuckDB: it outlives every execute
510        // call on this thread for this query). The caller is responsible for
511        // requesting the matching `T`.
512        Some(unsafe { &*raw.cast::<T>() })
513    }
514
515    /// The registration-time "extra info", if any — see [`BindInfo::extra_info`].
516    ///
517    /// # Safety
518    ///
519    /// The caller must request the same `T` that was stored at registration
520    /// time; there is no runtime type check.
521    pub fn extra_info<T>(&self) -> Option<&T> {
522        // SAFETY: `self.ptr` is valid for the duration of the callback.
523        let raw = unsafe { duckdb_function_get_extra_info(self.ptr) };
524        if raw.is_null() {
525            return None;
526        }
527        // SAFETY: see `BindInfo::extra_info` — same invariants, same source.
528        Some(unsafe { &*raw.cast::<T>() })
529    }
530}
531
532impl<V: VTab> CallbackErrorSink for TableFunctionInfo<V> {
533    fn set_c_error(
534        &self,
535        error: &CStr,
536    ) {
537        // SAFETY: `self.ptr` is valid for the duration of the callback; `error`
538        // is a valid, NUL-terminated C string.
539        unsafe { duckdb_function_set_error(self.ptr, error.as_ptr()) };
540    }
541}