duckdb/vtab/
mod.rs

1// #![warn(unsafe_op_in_unsafe_fn)]
2
3use std::ffi::c_void;
4
5use crate::{error::Error, inner_connection::InnerConnection, Connection, Result};
6
7use super::ffi;
8
9mod function;
10mod value;
11
12/// The duckdb Arrow table function interface
13#[cfg(feature = "vtab-arrow")]
14pub mod arrow;
15#[cfg(feature = "vtab-arrow")]
16pub use self::arrow::{
17    arrow_arraydata_to_query_params, arrow_ffi_to_query_params, arrow_recordbatch_to_query_params,
18    record_batch_to_duckdb_data_chunk, to_duckdb_logical_type, to_duckdb_type_id,
19};
20#[cfg(feature = "vtab-excel")]
21mod excel;
22
23pub use function::{BindInfo, InitInfo, TableFunction, TableFunctionInfo};
24pub use value::Value;
25
26use crate::core::{DataChunkHandle, LogicalTypeHandle};
27use ffi::{duckdb_bind_info, duckdb_data_chunk, duckdb_function_info, duckdb_init_info};
28
29/// Given a raw pointer to a box, free the box and the data contained within it.
30///
31/// # Safety
32/// The pointer must be a valid pointer to a `Box<T>` created by `Box::into_raw`.
33unsafe extern "C" fn drop_boxed<T>(v: *mut c_void) {
34    drop(unsafe { Box::from_raw(v.cast::<T>()) });
35}
36
37/// Duckdb table function trait
38///
39/// See to the HelloVTab example for more details
40/// <https://duckdb.org/docs/api/c/table_functions>
41pub trait VTab: Sized {
42    /// The data type of the init data.
43    ///
44    /// The init data tracks the state of the table function and is global across threads.
45    ///
46    /// The init data is shared across threads so must be `Send + Sync`.
47    type InitData: Sized + Send + Sync;
48
49    /// The data type of the bind data.
50    ///
51    /// The bind data is shared across threads so must be `Send + Sync`.
52    type BindData: Sized + Send + Sync;
53
54    /// Bind data to the table function
55    ///
56    /// This function is used for determining the return type of a table producing function and returning bind data
57    fn bind(bind: &BindInfo) -> Result<Self::BindData, Box<dyn std::error::Error>>;
58
59    /// Initialize the table function
60    fn init(init: &InitInfo) -> Result<Self::InitData, Box<dyn std::error::Error>>;
61
62    /// Generate rows from the table function.
63    ///
64    /// The implementation should populate the `output` parameter with the rows to be returned.
65    ///
66    /// When the table function is done, the implementation should set the length of the output to 0.
67    fn func(func: &TableFunctionInfo<Self>, output: &mut DataChunkHandle) -> Result<(), Box<dyn std::error::Error>>;
68
69    /// Does the table function support pushdown
70    /// default is false
71    fn supports_pushdown() -> bool {
72        false
73    }
74    /// The parameters of the table function
75    /// default is None
76    fn parameters() -> Option<Vec<LogicalTypeHandle>> {
77        None
78    }
79    /// The named parameters of the table function
80    /// default is None
81    fn named_parameters() -> Option<Vec<(String, LogicalTypeHandle)>> {
82        None
83    }
84}
85
86unsafe extern "C" fn func<T>(info: duckdb_function_info, output: duckdb_data_chunk)
87where
88    T: VTab,
89{
90    let info = TableFunctionInfo::<T>::from(info);
91    let mut data_chunk_handle = DataChunkHandle::new_unowned(output);
92    let result = T::func(&info, &mut data_chunk_handle);
93    if result.is_err() {
94        info.set_error(&result.err().unwrap().to_string());
95    }
96}
97
98unsafe extern "C" fn init<T>(info: duckdb_init_info)
99where
100    T: VTab,
101{
102    let info = InitInfo::from(info);
103    match T::init(&info) {
104        Ok(init_data) => {
105            info.set_init_data(
106                Box::into_raw(Box::new(init_data)) as *mut c_void,
107                Some(drop_boxed::<T::InitData>),
108            );
109        }
110        Err(e) => {
111            info.set_error(&e.to_string());
112        }
113    }
114}
115
116unsafe extern "C" fn bind<T>(info: duckdb_bind_info)
117where
118    T: VTab,
119{
120    let info = BindInfo::from(info);
121    match T::bind(&info) {
122        Ok(bind_data) => {
123            info.set_bind_data(
124                Box::into_raw(Box::new(bind_data)) as *mut c_void,
125                Some(drop_boxed::<T::BindData>),
126            );
127        }
128        Err(e) => {
129            info.set_error(&e.to_string());
130        }
131    }
132}
133
134impl Connection {
135    /// Register the given TableFunction with the current db
136    #[inline]
137    pub fn register_table_function<T: VTab>(&self, name: &str) -> Result<()> {
138        let table_function = TableFunction::default();
139        table_function
140            .set_name(name)
141            .supports_pushdown(T::supports_pushdown())
142            .set_bind(Some(bind::<T>))
143            .set_init(Some(init::<T>))
144            .set_function(Some(func::<T>));
145        for ty in T::parameters().unwrap_or_default() {
146            table_function.add_parameter(&ty);
147        }
148        for (name, ty) in T::named_parameters().unwrap_or_default() {
149            table_function.add_named_parameter(&name, &ty);
150        }
151        self.db.borrow_mut().register_table_function(table_function)
152    }
153}
154
155impl InnerConnection {
156    /// Register the given TableFunction with the current db
157    pub fn register_table_function(&mut self, table_function: TableFunction) -> Result<()> {
158        unsafe {
159            let rc = ffi::duckdb_register_table_function(self.con, table_function.ptr);
160            if rc != ffi::DuckDBSuccess {
161                return Err(Error::DuckDBFailure(ffi::Error::new(rc), None));
162            }
163        }
164        Ok(())
165    }
166}
167
168#[cfg(test)]
169mod test {
170    use super::*;
171    use crate::core::{Inserter, LogicalTypeId};
172    use std::{
173        error::Error,
174        ffi::CString,
175        sync::atomic::{AtomicBool, Ordering},
176    };
177
178    struct HelloBindData {
179        name: String,
180    }
181
182    struct HelloInitData {
183        done: AtomicBool,
184    }
185
186    struct HelloVTab;
187
188    impl VTab for HelloVTab {
189        type InitData = HelloInitData;
190        type BindData = HelloBindData;
191
192        fn bind(bind: &BindInfo) -> Result<Self::BindData, Box<dyn std::error::Error>> {
193            bind.add_result_column("column0", LogicalTypeHandle::from(LogicalTypeId::Varchar));
194            let name = bind.get_parameter(0).to_string();
195            Ok(HelloBindData { name })
196        }
197
198        fn init(_: &InitInfo) -> Result<Self::InitData, Box<dyn std::error::Error>> {
199            Ok(HelloInitData {
200                done: AtomicBool::new(false),
201            })
202        }
203
204        fn func(
205            func: &TableFunctionInfo<Self>,
206            output: &mut DataChunkHandle,
207        ) -> Result<(), Box<dyn std::error::Error>> {
208            let init_data = func.get_init_data();
209            let bind_data = func.get_bind_data();
210
211            if init_data.done.swap(true, Ordering::Relaxed) {
212                output.set_len(0);
213            } else {
214                let vector = output.flat_vector(0);
215                let result = CString::new(format!("Hello {}", bind_data.name))?;
216                vector.insert(0, result);
217                output.set_len(1);
218            }
219            Ok(())
220        }
221
222        fn parameters() -> Option<Vec<LogicalTypeHandle>> {
223            Some(vec![LogicalTypeHandle::from(LogicalTypeId::Varchar)])
224        }
225    }
226
227    struct HelloWithNamedVTab {}
228    impl VTab for HelloWithNamedVTab {
229        type InitData = HelloInitData;
230        type BindData = HelloBindData;
231
232        fn bind(bind: &BindInfo) -> Result<Self::BindData, Box<dyn Error>> {
233            bind.add_result_column("column0", LogicalTypeHandle::from(LogicalTypeId::Varchar));
234            let name = bind.get_named_parameter("name").unwrap().to_string();
235            assert!(bind.get_named_parameter("unknown_name").is_none());
236            Ok(HelloBindData { name })
237        }
238
239        fn init(init_info: &InitInfo) -> Result<Self::InitData, Box<dyn Error>> {
240            HelloVTab::init(init_info)
241        }
242
243        fn func(func: &TableFunctionInfo<Self>, output: &mut DataChunkHandle) -> Result<(), Box<dyn Error>> {
244            let init_data = func.get_init_data();
245            let bind_data = func.get_bind_data();
246
247            if init_data.done.swap(true, Ordering::Relaxed) {
248                output.set_len(0);
249            } else {
250                let vector = output.flat_vector(0);
251                let result = CString::new(format!("Hello {}", bind_data.name))?;
252                vector.insert(0, result);
253                output.set_len(1);
254            }
255            Ok(())
256        }
257
258        fn named_parameters() -> Option<Vec<(String, LogicalTypeHandle)>> {
259            Some(vec![(
260                "name".to_string(),
261                LogicalTypeHandle::from(LogicalTypeId::Varchar),
262            )])
263        }
264    }
265
266    #[test]
267    fn test_table_function() -> Result<(), Box<dyn Error>> {
268        let conn = Connection::open_in_memory()?;
269        conn.register_table_function::<HelloVTab>("hello")?;
270
271        let val = conn.query_row("select * from hello('duckdb')", [], |row| <(String,)>::try_from(row))?;
272        assert_eq!(val, ("Hello duckdb".to_string(),));
273
274        Ok(())
275    }
276
277    #[test]
278    fn test_named_table_function() -> Result<(), Box<dyn Error>> {
279        let conn = Connection::open_in_memory()?;
280        conn.register_table_function::<HelloWithNamedVTab>("hello_named")?;
281
282        let val = conn.query_row("select * from hello_named(name = 'duckdb')", [], |row| {
283            <(String,)>::try_from(row)
284        })?;
285        assert_eq!(val, ("Hello duckdb".to_string(),));
286
287        Ok(())
288    }
289
290    #[cfg(feature = "vtab-loadable")]
291    use duckdb_loadable_macros::duckdb_entrypoint;
292
293    // this function is never called, but is still type checked
294    // Exposes a extern C function named "libhello_ext_init" in the compiled dynamic library,
295    // the "entrypoint" that duckdb will use to load the extension.
296    #[cfg(feature = "vtab-loadable")]
297    #[duckdb_entrypoint]
298    fn libhello_ext_init(conn: Connection) -> Result<(), Box<dyn Error>> {
299        conn.register_table_function::<HelloVTab>("hello")?;
300        Ok(())
301    }
302}