better_duck_core/udf/table/
mod.rs1mod function;
5mod row;
6
7use std::ffi::CString;
8
9use crate::{
10 connection::Connection,
11 error::Result,
12 ffi::{duckdb_bind_info, duckdb_data_chunk, duckdb_function_info, duckdb_init_info},
13};
14
15use self::function::TableFunction;
16pub use self::function::{BindInfo, InitInfo, TableFunctionInfo};
17pub use self::row::{run_table_func, TableInitData, TableRow};
18use super::{callback::contain_callback, data_chunk::DataChunkHandle};
19use crate::types::LogicalType;
20
21pub trait VTab: Sized {
26 type BindData: Send + Sync;
29
30 type InitData: Send + Sync;
33
34 fn parameters() -> Result<Vec<LogicalType>> {
41 Ok(Vec::new())
42 }
43
44 fn named_parameters() -> Result<Vec<(String, LogicalType)>> {
52 Ok(Vec::new())
53 }
54
55 fn supports_projection_pushdown() -> bool {
60 false
61 }
62
63 fn bind(bind: &BindInfo) -> super::UdfResult<Self::BindData>;
71
72 fn init(init: &InitInfo<Self>) -> super::UdfResult<Self::InitData>;
79
80 fn func(
89 func: &TableFunctionInfo<Self>,
90 output: &mut DataChunkHandle,
91 ) -> super::UdfResult<()>;
92}
93
94pub trait VTabLocalInit: VTab {
102 type LocalInitData: Send + Sync;
105
106 fn local_init(init: &InitInfo<Self>) -> super::UdfResult<Self::LocalInitData>;
112}
113
114unsafe extern "C" fn table_bind_trampoline<T: VTab>(info: duckdb_bind_info) {
119 let bind = BindInfo::from(info);
120 contain_callback(&bind, || {
121 let data = T::bind(&bind)?;
122 bind.set_bind_data(data);
123 Ok(())
124 });
125}
126
127unsafe extern "C" fn table_init_trampoline<T: VTab>(info: duckdb_init_info) {
129 let init = InitInfo::<T>::from(info);
130 contain_callback(&init, || {
131 let data = T::init(&init)?;
132 init.set_init_data(data);
133 Ok(())
134 });
135}
136
137unsafe extern "C" fn table_local_init_trampoline<T: VTabLocalInit>(info: duckdb_init_info) {
144 let init = InitInfo::<T>::from(info);
145 contain_callback(&init, || {
146 let data = T::local_init(&init)?;
147 init.set_local_init_data(data);
148 Ok(())
149 });
150}
151
152unsafe extern "C" fn table_func_trampoline<T: VTab>(
154 info: duckdb_function_info,
155 output: duckdb_data_chunk,
156) {
157 let func = TableFunctionInfo::<T>::from(info);
158 contain_callback(&func, || {
159 let mut chunk = unsafe { DataChunkHandle::borrowed(output) };
162 T::func(&func, &mut chunk)
163 });
164}
165
166impl Connection {
167 fn build_table_function<T: VTab>(name: &str) -> Result<TableFunction> {
173 let c_name = CString::new(name)?;
174 let f = TableFunction::new(&c_name);
175 for param in T::parameters()? {
176 f.add_parameter(¶m);
177 }
178 for (param_name, ty) in T::named_parameters()? {
179 f.add_named_parameter(¶m_name, &ty)?;
180 }
181 f.set_supports_projection_pushdown(T::supports_projection_pushdown());
182 f.set_bind(Some(table_bind_trampoline::<T>));
183 f.set_init(Some(table_init_trampoline::<T>));
184 f.set_function(Some(table_func_trampoline::<T>));
185 Ok(f)
186 }
187
188 pub fn register_table_function<T: VTab>(
196 &mut self,
197 name: &str,
198 ) -> Result<()> {
199 let f = Self::build_table_function::<T>(name)?;
200 f.register(self.raw_con(), name)
201 }
202
203 pub fn register_table_function_ext<T: VTabLocalInit>(
210 &mut self,
211 name: &str,
212 ) -> Result<()> {
213 let f = Self::build_table_function::<T>(name)?;
214 f.set_local_init(Some(table_local_init_trampoline::<T>));
215 f.register(self.raw_con(), name)
216 }
217
218 pub fn register_table_function_with_extra_info<T: VTab, E: Send + Sync + 'static>(
227 &mut self,
228 name: &str,
229 extra_info: E,
230 ) -> Result<()> {
231 let f = Self::build_table_function::<T>(name)?;
232 f.set_extra_info(extra_info);
233 f.register(self.raw_con(), name)
234 }
235}
236
237#[cfg(test)]
238mod tests {
239 use super::*;
240 use crate::connection::Connection;
241 use crate::types::value::DuckValue;
242
243 struct Series;
247
248 struct SeriesBind {
249 start: i64,
250 stop: i64,
251 }
252
253 struct SeriesInit {
254 next: std::sync::atomic::AtomicI64,
255 }
256
257 impl VTab for Series {
258 type BindData = SeriesBind;
259 type InitData = SeriesInit;
260
261 fn parameters() -> Result<Vec<LogicalType>> {
262 Ok(vec![LogicalType::of::<i64>()?, LogicalType::of::<i64>()?])
263 }
264
265 fn bind(bind: &BindInfo) -> super::super::UdfResult<Self::BindData> {
266 bind.add_result_column("n", &LogicalType::of::<i64>()?)?;
267 let start: i64 = bind.get_parameter(0)?;
268 let stop: i64 = bind.get_parameter(1)?;
269 bind.set_cardinality((stop - start).max(0) as u64, true);
270 Ok(SeriesBind { start, stop })
271 }
272
273 fn init(init: &InitInfo<Self>) -> super::super::UdfResult<Self::InitData> {
274 Ok(SeriesInit { next: std::sync::atomic::AtomicI64::new(init.bind_data().start) })
275 }
276
277 fn func(
278 func: &TableFunctionInfo<Self>,
279 output: &mut DataChunkHandle,
280 ) -> super::super::UdfResult<()> {
281 let stop = func.bind_data().stop;
282 let init = func.init_data();
283 let cap = output.capacity();
284 let mut written = 0usize;
285 {
286 let mut col = output.vector_mut(0)?;
287 while written < cap {
288 let n = init.next.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
289 if n >= stop {
290 init.next.fetch_sub(1, std::sync::atomic::Ordering::Relaxed);
291 break;
292 }
293 col.set(written, n)?;
294 written += 1;
295 }
296 }
297 output.set_len(written)?;
298 Ok(())
299 }
300 }
301
302 #[test]
303 fn register_and_scan_table_function() {
304 let mut conn = Connection::open_in_memory().unwrap();
305 conn.register_table_function::<Series>("series").unwrap();
306 let result = conn.execute("SELECT n FROM series(1, 6) ORDER BY n").unwrap();
307 let rows: Vec<_> = result.collect::<Result<_>>().unwrap();
308 let got: Vec<i64> = rows
309 .iter()
310 .map(|r| match r.get("n").unwrap() {
311 DuckValue::BigInt(n) => *n,
312 other => panic!("expected BigInt, got {other:?}"),
313 })
314 .collect();
315 assert_eq!(got, vec![1, 2, 3, 4, 5]);
316 }
317
318 #[test]
319 fn aggregate_over_table_function_matches_expected_sum() {
320 let mut conn = Connection::open_in_memory().unwrap();
321 conn.register_table_function::<Series>("series").unwrap();
322 let mut result = conn.execute("SELECT sum(n) AS total FROM series(1, 101)").unwrap();
323 let row = result.next().unwrap().unwrap();
324 assert_eq!(row.get("total"), Some(&DuckValue::HugeInt(5050)));
325 }
326
327 #[test]
330 fn scan_spanning_multiple_chunks() {
331 let mut conn = Connection::open_in_memory().unwrap();
332 conn.register_table_function::<Series>("series").unwrap();
333 let result = conn.execute("SELECT count(*) AS n FROM series(0, 10000)").unwrap();
334 let rows: Vec<_> = result.collect::<Result<_>>().unwrap();
335 assert_eq!(rows[0].get("n"), Some(&DuckValue::BigInt(10000)));
336 }
337
338 struct AlwaysFailsBind;
341 impl VTab for AlwaysFailsBind {
342 type BindData = ();
343 type InitData = ();
344
345 fn bind(_bind: &BindInfo) -> super::super::UdfResult<Self::BindData> {
346 Err("deliberate bind failure".into())
347 }
348
349 fn init(_init: &InitInfo<Self>) -> super::super::UdfResult<Self::InitData> {
350 Ok(())
351 }
352
353 fn func(
354 _func: &TableFunctionInfo<Self>,
355 output: &mut DataChunkHandle,
356 ) -> super::super::UdfResult<()> {
357 output.set_len(0)?;
358 Ok(())
359 }
360 }
361
362 #[test]
363 fn bind_error_surfaces_as_query_error_and_connection_stays_usable() {
364 let mut conn = Connection::open_in_memory().unwrap();
365 conn.register_table_function::<AlwaysFailsBind>("always_fails_bind").unwrap();
366 let err = match conn.execute("SELECT * FROM always_fails_bind()") {
367 Ok(_) => panic!("expected an error"),
368 Err(e) => e,
369 };
370 assert!(err.to_string().contains("deliberate bind failure"), "{err}");
371 conn.execute_batch("CREATE TABLE t (v INTEGER)").unwrap();
372 }
373
374 struct Doubler;
379
380 struct DoublerInit {
381 next: std::sync::atomic::AtomicI64,
382 stop: i64,
383 }
384
385 impl VTab for Doubler {
386 type BindData = i64;
387 type InitData = DoublerInit;
388
389 fn parameters() -> Result<Vec<LogicalType>> {
390 Ok(vec![LogicalType::of::<i64>()?])
391 }
392
393 fn bind(bind: &BindInfo) -> super::super::UdfResult<Self::BindData> {
394 bind.add_result_column("n", &LogicalType::of::<i64>()?)?;
395 let stop: i64 = bind.get_parameter(0)?;
396 Ok(stop)
397 }
398
399 fn init(init: &InitInfo<Self>) -> super::super::UdfResult<Self::InitData> {
400 let stop = *init.bind_data();
401 Ok(DoublerInit { next: std::sync::atomic::AtomicI64::new(0), stop })
402 }
403
404 fn func(
405 func: &TableFunctionInfo<Self>,
406 output: &mut DataChunkHandle,
407 ) -> super::super::UdfResult<()> {
408 let init = func.init_data();
409 let multiplier = func.local_init_data::<i64>().copied().expect("local init ran");
410 let cap = output.capacity();
411 let mut written = 0usize;
412 {
413 let mut col = output.vector_mut(0)?;
414 while written < cap {
415 let n = init.next.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
416 if n >= init.stop {
417 init.next.fetch_sub(1, std::sync::atomic::Ordering::Relaxed);
418 break;
419 }
420 col.set(written, n * multiplier)?;
421 written += 1;
422 }
423 }
424 output.set_len(written)?;
425 Ok(())
426 }
427 }
428
429 impl VTabLocalInit for Doubler {
430 type LocalInitData = i64;
431
432 fn local_init(_init: &InitInfo<Self>) -> super::super::UdfResult<Self::LocalInitData> {
433 Ok(2)
434 }
435 }
436
437 #[test]
438 fn local_init_data_is_readable_from_func() {
439 let mut conn = Connection::open_in_memory().unwrap();
440 conn.register_table_function_ext::<Doubler>("doubler").unwrap();
441 let result = conn.execute("SELECT n FROM doubler(5) ORDER BY n").unwrap();
442 let rows: Vec<_> = result.collect::<Result<_>>().unwrap();
443 let got: Vec<i64> = rows
444 .iter()
445 .map(|r| match r.get("n").unwrap() {
446 DuckValue::BigInt(n) => *n,
447 other => panic!("expected BigInt, got {other:?}"),
448 })
449 .collect();
450 assert_eq!(got, vec![0, 2, 4, 6, 8]);
451 }
452
453 struct WithContext;
456
457 impl VTab for WithContext {
458 type BindData = ();
459 type InitData = std::sync::atomic::AtomicBool;
460
461 fn bind(bind: &BindInfo) -> super::super::UdfResult<Self::BindData> {
462 bind.add_result_column("ctx", &LogicalType::of::<i64>()?)?;
463 assert!(bind.extra_info::<i64>().is_some());
465 Ok(())
466 }
467
468 fn init(_init: &InitInfo<Self>) -> super::super::UdfResult<Self::InitData> {
469 Ok(std::sync::atomic::AtomicBool::new(false))
470 }
471
472 fn func(
473 func: &TableFunctionInfo<Self>,
474 output: &mut DataChunkHandle,
475 ) -> super::super::UdfResult<()> {
476 let already_emitted = func.init_data().swap(true, std::sync::atomic::Ordering::Relaxed);
477 if already_emitted {
478 output.set_len(0)?;
479 return Ok(());
480 }
481 let ctx = *func.extra_info::<i64>().expect("extra info was registered");
482 output.vector_mut(0)?.set(0, ctx)?;
483 output.set_len(1)?;
484 Ok(())
485 }
486 }
487
488 #[test]
489 fn extra_info_is_shared_and_readable_from_bind_and_func() {
490 let mut conn = Connection::open_in_memory().unwrap();
491 conn.register_table_function_with_extra_info::<WithContext, i64>("with_context", 777)
492 .unwrap();
493 let mut result = conn.execute("SELECT ctx FROM with_context()").unwrap();
494 let row = result.next().unwrap().unwrap();
495 assert_eq!(row.get("ctx"), Some(&DuckValue::BigInt(777)));
496 }
497}