better_duck_core/udf/scalar/
mod.rs1mod function;
5
6use std::ffi::CString;
7
8use crate::{
9 connection::Connection,
10 error::Result,
11 ffi::{duckdb_data_chunk, duckdb_function_info, duckdb_vector},
12};
13
14use self::function::{ScalarFunction, ScalarFunctionInfo, ScalarFunctionSet};
15use super::{
16 callback::contain_callback, data_chunk::DataChunkHandle, logical_type::LogicalType,
17 vector::VectorMut,
18};
19
20pub trait VScalar: Sized {
24 type State: Send + Sync + 'static;
28
29 fn signatures() -> Result<Vec<ScalarSignature>>;
36
37 fn invoke(
46 state: &Self::State,
47 input: &DataChunkHandle,
48 output: &mut VectorMut<'_>,
49 ) -> super::UdfResult<()>;
50
51 fn volatile() -> bool {
55 false
56 }
57
58 fn special_handling() -> bool {
63 false
64 }
65}
66
67enum ScalarParams {
69 Exact(Vec<LogicalType>),
71 Variadic(LogicalType),
73}
74
75pub struct ScalarSignature {
77 parameters: Option<ScalarParams>,
78 return_type: LogicalType,
79}
80
81impl ScalarSignature {
82 pub fn exact(
84 parameters: Vec<LogicalType>,
85 return_type: LogicalType,
86 ) -> Self {
87 Self { parameters: Some(ScalarParams::Exact(parameters)), return_type }
88 }
89
90 pub fn variadic(
92 parameter: LogicalType,
93 return_type: LogicalType,
94 ) -> Self {
95 Self { parameters: Some(ScalarParams::Variadic(parameter)), return_type }
96 }
97
98 fn apply(
99 &self,
100 f: &ScalarFunction,
101 ) {
102 f.set_return_type(&self.return_type);
103 match &self.parameters {
104 Some(ScalarParams::Exact(params)) => {
105 for p in params {
106 f.add_parameter(p);
107 }
108 },
109 Some(ScalarParams::Variadic(p)) => f.set_varargs(p),
110 None => {},
111 }
112 }
113}
114
115unsafe extern "C" fn scalar_trampoline<S: VScalar>(
122 info: duckdb_function_info,
123 input: duckdb_data_chunk,
124 output: duckdb_vector,
125) {
126 let sink = ScalarFunctionInfo::from(info);
127 contain_callback(&sink, || {
128 let chunk = unsafe { DataChunkHandle::borrowed(input) };
131 let mut out = unsafe { VectorMut::new(output) };
135 let state = unsafe { sink.state::<S::State>() };
139 S::invoke(state, &chunk, &mut out)
140 });
141}
142
143impl Connection {
144 pub fn register_scalar_function<S: VScalar>(
153 &mut self,
154 name: &str,
155 ) -> Result<()>
156 where
157 S::State: Default,
158 {
159 register_scalar_function_impl::<S>(self, name, S::State::default)
162 }
163
164 pub fn register_scalar_function_with_state<S: VScalar>(
173 &mut self,
174 name: &str,
175 state: S::State,
176 ) -> Result<()>
177 where
178 S::State: Clone,
179 {
180 register_scalar_function_impl::<S>(self, name, move || state.clone())
181 }
182}
183
184fn register_scalar_function_impl<S: VScalar>(
185 conn: &mut Connection,
186 name: &str,
187 mut make_state: impl FnMut() -> S::State,
188) -> Result<()> {
189 let c_name = CString::new(name)?;
190 let set = ScalarFunctionSet::new(&c_name);
191 for signature in S::signatures()? {
192 let f = ScalarFunction::new(&c_name);
193 signature.apply(&f);
194 f.set_function(Some(scalar_trampoline::<S>));
195 if S::volatile() {
196 f.set_volatile();
197 }
198 if S::special_handling() {
199 f.set_special_handling();
200 }
201 f.set_extra_info(make_state());
202 set.add_function(&f)?;
203 }
204 set.register(conn.raw_con(), name)
205}
206
207#[cfg(test)]
208mod tests {
209 use super::*;
210 use crate::connection::Connection;
211
212 struct AddOne;
215
216 impl VScalar for AddOne {
217 type State = ();
218
219 fn signatures() -> Result<Vec<ScalarSignature>> {
220 Ok(vec![ScalarSignature::exact(
221 vec![LogicalType::of::<i32>()?],
222 LogicalType::of::<i32>()?,
223 )])
224 }
225
226 fn invoke(
227 _state: &(),
228 input: &DataChunkHandle,
229 output: &mut VectorMut<'_>,
230 ) -> super::super::UdfResult<()> {
231 let col = input.vector(0)?;
232 for row in 0..input.len() {
233 let v: i32 = col.get(row)?;
234 output.set(row, v + 1)?;
235 }
236 Ok(())
237 }
238 }
239
240 #[test]
241 fn register_and_call_scalar_function() {
242 let mut conn = Connection::open_in_memory().unwrap();
243 conn.register_scalar_function::<AddOne>("add_one").unwrap();
244 conn.execute_batch("CREATE TABLE t (v INTEGER)").unwrap();
245 conn.execute_batch("INSERT INTO t VALUES (1), (2), (41)").unwrap();
246 let result = conn.execute("SELECT add_one(v) AS r FROM t ORDER BY v").unwrap();
247 let rows: Vec<_> = result.collect::<Result<_>>().unwrap();
248 assert_eq!(rows.len(), 3);
249 assert_eq!(rows[2].get("r"), Some(&crate::types::value::DuckValue::Int(42)));
250 }
251
252 struct AlwaysFails;
255
256 impl VScalar for AlwaysFails {
257 type State = ();
258
259 fn signatures() -> Result<Vec<ScalarSignature>> {
260 Ok(vec![ScalarSignature::exact(
261 vec![LogicalType::of::<i32>()?],
262 LogicalType::of::<i32>()?,
263 )])
264 }
265
266 fn invoke(
267 _state: &(),
268 _input: &DataChunkHandle,
269 _output: &mut VectorMut<'_>,
270 ) -> super::super::UdfResult<()> {
271 Err("deliberate failure".into())
272 }
273 }
274
275 #[test]
276 fn invoke_error_surfaces_as_query_error_and_connection_stays_usable() {
277 let mut conn = Connection::open_in_memory().unwrap();
278 conn.register_scalar_function::<AlwaysFails>("always_fails").unwrap();
279 conn.execute_batch("CREATE TABLE t (v INTEGER)").unwrap();
280 conn.execute_batch("INSERT INTO t VALUES (1)").unwrap();
281 let err = match conn.execute("SELECT always_fails(v) FROM t") {
282 Ok(_) => panic!("expected an error"),
283 Err(e) => e,
284 };
285 assert!(err.to_string().contains("deliberate failure"), "{err}");
286 conn.execute_batch("INSERT INTO t VALUES (2)").unwrap();
288 }
289
290 struct AlwaysPanics;
292
293 impl VScalar for AlwaysPanics {
294 type State = ();
295
296 fn signatures() -> Result<Vec<ScalarSignature>> {
297 Ok(vec![ScalarSignature::exact(
298 vec![LogicalType::of::<i32>()?],
299 LogicalType::of::<i32>()?,
300 )])
301 }
302
303 fn invoke(
304 _state: &(),
305 _input: &DataChunkHandle,
306 _output: &mut VectorMut<'_>,
307 ) -> super::super::UdfResult<()> {
308 panic!("deliberate panic")
309 }
310 }
311
312 #[test]
313 #[cfg(panic = "unwind")]
314 fn invoke_panic_is_contained_and_connection_stays_usable() {
315 let mut conn = Connection::open_in_memory().unwrap();
316 conn.register_scalar_function::<AlwaysPanics>("always_panics").unwrap();
317 conn.execute_batch("CREATE TABLE t (v INTEGER)").unwrap();
318 conn.execute_batch("INSERT INTO t VALUES (1)").unwrap();
319 let err = match conn.execute("SELECT always_panics(v) FROM t") {
320 Ok(_) => panic!("expected an error"),
321 Err(e) => e,
322 };
323 assert!(err.to_string().contains("deliberate panic"), "{err}");
324 conn.execute_batch("INSERT INTO t VALUES (2)").unwrap();
325 }
326}