datafusion_ffi/udaf/
accumulator.rs1use std::any::Any;
19use std::ffi::c_void;
20use std::ops::Deref;
21use std::ptr::null_mut;
22
23use arrow::array::ArrayRef;
24use arrow::error::ArrowError;
25use datafusion_common::error::{DataFusionError, Result};
26use datafusion_common::scalar::ScalarValue;
27use datafusion_expr::Accumulator;
28use prost::Message;
29
30use stabby::vec::Vec as SVec;
31
32use crate::arrow_wrappers::WrappedArray;
33use crate::util::FFI_Result;
34use crate::{df_result, sresult, sresult_return};
35
36#[repr(C)]
40#[derive(Debug)]
41pub struct FFI_Accumulator {
42 pub update_batch: unsafe extern "C" fn(
43 accumulator: &mut Self,
44 values: SVec<WrappedArray>,
45 ) -> FFI_Result<()>,
46
47 pub evaluate: unsafe extern "C" fn(accumulator: &mut Self) -> FFI_Result<SVec<u8>>,
49
50 pub size: unsafe extern "C" fn(accumulator: &Self) -> usize,
51
52 pub state: unsafe extern "C" fn(accumulator: &mut Self) -> FFI_Result<SVec<SVec<u8>>>,
53
54 pub merge_batch: unsafe extern "C" fn(
55 accumulator: &mut Self,
56 states: SVec<WrappedArray>,
57 ) -> FFI_Result<()>,
58
59 pub retract_batch: unsafe extern "C" fn(
60 accumulator: &mut Self,
61 values: SVec<WrappedArray>,
62 ) -> FFI_Result<()>,
63
64 pub supports_retract_batch: bool,
65
66 pub release: unsafe extern "C" fn(accumulator: &mut Self),
68
69 pub private_data: *mut c_void,
72
73 pub library_marker_id: extern "C" fn() -> usize,
77}
78
79unsafe impl Send for FFI_Accumulator {}
80unsafe impl Sync for FFI_Accumulator {}
81
82pub struct AccumulatorPrivateData {
83 pub accumulator: Box<dyn Accumulator>,
84}
85
86impl FFI_Accumulator {
87 #[inline]
88 unsafe fn inner_mut(&mut self) -> &mut Box<dyn Accumulator> {
89 unsafe {
90 let private_data = self.private_data as *mut AccumulatorPrivateData;
91 &mut (*private_data).accumulator
92 }
93 }
94
95 #[inline]
96 unsafe fn inner(&self) -> &dyn Accumulator {
97 unsafe {
98 let private_data = self.private_data as *const AccumulatorPrivateData;
99 (*private_data).accumulator.deref()
100 }
101 }
102}
103
104unsafe extern "C" fn update_batch_fn_wrapper(
105 accumulator: &mut FFI_Accumulator,
106 values: SVec<WrappedArray>,
107) -> FFI_Result<()> {
108 unsafe {
109 let accumulator = accumulator.inner_mut();
110
111 let values_arrays = values
112 .into_iter()
113 .map(|v| v.try_into().map_err(DataFusionError::from))
114 .collect::<Result<Vec<ArrayRef>>>();
115 let values_arrays = sresult_return!(values_arrays);
116
117 sresult!(accumulator.update_batch(&values_arrays))
118 }
119}
120
121unsafe extern "C" fn evaluate_fn_wrapper(
122 accumulator: &mut FFI_Accumulator,
123) -> FFI_Result<SVec<u8>> {
124 unsafe {
125 let accumulator = accumulator.inner_mut();
126
127 let scalar_result = sresult_return!(accumulator.evaluate());
128 let proto_result: datafusion_proto::protobuf::ScalarValue =
129 sresult_return!((&scalar_result).try_into());
130
131 FFI_Result::Ok(proto_result.encode_to_vec().into_iter().collect())
132 }
133}
134
135unsafe extern "C" fn size_fn_wrapper(accumulator: &FFI_Accumulator) -> usize {
136 unsafe { accumulator.inner().size() }
137}
138
139unsafe extern "C" fn state_fn_wrapper(
140 accumulator: &mut FFI_Accumulator,
141) -> FFI_Result<SVec<SVec<u8>>> {
142 unsafe {
143 let accumulator = accumulator.inner_mut();
144
145 let state = sresult_return!(accumulator.state());
146 let state = state
147 .into_iter()
148 .map(|state_val| {
149 datafusion_proto::protobuf::ScalarValue::try_from(&state_val)
150 .map_err(DataFusionError::from)
151 .map(|v| v.encode_to_vec().into_iter().collect::<SVec<u8>>())
152 })
153 .collect::<Result<Vec<_>>>()
154 .map(|state_vec| state_vec.into_iter().collect());
155
156 sresult!(state)
157 }
158}
159
160unsafe extern "C" fn merge_batch_fn_wrapper(
161 accumulator: &mut FFI_Accumulator,
162 states: SVec<WrappedArray>,
163) -> FFI_Result<()> {
164 unsafe {
165 let accumulator = accumulator.inner_mut();
166
167 let states = sresult_return!(
168 states
169 .into_iter()
170 .map(|state| ArrayRef::try_from(state).map_err(DataFusionError::from))
171 .collect::<Result<Vec<_>>>()
172 );
173
174 sresult!(accumulator.merge_batch(&states))
175 }
176}
177
178unsafe extern "C" fn retract_batch_fn_wrapper(
179 accumulator: &mut FFI_Accumulator,
180 values: SVec<WrappedArray>,
181) -> FFI_Result<()> {
182 unsafe {
183 let accumulator = accumulator.inner_mut();
184
185 let values_arrays = values
186 .into_iter()
187 .map(|v| v.try_into().map_err(DataFusionError::from))
188 .collect::<Result<Vec<ArrayRef>>>();
189 let values_arrays = sresult_return!(values_arrays);
190
191 sresult!(accumulator.retract_batch(&values_arrays))
192 }
193}
194
195unsafe extern "C" fn release_fn_wrapper(accumulator: &mut FFI_Accumulator) {
196 unsafe {
197 if !accumulator.private_data.is_null() {
198 let private_data =
199 Box::from_raw(accumulator.private_data as *mut AccumulatorPrivateData);
200 drop(private_data);
201 accumulator.private_data = null_mut();
202 }
203 }
204}
205
206impl From<Box<dyn Accumulator>> for FFI_Accumulator {
207 fn from(accumulator: Box<dyn Accumulator>) -> Self {
208 if (accumulator.as_ref() as &dyn Any).is::<ForeignAccumulator>() {
209 let accumulator = (accumulator as Box<dyn Any>)
210 .downcast::<ForeignAccumulator>()
211 .expect("already checked type");
212 return accumulator.accumulator;
213 }
214
215 let supports_retract_batch = accumulator.supports_retract_batch();
216 let private_data = AccumulatorPrivateData { accumulator };
217
218 Self {
219 update_batch: update_batch_fn_wrapper,
220 evaluate: evaluate_fn_wrapper,
221 size: size_fn_wrapper,
222 state: state_fn_wrapper,
223 merge_batch: merge_batch_fn_wrapper,
224 retract_batch: retract_batch_fn_wrapper,
225 supports_retract_batch,
226 release: release_fn_wrapper,
227 private_data: Box::into_raw(Box::new(private_data)) as *mut c_void,
228 library_marker_id: crate::get_library_marker_id,
229 }
230 }
231}
232
233impl Drop for FFI_Accumulator {
234 fn drop(&mut self) {
235 unsafe { (self.release)(self) }
236 }
237}
238
239#[derive(Debug)]
246pub struct ForeignAccumulator {
247 accumulator: FFI_Accumulator,
248}
249
250impl From<FFI_Accumulator> for Box<dyn Accumulator> {
251 fn from(mut accumulator: FFI_Accumulator) -> Self {
252 if (accumulator.library_marker_id)() == crate::get_library_marker_id() {
253 unsafe {
254 let private_data = Box::from_raw(
255 accumulator.private_data as *mut AccumulatorPrivateData,
256 );
257 accumulator.private_data = null_mut();
259 private_data.accumulator
260 }
261 } else {
262 Box::new(ForeignAccumulator { accumulator })
263 }
264 }
265}
266
267impl Accumulator for ForeignAccumulator {
268 fn update_batch(&mut self, values: &[ArrayRef]) -> Result<()> {
269 unsafe {
270 let values = values
271 .iter()
272 .map(WrappedArray::try_from)
273 .collect::<std::result::Result<Vec<_>, ArrowError>>()?;
274 df_result!((self.accumulator.update_batch)(
275 &mut self.accumulator,
276 values.into_iter().collect()
277 ))
278 }
279 }
280
281 fn evaluate(&mut self) -> Result<ScalarValue> {
282 unsafe {
283 let scalar_bytes =
284 df_result!((self.accumulator.evaluate)(&mut self.accumulator))?;
285
286 let proto_scalar =
287 datafusion_proto::protobuf::ScalarValue::decode(scalar_bytes.as_ref())
288 .map_err(|e| DataFusionError::External(Box::new(e)))?;
289
290 ScalarValue::try_from(&proto_scalar).map_err(DataFusionError::from)
291 }
292 }
293
294 fn size(&self) -> usize {
295 unsafe { (self.accumulator.size)(&self.accumulator) }
296 }
297
298 fn state(&mut self) -> Result<Vec<ScalarValue>> {
299 unsafe {
300 let state_protos =
301 df_result!((self.accumulator.state)(&mut self.accumulator))?;
302
303 state_protos
304 .into_iter()
305 .map(|proto_bytes| {
306 datafusion_proto::protobuf::ScalarValue::decode(proto_bytes.as_ref())
307 .map_err(|e| DataFusionError::External(Box::new(e)))
308 .and_then(|proto_value| {
309 ScalarValue::try_from(&proto_value)
310 .map_err(DataFusionError::from)
311 })
312 })
313 .collect::<Result<Vec<_>>>()
314 }
315 }
316
317 fn merge_batch(&mut self, states: &[ArrayRef]) -> Result<()> {
318 unsafe {
319 let states = states
320 .iter()
321 .map(WrappedArray::try_from)
322 .collect::<std::result::Result<Vec<_>, ArrowError>>()?;
323 df_result!((self.accumulator.merge_batch)(
324 &mut self.accumulator,
325 states.into_iter().collect()
326 ))
327 }
328 }
329
330 fn retract_batch(&mut self, values: &[ArrayRef]) -> Result<()> {
331 unsafe {
332 let values = values
333 .iter()
334 .map(WrappedArray::try_from)
335 .collect::<std::result::Result<Vec<_>, ArrowError>>()?;
336 df_result!((self.accumulator.retract_batch)(
337 &mut self.accumulator,
338 values.into_iter().collect()
339 ))
340 }
341 }
342
343 fn supports_retract_batch(&self) -> bool {
344 self.accumulator.supports_retract_batch
345 }
346}
347
348#[cfg(test)]
349mod tests {
350 use arrow::array::{Array, make_array};
351 use datafusion::common::create_array;
352 use datafusion::error::Result;
353 use datafusion::functions_aggregate::average::AvgAccumulator;
354 use datafusion::logical_expr::Accumulator;
355 use datafusion::scalar::ScalarValue;
356
357 use super::{FFI_Accumulator, ForeignAccumulator};
358
359 #[test]
360 fn test_foreign_avg_accumulator() -> Result<()> {
361 let original_accum = AvgAccumulator::default();
362 let original_size = original_accum.size();
363 let original_supports_retract = original_accum.supports_retract_batch();
364
365 let boxed_accum: Box<dyn Accumulator> = Box::new(original_accum);
366 let mut ffi_accum: FFI_Accumulator = boxed_accum.into();
367 ffi_accum.library_marker_id = crate::mock_foreign_marker_id;
368 let mut foreign_accum: Box<dyn Accumulator> = ffi_accum.into();
369
370 let values = create_array!(Float64, vec![10., 20., 30., 40., 50.]);
372 foreign_accum.update_batch(&[values])?;
373
374 let avg = foreign_accum.evaluate()?;
375 assert_eq!(avg, ScalarValue::Float64(Some(30.0)));
376
377 let state = foreign_accum.state()?;
378 assert_eq!(state.len(), 2);
379 assert_eq!(state[0], ScalarValue::UInt64(Some(5)));
380 assert_eq!(state[1], ScalarValue::Float64(Some(150.0)));
381
382 let second_states = vec![
385 make_array(create_array!(UInt64, vec![1]).to_data()),
386 make_array(create_array!(Float64, vec![0.0]).to_data()),
387 ];
388
389 foreign_accum.merge_batch(&second_states)?;
390 let avg = foreign_accum.evaluate()?;
391 assert_eq!(avg, ScalarValue::Float64(Some(25.0)));
392
393 let values = create_array!(Float64, vec![0.0]);
396 foreign_accum.retract_batch(&[values])?;
397 let avg = foreign_accum.evaluate()?;
398 assert_eq!(avg, ScalarValue::Float64(Some(30.0)));
399
400 assert_eq!(original_size, foreign_accum.size());
401 assert_eq!(
402 original_supports_retract,
403 foreign_accum.supports_retract_batch()
404 );
405
406 Ok(())
407 }
408
409 #[test]
410 fn test_ffi_accumulator_local_bypass() -> Result<()> {
411 let original_accum = AvgAccumulator::default();
412 let boxed_accum: Box<dyn Accumulator> = Box::new(original_accum);
413 let original_size = boxed_accum.size();
414
415 let ffi_accum: FFI_Accumulator = boxed_accum.into();
416
417 let foreign_accum: Box<dyn Accumulator> = ffi_accum.into();
419 unsafe {
420 let concrete = &*(foreign_accum.as_ref() as *const dyn Accumulator
421 as *const AvgAccumulator);
422 assert_eq!(original_size, concrete.size());
423 }
424
425 let original_accum = AvgAccumulator::default();
427 let boxed_accum: Box<dyn Accumulator> = Box::new(original_accum);
428 let mut ffi_accum: FFI_Accumulator = boxed_accum.into();
429 ffi_accum.library_marker_id = crate::mock_foreign_marker_id;
430 let foreign_accum: Box<dyn Accumulator> = ffi_accum.into();
431 unsafe {
432 let concrete = &*(foreign_accum.as_ref() as *const dyn Accumulator
433 as *const ForeignAccumulator);
434 assert_eq!(original_size, concrete.size());
435 }
436
437 Ok(())
438 }
439}