Skip to main content

datafusion_ffi/udaf/
groups_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;
22use std::sync::Arc;
23
24use arrow::array::{Array, ArrayRef, BooleanArray};
25use arrow::error::ArrowError;
26use arrow::ffi::to_ffi;
27use datafusion_common::error::{DataFusionError, Result};
28use datafusion_expr::{EmitTo, GroupsAccumulator};
29
30use stabby::vec::Vec as SVec;
31
32use crate::arrow_wrappers::{WrappedArray, WrappedSchema};
33use crate::util::{FFI_Option, FFI_Result};
34use crate::{df_result, sresult, sresult_return};
35
36/// A stable struct for sharing [`GroupsAccumulator`] across FFI boundaries.
37/// For an explanation of each field, see the corresponding function
38/// defined in [`GroupsAccumulator`].
39#[repr(C)]
40#[derive(Debug)]
41pub struct FFI_GroupsAccumulator {
42    pub update_batch: unsafe extern "C" fn(
43        accumulator: &mut Self,
44        values: SVec<WrappedArray>,
45        group_indices: SVec<usize>,
46        opt_filter: FFI_Option<WrappedArray>,
47        total_num_groups: usize,
48    ) -> FFI_Result<()>,
49
50    // Evaluate and return a ScalarValues as protobuf bytes
51    pub evaluate: unsafe extern "C" fn(
52        accumulator: &mut Self,
53        emit_to: FFI_EmitTo,
54    ) -> FFI_Result<WrappedArray>,
55
56    pub size: unsafe extern "C" fn(accumulator: &Self) -> usize,
57
58    pub state: unsafe extern "C" fn(
59        accumulator: &mut Self,
60        emit_to: FFI_EmitTo,
61    ) -> FFI_Result<SVec<WrappedArray>>,
62
63    pub merge_batch: unsafe extern "C" fn(
64        accumulator: &mut Self,
65        values: SVec<WrappedArray>,
66        group_indices: SVec<usize>,
67        opt_filter: FFI_Option<WrappedArray>,
68        total_num_groups: usize,
69    ) -> FFI_Result<()>,
70
71    pub convert_to_state: unsafe extern "C" fn(
72        accumulator: &Self,
73        values: SVec<WrappedArray>,
74        opt_filter: FFI_Option<WrappedArray>,
75    ) -> FFI_Result<SVec<WrappedArray>>,
76
77    pub supports_convert_to_state: bool,
78
79    /// Release the memory of the private data when it is no longer being used.
80    pub release: unsafe extern "C" fn(accumulator: &mut Self),
81
82    /// Internal data. This is only to be accessed by the provider of the accumulator.
83    /// A [`ForeignGroupsAccumulator`] should never attempt to access this data.
84    pub private_data: *mut c_void,
85
86    /// Utility to identify when FFI objects are accessed locally through
87    /// the foreign interface. See [`crate::get_library_marker_id`] and
88    /// the crate's `README.md` for more information.
89    pub library_marker_id: extern "C" fn() -> usize,
90}
91
92pub struct GroupsAccumulatorPrivateData {
93    pub accumulator: Box<dyn GroupsAccumulator>,
94}
95
96impl FFI_GroupsAccumulator {
97    #[inline]
98    unsafe fn inner_mut(&mut self) -> &mut Box<dyn GroupsAccumulator> {
99        unsafe {
100            let private_data = self.private_data as *mut GroupsAccumulatorPrivateData;
101            &mut (*private_data).accumulator
102        }
103    }
104
105    #[inline]
106    unsafe fn inner(&self) -> &dyn GroupsAccumulator {
107        unsafe {
108            let private_data = self.private_data as *const GroupsAccumulatorPrivateData;
109            (*private_data).accumulator.deref()
110        }
111    }
112}
113
114fn process_values(values: SVec<WrappedArray>) -> Result<Vec<Arc<dyn Array>>> {
115    values
116        .into_iter()
117        .map(|v| v.try_into().map_err(DataFusionError::from))
118        .collect::<Result<Vec<ArrayRef>>>()
119}
120
121/// Convert C-typed opt_filter into the internal type.
122fn process_opt_filter(
123    opt_filter: FFI_Option<WrappedArray>,
124) -> Result<Option<BooleanArray>> {
125    opt_filter
126        .into_option()
127        .map(|filter| {
128            ArrayRef::try_from(filter)
129                .map_err(DataFusionError::from)
130                .map(|arr| BooleanArray::from(arr.into_data()))
131        })
132        .transpose()
133}
134
135unsafe extern "C" fn update_batch_fn_wrapper(
136    accumulator: &mut FFI_GroupsAccumulator,
137    values: SVec<WrappedArray>,
138    group_indices: SVec<usize>,
139    opt_filter: FFI_Option<WrappedArray>,
140    total_num_groups: usize,
141) -> FFI_Result<()> {
142    unsafe {
143        let accumulator = accumulator.inner_mut();
144        let values = sresult_return!(process_values(values));
145        let group_indices: Vec<usize> = group_indices.into_iter().collect();
146        let opt_filter = sresult_return!(process_opt_filter(opt_filter));
147
148        sresult!(accumulator.update_batch(
149            &values,
150            &group_indices,
151            opt_filter.as_ref(),
152            total_num_groups
153        ))
154    }
155}
156
157unsafe extern "C" fn evaluate_fn_wrapper(
158    accumulator: &mut FFI_GroupsAccumulator,
159    emit_to: FFI_EmitTo,
160) -> FFI_Result<WrappedArray> {
161    unsafe {
162        let accumulator = accumulator.inner_mut();
163
164        let result = sresult_return!(accumulator.evaluate(emit_to.into()));
165
166        sresult!(WrappedArray::try_from(&result))
167    }
168}
169
170unsafe extern "C" fn size_fn_wrapper(accumulator: &FFI_GroupsAccumulator) -> usize {
171    unsafe {
172        let accumulator = accumulator.inner();
173        accumulator.size()
174    }
175}
176
177unsafe extern "C" fn state_fn_wrapper(
178    accumulator: &mut FFI_GroupsAccumulator,
179    emit_to: FFI_EmitTo,
180) -> FFI_Result<SVec<WrappedArray>> {
181    unsafe {
182        let accumulator = accumulator.inner_mut();
183
184        let state = sresult_return!(accumulator.state(emit_to.into()));
185        sresult!(
186            state
187                .into_iter()
188                .map(|arr| WrappedArray::try_from(&arr).map_err(DataFusionError::from))
189                .collect::<Result<SVec<_>>>()
190        )
191    }
192}
193
194unsafe extern "C" fn merge_batch_fn_wrapper(
195    accumulator: &mut FFI_GroupsAccumulator,
196    values: SVec<WrappedArray>,
197    group_indices: SVec<usize>,
198    opt_filter: FFI_Option<WrappedArray>,
199    total_num_groups: usize,
200) -> FFI_Result<()> {
201    unsafe {
202        let accumulator = accumulator.inner_mut();
203        let values = sresult_return!(process_values(values));
204        let group_indices: Vec<usize> = group_indices.into_iter().collect();
205        let opt_filter = sresult_return!(process_opt_filter(opt_filter));
206
207        sresult!(accumulator.merge_batch(
208            &values,
209            &group_indices,
210            opt_filter.as_ref(),
211            total_num_groups
212        ))
213    }
214}
215
216unsafe extern "C" fn convert_to_state_fn_wrapper(
217    accumulator: &FFI_GroupsAccumulator,
218    values: SVec<WrappedArray>,
219    opt_filter: FFI_Option<WrappedArray>,
220) -> FFI_Result<SVec<WrappedArray>> {
221    unsafe {
222        let accumulator = accumulator.inner();
223        let values = sresult_return!(process_values(values));
224        let opt_filter = sresult_return!(process_opt_filter(opt_filter));
225        let state =
226            sresult_return!(accumulator.convert_to_state(&values, opt_filter.as_ref()));
227
228        sresult!(
229            state
230                .iter()
231                .map(|arr| WrappedArray::try_from(arr).map_err(DataFusionError::from))
232                .collect::<Result<SVec<_>>>()
233        )
234    }
235}
236
237unsafe extern "C" fn release_fn_wrapper(accumulator: &mut FFI_GroupsAccumulator) {
238    unsafe {
239        if !accumulator.private_data.is_null() {
240            let private_data = Box::from_raw(
241                accumulator.private_data as *mut GroupsAccumulatorPrivateData,
242            );
243            drop(private_data);
244            accumulator.private_data = null_mut();
245        }
246    }
247}
248
249impl From<Box<dyn GroupsAccumulator>> for FFI_GroupsAccumulator {
250    fn from(accumulator: Box<dyn GroupsAccumulator>) -> Self {
251        if (accumulator.as_ref() as &dyn Any).is::<ForeignGroupsAccumulator>() {
252            let accumulator = (accumulator as Box<dyn Any>)
253                .downcast::<ForeignGroupsAccumulator>()
254                .expect("already checked type");
255            return accumulator.accumulator;
256        }
257
258        let supports_convert_to_state = accumulator.supports_convert_to_state();
259        let private_data = GroupsAccumulatorPrivateData { accumulator };
260
261        Self {
262            update_batch: update_batch_fn_wrapper,
263            evaluate: evaluate_fn_wrapper,
264            size: size_fn_wrapper,
265            state: state_fn_wrapper,
266            merge_batch: merge_batch_fn_wrapper,
267            convert_to_state: convert_to_state_fn_wrapper,
268            supports_convert_to_state,
269
270            release: release_fn_wrapper,
271            private_data: Box::into_raw(Box::new(private_data)) as *mut c_void,
272            library_marker_id: crate::get_library_marker_id,
273        }
274    }
275}
276
277impl Drop for FFI_GroupsAccumulator {
278    fn drop(&mut self) {
279        unsafe { (self.release)(self) }
280    }
281}
282
283/// This struct is used to access an UDF provided by a foreign
284/// library across a FFI boundary.
285///
286/// The ForeignGroupsAccumulator is to be used by the caller of the UDF, so it has
287/// no knowledge or access to the private data. All interaction with the UDF
288/// must occur through the functions defined in FFI_GroupsAccumulator.
289#[derive(Debug)]
290pub struct ForeignGroupsAccumulator {
291    accumulator: FFI_GroupsAccumulator,
292}
293
294unsafe impl Send for ForeignGroupsAccumulator {}
295unsafe impl Sync for ForeignGroupsAccumulator {}
296
297impl From<FFI_GroupsAccumulator> for Box<dyn GroupsAccumulator> {
298    fn from(mut accumulator: FFI_GroupsAccumulator) -> Self {
299        if (accumulator.library_marker_id)() == crate::get_library_marker_id() {
300            unsafe {
301                let private_data = Box::from_raw(
302                    accumulator.private_data as *mut GroupsAccumulatorPrivateData,
303                );
304                // We must set this to null to avoid a double free
305                accumulator.private_data = null_mut();
306                private_data.accumulator
307            }
308        } else {
309            Box::new(ForeignGroupsAccumulator { accumulator })
310        }
311    }
312}
313
314impl GroupsAccumulator for ForeignGroupsAccumulator {
315    fn update_batch(
316        &mut self,
317        values: &[ArrayRef],
318        group_indices: &[usize],
319        opt_filter: Option<&BooleanArray>,
320        total_num_groups: usize,
321    ) -> Result<()> {
322        unsafe {
323            let values = values
324                .iter()
325                .map(WrappedArray::try_from)
326                .collect::<std::result::Result<Vec<_>, ArrowError>>()?;
327            let group_indices = group_indices.iter().cloned().collect();
328            let opt_filter = opt_filter
329                .map(|bool_array| to_ffi(&bool_array.to_data()))
330                .transpose()?
331                .map(|(array, schema)| WrappedArray {
332                    array,
333                    schema: WrappedSchema(schema),
334                })
335                .into();
336
337            df_result!((self.accumulator.update_batch)(
338                &mut self.accumulator,
339                values.into_iter().collect(),
340                group_indices,
341                opt_filter,
342                total_num_groups
343            ))
344        }
345    }
346
347    fn size(&self) -> usize {
348        unsafe { (self.accumulator.size)(&self.accumulator) }
349    }
350
351    fn evaluate(&mut self, emit_to: EmitTo) -> Result<ArrayRef> {
352        unsafe {
353            let return_array = df_result!((self.accumulator.evaluate)(
354                &mut self.accumulator,
355                emit_to.into()
356            ))?;
357
358            return_array.try_into().map_err(DataFusionError::from)
359        }
360    }
361
362    fn state(&mut self, emit_to: EmitTo) -> Result<Vec<ArrayRef>> {
363        unsafe {
364            let returned_arrays = df_result!((self.accumulator.state)(
365                &mut self.accumulator,
366                emit_to.into()
367            ))?;
368
369            returned_arrays
370                .into_iter()
371                .map(|wrapped_array| {
372                    wrapped_array.try_into().map_err(DataFusionError::from)
373                })
374                .collect::<Result<Vec<_>>>()
375        }
376    }
377
378    fn merge_batch(
379        &mut self,
380        values: &[ArrayRef],
381        group_indices: &[usize],
382        opt_filter: Option<&BooleanArray>,
383        total_num_groups: usize,
384    ) -> Result<()> {
385        unsafe {
386            let values = values
387                .iter()
388                .map(WrappedArray::try_from)
389                .collect::<std::result::Result<Vec<_>, ArrowError>>()?;
390            let group_indices = group_indices.iter().cloned().collect();
391            let opt_filter = opt_filter
392                .map(|bool_array| to_ffi(&bool_array.to_data()))
393                .transpose()?
394                .map(|(array, schema)| WrappedArray {
395                    array,
396                    schema: WrappedSchema(schema),
397                })
398                .into();
399
400            df_result!((self.accumulator.merge_batch)(
401                &mut self.accumulator,
402                values.into_iter().collect(),
403                group_indices,
404                opt_filter,
405                total_num_groups
406            ))
407        }
408    }
409
410    fn convert_to_state(
411        &self,
412        values: &[ArrayRef],
413        opt_filter: Option<&BooleanArray>,
414    ) -> Result<Vec<ArrayRef>> {
415        unsafe {
416            let values = values
417                .iter()
418                .map(WrappedArray::try_from)
419                .collect::<std::result::Result<SVec<_>, ArrowError>>()?;
420
421            let opt_filter = opt_filter
422                .map(|bool_array| to_ffi(&bool_array.to_data()))
423                .transpose()?
424                .map(|(array, schema)| WrappedArray {
425                    array,
426                    schema: WrappedSchema(schema),
427                })
428                .into();
429
430            let returned_array = df_result!((self.accumulator.convert_to_state)(
431                &self.accumulator,
432                values,
433                opt_filter
434            ))?;
435
436            returned_array
437                .into_iter()
438                .map(|arr| arr.try_into().map_err(DataFusionError::from))
439                .collect()
440        }
441    }
442
443    fn supports_convert_to_state(&self) -> bool {
444        self.accumulator.supports_convert_to_state
445    }
446}
447
448#[repr(C)]
449#[derive(Debug)]
450pub enum FFI_EmitTo {
451    All,
452    First(usize),
453}
454
455impl From<EmitTo> for FFI_EmitTo {
456    fn from(value: EmitTo) -> Self {
457        match value {
458            EmitTo::All => Self::All,
459            EmitTo::First(v) => Self::First(v),
460        }
461    }
462}
463
464impl From<FFI_EmitTo> for EmitTo {
465    fn from(value: FFI_EmitTo) -> Self {
466        match value {
467            FFI_EmitTo::All => Self::All,
468            FFI_EmitTo::First(v) => Self::First(v),
469        }
470    }
471}
472
473#[cfg(test)]
474mod tests {
475    use arrow::array::{Array, BooleanArray, make_array};
476    use datafusion::common::create_array;
477    use datafusion::error::Result;
478    use datafusion::functions_aggregate::stddev::StddevGroupsAccumulator;
479    use datafusion::logical_expr::{EmitTo, GroupsAccumulator};
480    use datafusion_functions_aggregate_common::aggregate::groups_accumulator::bool_op::BooleanGroupsAccumulator;
481    use datafusion_functions_aggregate_common::stats::StatsType;
482
483    use super::{FFI_EmitTo, FFI_GroupsAccumulator, ForeignGroupsAccumulator};
484
485    #[test]
486    fn test_foreign_avg_accumulator() -> Result<()> {
487        let boxed_accum: Box<dyn GroupsAccumulator> =
488            Box::new(BooleanGroupsAccumulator::new(|a, b| a && b, true));
489        let mut ffi_accum: FFI_GroupsAccumulator = boxed_accum.into();
490        ffi_accum.library_marker_id = crate::mock_foreign_marker_id;
491        let mut foreign_accum: Box<dyn GroupsAccumulator> = ffi_accum.into();
492
493        // Send in an array to evaluate. We want a mean of 30 and standard deviation of 4.
494        let values = create_array!(Boolean, vec![true, true, true, false, true, true]);
495        let opt_filter =
496            create_array!(Boolean, vec![true, true, true, true, false, false]);
497        foreign_accum.update_batch(
498            &[values],
499            &[0, 0, 1, 1, 2, 2],
500            Some(opt_filter.as_ref()),
501            3,
502        )?;
503
504        let groups_bool = foreign_accum.evaluate(EmitTo::All)?;
505        let groups_bool = groups_bool.as_any().downcast_ref::<BooleanArray>().unwrap();
506
507        assert_eq!(
508            groups_bool,
509            create_array!(Boolean, vec![Some(true), Some(false), None]).as_ref()
510        );
511
512        let state = foreign_accum.state(EmitTo::All)?;
513        assert_eq!(state.len(), 1);
514
515        // To verify merging batches works, create a second state to add in
516        // This should cause our average to go down to 25.0
517        let second_states =
518            vec![make_array(create_array!(Boolean, vec![false]).to_data())];
519
520        let opt_filter = create_array!(Boolean, vec![true]);
521        foreign_accum.merge_batch(&second_states, &[0], Some(opt_filter.as_ref()), 1)?;
522        let groups_bool = foreign_accum.evaluate(EmitTo::All)?;
523        assert_eq!(groups_bool.len(), 1);
524        assert_eq!(
525            groups_bool.as_ref(),
526            make_array(create_array!(Boolean, vec![false]).to_data()).as_ref()
527        );
528
529        let values = create_array!(Boolean, vec![false]);
530        let opt_filter = create_array!(Boolean, vec![true]);
531        let groups_bool =
532            foreign_accum.convert_to_state(&[values], Some(opt_filter.as_ref()))?;
533
534        assert_eq!(
535            groups_bool[0].as_ref(),
536            make_array(create_array!(Boolean, vec![false]).to_data()).as_ref()
537        );
538
539        Ok(())
540    }
541
542    fn test_emit_to_round_trip(value: EmitTo) -> Result<()> {
543        let ffi_value: FFI_EmitTo = value.into();
544        let round_trip_value: EmitTo = ffi_value.into();
545
546        assert_eq!(value, round_trip_value);
547        Ok(())
548    }
549
550    /// This test ensures all enum values are properly translated
551    #[test]
552    fn test_all_emit_to_round_trip() -> Result<()> {
553        test_emit_to_round_trip(EmitTo::All)?;
554        test_emit_to_round_trip(EmitTo::First(10))?;
555
556        Ok(())
557    }
558
559    #[test]
560    fn test_ffi_groups_accumulator_local_bypass_inner() -> Result<()> {
561        let original_accum = StddevGroupsAccumulator::new(StatsType::Population);
562        let boxed_accum: Box<dyn GroupsAccumulator> = Box::new(original_accum);
563        let original_size = boxed_accum.size();
564
565        let ffi_accum: FFI_GroupsAccumulator = boxed_accum.into();
566
567        // Verify local libraries can be downcast to their original
568        let foreign_accum: Box<dyn GroupsAccumulator> = ffi_accum.into();
569        unsafe {
570            let concrete = &*(foreign_accum.as_ref() as *const dyn GroupsAccumulator
571                as *const StddevGroupsAccumulator);
572            assert_eq!(original_size, concrete.size());
573        }
574
575        // Verify different library markers generate foreign accumulator
576        let original_accum = StddevGroupsAccumulator::new(StatsType::Population);
577        let boxed_accum: Box<dyn GroupsAccumulator> = Box::new(original_accum);
578        let mut ffi_accum: FFI_GroupsAccumulator = boxed_accum.into();
579        ffi_accum.library_marker_id = crate::mock_foreign_marker_id;
580        let foreign_accum: Box<dyn GroupsAccumulator> = ffi_accum.into();
581        unsafe {
582            let concrete = &*(foreign_accum.as_ref() as *const dyn GroupsAccumulator
583                as *const ForeignGroupsAccumulator);
584            assert_eq!(original_size, concrete.size());
585        }
586
587        Ok(())
588    }
589}