Skip to main content

datafusion_ffi/udaf/
accumulator.rs

1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements.  See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership.  The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License.  You may obtain a copy of the License at
8//
9//   http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied.  See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18use 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/// A stable struct for sharing [`Accumulator`] across FFI boundaries.
37/// For an explanation of each field, see the corresponding function
38/// defined in [`Accumulator`].
39#[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    // Evaluate and return a ScalarValues as protobuf bytes
48    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    /// Release the memory of the private data when it is no longer being used.
67    pub release: unsafe extern "C" fn(accumulator: &mut Self),
68
69    /// Internal data. This is only to be accessed by the provider of the accumulator.
70    /// A [`ForeignAccumulator`] should never attempt to access this data.
71    pub private_data: *mut c_void,
72
73    /// Utility to identify when FFI objects are accessed locally through
74    /// the foreign interface. See [`crate::get_library_marker_id`] and
75    /// the crate's `README.md` for more information.
76    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/// This struct is used to access an UDF provided by a foreign
240/// library across a FFI boundary.
241///
242/// The ForeignAccumulator is to be used by the caller of the UDF, so it has
243/// no knowledge or access to the private data. All interaction with the UDF
244/// must occur through the functions defined in FFI_Accumulator.
245#[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                // We must set this to null to avoid a double free
258                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        // Send in an array to average. There are 5 values and it should average to 30.0
371        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        // To verify merging batches works, create a second state to add in
383        // This should cause our average to go down to 25.0
384        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        // If we remove a batch that is equivalent to the state we added
394        // we should go back to our original value of 30.0
395        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        // Verify local libraries can be downcast to their original
418        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        // Verify different library markers generate foreign accumulator
426        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}