better_duck_core/udf/aggregate/
mod.rs1mod function;
12
13use std::ffi::CString;
14use std::mem::MaybeUninit;
15use std::panic::{catch_unwind, AssertUnwindSafe};
16
17use crate::{
18 connection::Connection,
19 error::Result,
20 ffi::{duckdb_aggregate_state, duckdb_data_chunk, duckdb_function_info, duckdb_vector, idx_t},
21};
22
23use self::function::{AggregateFunction, AggregateFunctionInfo, AggregateFunctionSet};
24use super::{callback::contain_callback, data_chunk::DataChunkHandle, vector::VectorMut};
25use crate::types::LogicalType;
26
27pub trait VAggregate: Sized {
31 type State: Send + Sync + 'static;
34
35 type Shared: Send + Sync + 'static;
38
39 fn parameters() -> Result<Vec<LogicalType>>;
45
46 fn return_type() -> Result<LogicalType>;
52
53 fn init() -> Self::State;
55
56 fn update(
62 shared: &Self::Shared,
63 state: &mut Self::State,
64 input: &DataChunkHandle,
65 row: usize,
66 ) -> super::UdfResult<()>;
67
68 fn combine(
70 shared: &Self::Shared,
71 source: &Self::State,
72 target: &mut Self::State,
73 );
74
75 fn finalize(
81 shared: &Self::Shared,
82 state: &Self::State,
83 output: &mut VectorMut<'_>,
84 row: usize,
85 ) -> super::UdfResult<()>;
86
87 fn special_handling() -> bool {
90 false
91 }
92}
93
94struct RawState<S> {
98 initialised: bool,
99 data: MaybeUninit<S>,
100}
101
102impl<S> RawState<S> {
103 fn ready(value: S) -> Self {
104 Self { initialised: true, data: MaybeUninit::new(value) }
105 }
106
107 fn poisoned() -> Self {
108 Self { initialised: false, data: MaybeUninit::uninit() }
109 }
110
111 unsafe fn get(&self) -> &S {
114 debug_assert!(self.initialised, "aggregate state read before init");
115 unsafe { &*self.data.as_ptr() }
117 }
118
119 unsafe fn get_mut(&mut self) -> &mut S {
122 debug_assert!(self.initialised, "aggregate state written before init");
123 unsafe { &mut *self.data.as_mut_ptr() }
125 }
126
127 unsafe fn drop_value(&mut self) {
129 if self.initialised {
130 self.initialised = false;
131 unsafe { self.data.assume_init_drop() };
133 }
134 }
135}
136
137unsafe extern "C" fn agg_state_size<A: VAggregate>(_info: duckdb_function_info) -> idx_t {
139 std::mem::size_of::<RawState<A::State>>() as idx_t
140}
141
142unsafe extern "C" fn agg_init<A: VAggregate>(
144 _info: duckdb_function_info,
145 state: duckdb_aggregate_state,
146) {
147 let slot = state as *mut RawState<A::State>;
148 let value = catch_unwind(AssertUnwindSafe(A::init));
151 unsafe {
154 match value {
155 Ok(v) => slot.write(RawState::ready(v)),
156 Err(_) => slot.write(RawState::poisoned()),
157 }
158 }
159}
160
161unsafe extern "C" fn agg_update<A: VAggregate>(
163 info: duckdb_function_info,
164 input: duckdb_data_chunk,
165 states: *mut duckdb_aggregate_state,
166) {
167 let sink = AggregateFunctionInfo::from(info);
168 contain_callback(&sink, || {
169 let shared = unsafe { sink.extra_info::<A::Shared>() };
171 let chunk = unsafe { DataChunkHandle::borrowed(input) };
173 for row in 0..chunk.len() {
174 let slot = unsafe { &mut *((*states.add(row)) as *mut RawState<A::State>) };
177 let state = unsafe { slot.get_mut() };
179 A::update(shared, state, &chunk, row)?;
180 }
181 Ok(())
182 });
183}
184
185unsafe extern "C" fn agg_combine<A: VAggregate>(
187 info: duckdb_function_info,
188 source: *mut duckdb_aggregate_state,
189 target: *mut duckdb_aggregate_state,
190 count: idx_t,
191) {
192 let sink = AggregateFunctionInfo::from(info);
193 contain_callback(&sink, || {
194 let shared = unsafe { sink.extra_info::<A::Shared>() };
196 for i in 0..count as usize {
197 let src = unsafe { &*((*source.add(i)) as *const RawState<A::State>) };
199 let tgt = unsafe { &mut *((*target.add(i)) as *mut RawState<A::State>) };
201 let (s, t) = unsafe { (src.get(), tgt.get_mut()) };
203 A::combine(shared, s, t);
204 }
205 Ok(())
206 });
207}
208
209unsafe extern "C" fn agg_finalize<A: VAggregate>(
211 info: duckdb_function_info,
212 source: *mut duckdb_aggregate_state,
213 result: duckdb_vector,
214 count: idx_t,
215 offset: idx_t,
216) {
217 let sink = AggregateFunctionInfo::from(info);
218 contain_callback(&sink, || {
219 let shared = unsafe { sink.extra_info::<A::Shared>() };
221 let mut out = unsafe { VectorMut::new(result) };
223 for i in 0..count as usize {
224 let slot = unsafe { &*((*source.add(i)) as *const RawState<A::State>) };
226 let state = unsafe { slot.get() };
228 A::finalize(shared, state, &mut out, offset as usize + i)?;
229 }
230 Ok(())
231 });
232}
233
234unsafe extern "C" fn agg_destroy<A: VAggregate>(
236 states: *mut duckdb_aggregate_state,
237 count: idx_t,
238) {
239 let _ = catch_unwind(AssertUnwindSafe(|| {
241 for i in 0..count as usize {
242 let slot = unsafe { &mut *((*states.add(i)) as *mut RawState<A::State>) };
245 unsafe { slot.drop_value() };
247 }
248 }));
249}
250
251impl Connection {
252 pub fn register_aggregate_function<A: VAggregate>(
260 &mut self,
261 name: &str,
262 ) -> Result<()>
263 where
264 A::Shared: Default,
265 {
266 self.register_aggregate_function_with_state::<A>(name, A::Shared::default())
267 }
268
269 pub fn register_aggregate_function_with_state<A: VAggregate>(
276 &mut self,
277 name: &str,
278 shared: A::Shared,
279 ) -> Result<()> {
280 let c_name = CString::new(name)?;
281 let f = build_aggregate::<A>(&c_name, shared)?;
282 f.register(self.raw_con(), name)
283 }
284
285 pub fn register_aggregate_function_set(
297 &mut self,
298 name: &str,
299 build: impl FnOnce(&mut AggregateSetBuilder) -> Result<()>,
300 ) -> Result<()> {
301 let c_name = CString::new(name)?;
302 let set = AggregateFunctionSet::new(&c_name);
303 let mut builder = AggregateSetBuilder { set: &set, name };
304 build(&mut builder)?;
305 set.register(self.raw_con(), name)
306 }
307}
308
309fn build_aggregate<A: VAggregate>(
311 c_name: &std::ffi::CStr,
312 shared: A::Shared,
313) -> Result<AggregateFunction> {
314 let f = AggregateFunction::new(c_name);
315 for p in A::parameters()? {
316 f.add_parameter(&p);
317 }
318 f.set_return_type(&A::return_type()?);
319 f.set_extra_info(shared);
320 f.set_functions(
321 Some(agg_state_size::<A>),
322 Some(agg_init::<A>),
323 Some(agg_update::<A>),
324 Some(agg_combine::<A>),
325 Some(agg_finalize::<A>),
326 );
327 f.set_destructor(Some(agg_destroy::<A>));
328 if A::special_handling() {
329 f.set_special_handling();
330 }
331 Ok(f)
332}
333
334pub struct AggregateSetBuilder<'a> {
337 set: &'a AggregateFunctionSet,
338 name: &'a str,
339}
340
341impl AggregateSetBuilder<'_> {
342 pub fn add<A: VAggregate>(&mut self) -> Result<&mut Self>
349 where
350 A::Shared: Default,
351 {
352 self.add_with_state::<A>(A::Shared::default())
353 }
354
355 pub fn add_with_state<A: VAggregate>(
361 &mut self,
362 shared: A::Shared,
363 ) -> Result<&mut Self> {
364 let c_name = CString::new(self.name)?;
365 let f = build_aggregate::<A>(&c_name, shared)?;
366 self.set.add_function(&f)?;
367 Ok(self)
368 }
369}
370
371#[cfg(test)]
372mod tests {
373 use super::*;
374 use crate::types::value::DuckValue;
375
376 struct IntSum;
378
379 impl VAggregate for IntSum {
380 type State = i64;
381 type Shared = ();
382
383 fn parameters() -> Result<Vec<LogicalType>> {
384 Ok(vec![LogicalType::of::<i32>()?])
385 }
386
387 fn return_type() -> Result<LogicalType> {
388 LogicalType::of::<i64>()
389 }
390
391 fn init() -> i64 {
392 0
393 }
394
395 fn update(
396 _shared: &(),
397 state: &mut i64,
398 input: &DataChunkHandle,
399 row: usize,
400 ) -> super::super::UdfResult<()> {
401 let v: i32 = input.vector(0)?.get(row)?;
402 *state += i64::from(v);
403 Ok(())
404 }
405
406 fn combine(
407 _shared: &(),
408 source: &i64,
409 target: &mut i64,
410 ) {
411 *target += *source;
412 }
413
414 fn finalize(
415 _shared: &(),
416 state: &i64,
417 output: &mut VectorMut<'_>,
418 row: usize,
419 ) -> super::super::UdfResult<()> {
420 output.set(row, *state)?;
421 Ok(())
422 }
423 }
424
425 #[test]
426 fn aggregate_sums_and_groups() {
427 let mut conn = Connection::open_in_memory().unwrap();
428 conn.register_aggregate_function::<IntSum>("int_sum").unwrap();
429 conn.execute_batch("CREATE TABLE t (k INTEGER, v INTEGER)").unwrap();
430 conn.execute_batch("INSERT INTO t VALUES (1,10),(1,20),(2,5),(2,7),(2,100)").unwrap();
431
432 let total = conn.execute("SELECT int_sum(v) AS s FROM t").unwrap();
434 let rows: Vec<_> = total.collect::<Result<_>>().unwrap();
435 assert_eq!(rows[0].get("s"), Some(&DuckValue::BigInt(142)));
436
437 let mut grouped =
439 conn.execute("SELECT k, int_sum(v) AS s FROM t GROUP BY k ORDER BY k").unwrap();
440 let g0 = grouped.next().unwrap().unwrap();
441 assert_eq!(g0.get("s"), Some(&DuckValue::BigInt(30)));
442 let g1 = grouped.next().unwrap().unwrap();
443 assert_eq!(g1.get("s"), Some(&DuckValue::BigInt(112)));
444 }
445
446 struct BigIntSum;
448
449 impl VAggregate for BigIntSum {
450 type State = i64;
451 type Shared = ();
452
453 fn parameters() -> Result<Vec<LogicalType>> {
454 Ok(vec![LogicalType::of::<i64>()?])
455 }
456
457 fn return_type() -> Result<LogicalType> {
458 LogicalType::of::<i64>()
459 }
460
461 fn init() -> i64 {
462 0
463 }
464
465 fn update(
466 _shared: &(),
467 state: &mut i64,
468 input: &DataChunkHandle,
469 row: usize,
470 ) -> super::super::UdfResult<()> {
471 let v: i64 = input.vector(0)?.get(row)?;
472 *state += v;
473 Ok(())
474 }
475
476 fn combine(
477 _shared: &(),
478 source: &i64,
479 target: &mut i64,
480 ) {
481 *target += *source;
482 }
483
484 fn finalize(
485 _shared: &(),
486 state: &i64,
487 output: &mut VectorMut<'_>,
488 row: usize,
489 ) -> super::super::UdfResult<()> {
490 output.set(row, *state)?;
491 Ok(())
492 }
493 }
494
495 #[test]
496 fn aggregate_set_dispatches_on_argument_type() {
497 let mut conn = Connection::open_in_memory().unwrap();
498 conn.register_aggregate_function_set("my_sum", |b| {
500 b.add::<IntSum>()?;
501 b.add::<BigIntSum>()?;
502 Ok(())
503 })
504 .unwrap();
505
506 let mut r_int = conn
508 .execute("SELECT my_sum(CAST(v AS INTEGER)) AS s FROM (VALUES (1),(2),(3)) t(v)")
509 .unwrap();
510 assert_eq!(r_int.next().unwrap().unwrap().get("s"), Some(&DuckValue::BigInt(6)));
511
512 let mut r_big = conn
514 .execute("SELECT my_sum(CAST(v AS BIGINT)) AS s FROM (VALUES (10),(20)) t(v)")
515 .unwrap();
516 assert_eq!(r_big.next().unwrap().unwrap().get("s"), Some(&DuckValue::BigInt(30)));
517 }
518}