Skip to content

Commit cc3efce

Browse files
authored
Allow to pass extra info to table functions (#637)
Via `register_table_function_with_options`
2 parents 952943a + 25d281b commit cc3efce

File tree

2 files changed

+134
-16
lines changed

2 files changed

+134
-16
lines changed

crates/duckdb/src/vtab/function.rs

Lines changed: 35 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use super::{
2+
drop_boxed,
23
ffi::{
34
duckdb_bind_add_result_column, duckdb_bind_get_extra_info, duckdb_bind_get_named_parameter,
45
duckdb_bind_get_parameter, duckdb_bind_get_parameter_count, duckdb_bind_info, duckdb_bind_set_bind_data,
@@ -36,6 +37,7 @@ impl BindInfo {
3637
duckdb_bind_add_result_column(self.ptr, c_str.as_ptr() as *const c_char, column_type.ptr);
3738
}
3839
}
40+
3941
/// Report that an error has occurred while calling bind.
4042
///
4143
/// # Arguments
@@ -49,18 +51,20 @@ impl BindInfo {
4951
/// Sets the user-provided bind data in the bind object. This object can be retrieved again during execution.
5052
///
5153
/// # Arguments
52-
/// * `extra_data`: The bind data object.
53-
/// * `destroy`: The callback that will be called to destroy the bind data (if any)
54+
/// * `data`: The bind data object.
55+
/// * `free_function`: The callback that will be called to destroy the bind data (if any)
5456
///
5557
/// # Safety
56-
///
58+
/// `data` must be a valid pointer, and `free_function` must properly free it.
5759
pub unsafe fn set_bind_data(&self, data: *mut c_void, free_function: Option<unsafe extern "C" fn(*mut c_void)>) {
5860
duckdb_bind_set_bind_data(self.ptr, data, free_function);
5961
}
62+
6063
/// Retrieves the number of regular (non-named) parameters to the function.
6164
pub fn get_parameter_count(&self) -> u64 {
6265
unsafe { duckdb_bind_get_parameter_count(self.ptr) }
6366
}
67+
6468
/// Retrieves the parameter at the given index.
6569
///
6670
/// # Arguments
@@ -107,10 +111,7 @@ impl BindInfo {
107111
pub fn set_cardinality(&self, cardinality: idx_t, is_exact: bool) {
108112
unsafe { duckdb_bind_set_cardinality(self.ptr, cardinality, is_exact) }
109113
}
110-
/// Retrieves the extra info of the function as set in [`TableFunction::set_extra_info`]
111-
///
112-
/// # Arguments
113-
/// * `returns`: The extra info
114+
/// Retrieves the extra info of the function as set in [`TableFunction::with_extra_info`].
114115
pub fn get_extra_info<T>(&self) -> *const T {
115116
unsafe { duckdb_bind_get_extra_info(self.ptr).cast() }
116117
}
@@ -162,13 +163,11 @@ impl InitInfo {
162163
indices
163164
}
164165

165-
/// Retrieves the extra info of the function as set in [`TableFunction::set_extra_info`]
166-
///
167-
/// # Arguments
168-
/// * `returns`: The extra info
166+
/// Retrieves the extra info of the function as set in [`TableFunction::with_extra_info`].
169167
pub fn get_extra_info<T>(&self) -> *const T {
170168
unsafe { duckdb_init_get_extra_info(self.0).cast() }
171169
}
170+
172171
/// Gets the bind data set by [`BindInfo::set_bind_data`] during the bind.
173172
///
174173
/// Note that the bind data should be considered as read-only.
@@ -179,13 +178,15 @@ impl InitInfo {
179178
pub fn get_bind_data<T>(&self) -> *const T {
180179
unsafe { duckdb_init_get_bind_data(self.0).cast() }
181180
}
181+
182182
/// Sets how many threads can process this table function in parallel (default: 1)
183183
///
184184
/// # Arguments
185185
/// * `max_threads`: The maximum amount of threads that can process this table function
186186
pub fn set_max_threads(&self, max_threads: idx_t) {
187187
unsafe { duckdb_init_set_max_threads(self.0, max_threads) }
188188
}
189+
189190
/// Report that an error has occurred while calling init.
190191
///
191192
/// # Arguments
@@ -307,15 +308,35 @@ impl TableFunction {
307308

308309
/// Assigns extra information to the table function that can be fetched during binding, etc.
309310
///
311+
/// For most use cases, prefer [`with_extra_info`](Self::with_extra_info) which handles memory management automatically.
312+
///
310313
/// # Arguments
311314
/// * `extra_info`: The extra information
312315
/// * `destroy`: The callback that will be called to destroy the bind data (if any)
313316
///
314317
/// # Safety
318+
/// The caller must ensure that `extra_info` is a valid pointer and that `destroy`
319+
/// properly cleans up the data when called.
315320
pub unsafe fn set_extra_info(&self, extra_info: *mut c_void, destroy: duckdb_delete_callback_t) {
321+
duckdb_table_function_set_extra_info(self.ptr, extra_info, destroy);
322+
}
323+
324+
/// Assigns extra information to the table function that can be fetched during binding, init, and execution.
325+
///
326+
/// This is a safe wrapper around [`set_extra_info`](Self::set_extra_info) that handles memory management automatically.
327+
///
328+
/// # Arguments
329+
/// * `info`: The extra information to store
330+
pub fn with_extra_info<T>(&self, info: T) -> &Self
331+
where
332+
T: Send + Sync + 'static,
333+
{
316334
unsafe {
317-
duckdb_table_function_set_extra_info(self.ptr, extra_info, destroy);
335+
let boxed = Box::new(info);
336+
let ptr = Box::into_raw(boxed) as *mut c_void;
337+
self.set_extra_info(ptr, Some(drop_boxed::<T>));
318338
}
339+
self
319340
}
320341

321342
/// Sets the thread-local init function of the table function
@@ -383,13 +404,11 @@ impl<V: VTab> TableFunctionInfo<V> {
383404
}
384405
}
385406

386-
/// Retrieves the extra info of the function as set in [`TableFunction::set_extra_info`]
387-
///
388-
/// # Arguments
389-
/// * `returns`: The extra info
407+
/// Retrieves the extra info of the function as set in [`TableFunction::with_extra_info`].
390408
pub fn get_extra_info<T>(&self) -> *mut T {
391409
unsafe { duckdb_function_get_extra_info(self.ptr).cast() }
392410
}
411+
393412
/// Gets the thread-local init data set by [`InitInfo::set_init_data`] during the local_init.
394413
///
395414
/// # Arguments

crates/duckdb/src/vtab/mod.rs

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,20 @@ mod excel;
2323
pub use function::{BindInfo, InitInfo, TableFunction, TableFunctionInfo};
2424
pub use value::Value;
2525

26+
/// Options for registering a table function.
27+
pub struct TableFunctionOptions<E> {
28+
/// Extra info passed to the function at runtime.
29+
/// Accessible via `BindInfo::get_extra_info`, `InitInfo::get_extra_info`,
30+
/// or `TableFunctionInfo::get_extra_info`.
31+
pub extra_info: Option<E>,
32+
}
33+
34+
impl<E> Default for TableFunctionOptions<E> {
35+
fn default() -> Self {
36+
Self { extra_info: None }
37+
}
38+
}
39+
2640
use crate::core::{DataChunkHandle, LogicalTypeHandle};
2741
use ffi::{duckdb_bind_info, duckdb_data_chunk, duckdb_function_info, duckdb_init_info};
2842

@@ -150,6 +164,35 @@ impl Connection {
150164
}
151165
self.db.borrow_mut().register_table_function(table_function)
152166
}
167+
168+
/// Register the given TableFunction with options.
169+
#[inline]
170+
pub fn register_table_function_with_options<T: VTab, E>(
171+
&self,
172+
name: &str,
173+
options: TableFunctionOptions<E>,
174+
) -> Result<()>
175+
where
176+
E: Send + Sync + 'static,
177+
{
178+
let table_function = TableFunction::default();
179+
table_function
180+
.set_name(name)
181+
.supports_pushdown(T::supports_pushdown())
182+
.set_bind(Some(bind::<T>))
183+
.set_init(Some(init::<T>))
184+
.set_function(Some(func::<T>));
185+
if let Some(extra_info) = options.extra_info {
186+
table_function.with_extra_info(extra_info);
187+
}
188+
for ty in T::parameters().unwrap_or_default() {
189+
table_function.add_parameter(&ty);
190+
}
191+
for (name, ty) in T::named_parameters().unwrap_or_default() {
192+
table_function.add_named_parameter(&name, &ty);
193+
}
194+
self.db.borrow_mut().register_table_function(table_function)
195+
}
153196
}
154197

155198
impl InnerConnection {
@@ -287,6 +330,62 @@ mod test {
287330
Ok(())
288331
}
289332

333+
// Test table function with extra info
334+
struct PrefixVTab;
335+
336+
impl VTab for PrefixVTab {
337+
type InitData = HelloInitData;
338+
type BindData = HelloBindData;
339+
340+
fn bind(bind: &BindInfo) -> Result<Self::BindData, Box<dyn Error>> {
341+
bind.add_result_column("column0", LogicalTypeHandle::from(LogicalTypeId::Varchar));
342+
let name = bind.get_parameter(0).to_string();
343+
Ok(HelloBindData { name })
344+
}
345+
346+
fn init(_: &InitInfo) -> Result<Self::InitData, Box<dyn Error>> {
347+
Ok(HelloInitData {
348+
done: AtomicBool::new(false),
349+
})
350+
}
351+
352+
fn func(func: &TableFunctionInfo<Self>, output: &mut DataChunkHandle) -> Result<(), Box<dyn Error>> {
353+
let init_data = func.get_init_data();
354+
let bind_data = func.get_bind_data();
355+
let prefix = unsafe { &*func.get_extra_info::<String>() };
356+
357+
if init_data.done.swap(true, Ordering::Relaxed) {
358+
output.set_len(0);
359+
} else {
360+
let vector = output.flat_vector(0);
361+
let result = CString::new(format!("{prefix} {}", bind_data.name))?;
362+
vector.insert(0, result);
363+
output.set_len(1);
364+
}
365+
Ok(())
366+
}
367+
368+
fn parameters() -> Option<Vec<LogicalTypeHandle>> {
369+
Some(vec![LogicalTypeHandle::from(LogicalTypeId::Varchar)])
370+
}
371+
}
372+
373+
#[test]
374+
fn test_table_function_with_options() -> Result<(), Box<dyn Error>> {
375+
let conn = Connection::open_in_memory()?;
376+
conn.register_table_function_with_options::<PrefixVTab, _>(
377+
"greet",
378+
TableFunctionOptions {
379+
extra_info: Some("Howdy".to_string()),
380+
},
381+
)?;
382+
383+
let val = conn.query_row("select * from greet('partner')", [], |row| <(String,)>::try_from(row))?;
384+
assert_eq!(val, ("Howdy partner".to_string(),));
385+
386+
Ok(())
387+
}
388+
290389
#[cfg(feature = "vtab-loadable")]
291390
use duckdb_loadable_macros::duckdb_entrypoint;
292391

0 commit comments

Comments
 (0)