1use 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#[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
29unsafe extern "C" fn drop_boxed<T>(v: *mut c_void) {
34 drop(unsafe { Box::from_raw(v.cast::<T>()) });
35}
36
37pub trait VTab: Sized {
42 type InitData: Sized + Send + Sync;
48
49 type BindData: Sized + Send + Sync;
53
54 fn bind(bind: &BindInfo) -> Result<Self::BindData, Box<dyn std::error::Error>>;
58
59 fn init(init: &InitInfo) -> Result<Self::InitData, Box<dyn std::error::Error>>;
61
62 fn func(func: &TableFunctionInfo<Self>, output: &mut DataChunkHandle) -> Result<(), Box<dyn std::error::Error>>;
68
69 fn supports_pushdown() -> bool {
72 false
73 }
74 fn parameters() -> Option<Vec<LogicalTypeHandle>> {
77 None
78 }
79 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 #[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 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 #[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}