Skip to main content

datafusion_ffi/udaf/
mod.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 accumulator::FFI_Accumulator;
19use accumulator_args::{FFI_AccumulatorArgs, ForeignAccumulatorArgs};
20use arrow::datatypes::{DataType, Field};
21use arrow::ffi::FFI_ArrowSchema;
22use arrow_schema::FieldRef;
23use datafusion_common::{DataFusionError, Result, ffi_datafusion_err};
24use datafusion_expr::function::AggregateFunctionSimplification;
25use datafusion_expr::type_coercion::functions::fields_with_udf;
26use datafusion_expr::{
27    Accumulator, AggregateUDF, AggregateUDFImpl, GroupsAccumulator, Signature,
28};
29use datafusion_functions_aggregate_common::accumulator::{
30    AccumulatorArgs, StateFieldsArgs,
31};
32use datafusion_functions_aggregate_common::order::AggregateOrderSensitivity;
33use datafusion_proto_common::from_proto::parse_proto_fields_to_fields;
34use groups_accumulator::FFI_GroupsAccumulator;
35use prost::{DecodeError, Message};
36
37use stabby::str::Str as SStr;
38use stabby::string::String as SString;
39use stabby::vec::Vec as SVec;
40use std::ffi::c_void;
41use std::hash::{Hash, Hasher};
42use std::sync::Arc;
43
44use crate::arrow_wrappers::WrappedSchema;
45use crate::util::{
46    FFI_Option, FFI_Result, rvec_wrapped_to_vec_datatype, rvec_wrapped_to_vec_fieldref,
47    vec_datatype_to_rvec_wrapped, vec_fieldref_to_rvec_wrapped,
48};
49use crate::volatility::FFI_Volatility;
50use crate::{df_result, sresult, sresult_return};
51
52mod accumulator;
53mod accumulator_args;
54mod groups_accumulator;
55
56/// A stable struct for sharing a [`AggregateUDF`] across FFI boundaries.
57#[repr(C)]
58#[derive(Debug)]
59pub struct FFI_AggregateUDF {
60    /// FFI equivalent to the `name` of a [`AggregateUDF`]
61    pub name: SString,
62
63    /// FFI equivalent to the `aliases` of a [`AggregateUDF`]
64    pub aliases: SVec<SString>,
65
66    /// FFI equivalent to the `volatility` of a [`AggregateUDF`]
67    pub volatility: FFI_Volatility,
68
69    /// Determines the return field of the underlying [`AggregateUDF`] based on the
70    /// argument fields.
71    pub return_field: unsafe extern "C" fn(
72        udaf: &Self,
73        arg_fields: SVec<WrappedSchema>,
74    ) -> FFI_Result<WrappedSchema>,
75
76    /// FFI equivalent to the `is_nullable` of a [`AggregateUDF`]
77    pub is_nullable: bool,
78
79    /// FFI equivalent to [`AggregateUDF::groups_accumulator_supported`]
80    pub groups_accumulator_supported:
81        unsafe extern "C" fn(udaf: &FFI_AggregateUDF, args: FFI_AccumulatorArgs) -> bool,
82
83    /// FFI equivalent to [`AggregateUDF::accumulator`]
84    pub accumulator: unsafe extern "C" fn(
85        udaf: &FFI_AggregateUDF,
86        args: FFI_AccumulatorArgs,
87    ) -> FFI_Result<FFI_Accumulator>,
88
89    /// FFI equivalent to [`AggregateUDF::create_sliding_accumulator`]
90    pub create_sliding_accumulator: unsafe extern "C" fn(
91        udaf: &FFI_AggregateUDF,
92        args: FFI_AccumulatorArgs,
93    )
94        -> FFI_Result<FFI_Accumulator>,
95
96    /// FFI equivalent to [`AggregateUDF::state_fields`]
97    pub state_fields: unsafe extern "C" fn(
98        udaf: &FFI_AggregateUDF,
99        name: &SStr,
100        input_fields: SVec<WrappedSchema>,
101        return_field: WrappedSchema,
102        ordering_fields: SVec<SVec<u8>>,
103        is_distinct: bool,
104    ) -> FFI_Result<SVec<SVec<u8>>>,
105
106    /// FFI equivalent to [`AggregateUDF::create_groups_accumulator`]
107    pub create_groups_accumulator:
108        unsafe extern "C" fn(
109            udaf: &FFI_AggregateUDF,
110            args: FFI_AccumulatorArgs,
111        ) -> FFI_Result<FFI_GroupsAccumulator>,
112
113    /// FFI equivalent to [`AggregateUDF::with_beneficial_ordering`]
114    pub with_beneficial_ordering:
115        unsafe extern "C" fn(
116            udaf: &FFI_AggregateUDF,
117            beneficial_ordering: bool,
118        ) -> FFI_Result<FFI_Option<FFI_AggregateUDF>>,
119
120    /// FFI equivalent to [`AggregateUDF::order_sensitivity`]
121    pub order_sensitivity:
122        unsafe extern "C" fn(udaf: &FFI_AggregateUDF) -> FFI_AggregateOrderSensitivity,
123
124    /// Performs type coercion. To simply this interface, all UDFs are treated as having
125    /// user defined signatures, which will in turn call coerce_types to be called. This
126    /// call should be transparent to most users as the internal function performs the
127    /// appropriate calls on the underlying [`AggregateUDF`]
128    pub coerce_types: unsafe extern "C" fn(
129        udf: &Self,
130        arg_types: SVec<WrappedSchema>,
131    ) -> FFI_Result<SVec<WrappedSchema>>,
132
133    /// Used to create a clone on the provider of the udaf. This should
134    /// only need to be called by the receiver of the udaf.
135    pub clone: unsafe extern "C" fn(udaf: &Self) -> Self,
136
137    /// Release the memory of the private data when it is no longer being used.
138    pub release: unsafe extern "C" fn(udaf: &mut Self),
139
140    /// Internal data. This is only to be accessed by the provider of the udaf.
141    /// A [`ForeignAggregateUDF`] should never attempt to access this data.
142    pub private_data: *mut c_void,
143
144    /// Utility to identify when FFI objects are accessed locally through
145    /// the foreign interface. See [`crate::get_library_marker_id`] and
146    /// the crate's `README.md` for more information.
147    pub library_marker_id: extern "C" fn() -> usize,
148
149    /// FFI equivalent to [`AggregateUDF::supports_null_handling_clause`]
150    pub supports_null_handling_clause:
151        unsafe extern "C" fn(udaf: &FFI_AggregateUDF) -> bool,
152}
153
154unsafe impl Send for FFI_AggregateUDF {}
155unsafe impl Sync for FFI_AggregateUDF {}
156
157pub struct AggregateUDFPrivateData {
158    pub udaf: Arc<AggregateUDF>,
159}
160
161impl FFI_AggregateUDF {
162    unsafe fn inner(&self) -> &Arc<AggregateUDF> {
163        unsafe {
164            let private_data = self.private_data as *const AggregateUDFPrivateData;
165            &(*private_data).udaf
166        }
167    }
168}
169
170unsafe extern "C" fn return_field_fn_wrapper(
171    udaf: &FFI_AggregateUDF,
172    arg_fields: SVec<WrappedSchema>,
173) -> FFI_Result<WrappedSchema> {
174    unsafe {
175        let udaf = udaf.inner();
176
177        let arg_fields = sresult_return!(rvec_wrapped_to_vec_fieldref(&arg_fields));
178
179        let return_field = udaf
180            .return_field(&arg_fields)
181            .and_then(|v| {
182                FFI_ArrowSchema::try_from(v.as_ref()).map_err(DataFusionError::from)
183            })
184            .map(WrappedSchema);
185
186        sresult!(return_field)
187    }
188}
189
190unsafe extern "C" fn accumulator_fn_wrapper(
191    udaf: &FFI_AggregateUDF,
192    args: FFI_AccumulatorArgs,
193) -> FFI_Result<FFI_Accumulator> {
194    unsafe {
195        let udaf = udaf.inner();
196
197        let accumulator_args = &sresult_return!(ForeignAccumulatorArgs::try_from(args));
198
199        sresult!(
200            udaf.accumulator(accumulator_args.into())
201                .map(FFI_Accumulator::from)
202        )
203    }
204}
205
206unsafe extern "C" fn create_sliding_accumulator_fn_wrapper(
207    udaf: &FFI_AggregateUDF,
208    args: FFI_AccumulatorArgs,
209) -> FFI_Result<FFI_Accumulator> {
210    unsafe {
211        let udaf = udaf.inner();
212
213        let accumulator_args = &sresult_return!(ForeignAccumulatorArgs::try_from(args));
214
215        sresult!(
216            udaf.create_sliding_accumulator(accumulator_args.into())
217                .map(FFI_Accumulator::from)
218        )
219    }
220}
221
222unsafe extern "C" fn create_groups_accumulator_fn_wrapper(
223    udaf: &FFI_AggregateUDF,
224    args: FFI_AccumulatorArgs,
225) -> FFI_Result<FFI_GroupsAccumulator> {
226    unsafe {
227        let udaf = udaf.inner();
228
229        let accumulator_args = &sresult_return!(ForeignAccumulatorArgs::try_from(args));
230
231        sresult!(
232            udaf.create_groups_accumulator(accumulator_args.into())
233                .map(FFI_GroupsAccumulator::from)
234        )
235    }
236}
237
238unsafe extern "C" fn groups_accumulator_supported_fn_wrapper(
239    udaf: &FFI_AggregateUDF,
240    args: FFI_AccumulatorArgs,
241) -> bool {
242    unsafe {
243        let udaf = udaf.inner();
244
245        ForeignAccumulatorArgs::try_from(args)
246            .map(|a| udaf.groups_accumulator_supported((&a).into()))
247            .unwrap_or_else(|e| {
248                log::warn!("Unable to parse accumulator args. {e}");
249                false
250            })
251    }
252}
253
254unsafe extern "C" fn with_beneficial_ordering_fn_wrapper(
255    udaf: &FFI_AggregateUDF,
256    beneficial_ordering: bool,
257) -> FFI_Result<FFI_Option<FFI_AggregateUDF>> {
258    unsafe {
259        let udaf = udaf.inner().as_ref().clone();
260
261        let result = sresult_return!(udaf.with_beneficial_ordering(beneficial_ordering));
262        let result = sresult_return!(
263            result
264                .map(|func| func.with_beneficial_ordering(beneficial_ordering))
265                .transpose()
266        )
267        .flatten()
268        .map(|func| FFI_AggregateUDF::from(Arc::new(func)));
269
270        FFI_Result::Ok(result.into())
271    }
272}
273
274unsafe extern "C" fn state_fields_fn_wrapper(
275    udaf: &FFI_AggregateUDF,
276    name: &SStr,
277    input_fields: SVec<WrappedSchema>,
278    return_field: WrappedSchema,
279    ordering_fields: SVec<SVec<u8>>,
280    is_distinct: bool,
281) -> FFI_Result<SVec<SVec<u8>>> {
282    unsafe {
283        let udaf = udaf.inner();
284
285        let input_fields = &sresult_return!(rvec_wrapped_to_vec_fieldref(&input_fields));
286        let return_field = sresult_return!(Field::try_from(&return_field.0)).into();
287
288        let ordering_fields = &sresult_return!(
289            ordering_fields
290                .into_iter()
291                .map(|field_bytes| datafusion_proto_common::Field::decode(
292                    field_bytes.as_ref()
293                ))
294                .collect::<std::result::Result<Vec<_>, DecodeError>>()
295        );
296
297        let ordering_fields =
298            &sresult_return!(parse_proto_fields_to_fields(ordering_fields))
299                .into_iter()
300                .map(Arc::new)
301                .collect::<Vec<_>>();
302
303        let args = StateFieldsArgs {
304            name: name.as_str(),
305            input_fields,
306            return_field,
307            ordering_fields,
308            is_distinct,
309        };
310
311        let state_fields = sresult_return!(udaf.state_fields(args));
312        let state_fields = sresult_return!(
313            state_fields
314                .iter()
315                .map(|f| f.as_ref())
316                .map(datafusion_proto::protobuf::Field::try_from)
317                .map(|v| v.map_err(DataFusionError::from))
318                .collect::<Result<Vec<_>>>()
319        )
320        .into_iter()
321        .map(|field| field.encode_to_vec().into_iter().collect())
322        .collect();
323
324        FFI_Result::Ok(state_fields)
325    }
326}
327
328unsafe extern "C" fn order_sensitivity_fn_wrapper(
329    udaf: &FFI_AggregateUDF,
330) -> FFI_AggregateOrderSensitivity {
331    unsafe { udaf.inner().order_sensitivity().into() }
332}
333
334unsafe extern "C" fn supports_null_handling_clause_fn_wrapper(
335    udaf: &FFI_AggregateUDF,
336) -> bool {
337    unsafe { udaf.inner().supports_null_handling_clause() }
338}
339
340unsafe extern "C" fn coerce_types_fn_wrapper(
341    udaf: &FFI_AggregateUDF,
342    arg_types: SVec<WrappedSchema>,
343) -> FFI_Result<SVec<WrappedSchema>> {
344    unsafe {
345        let udaf = udaf.inner();
346
347        let arg_types = sresult_return!(rvec_wrapped_to_vec_datatype(&arg_types));
348
349        let arg_fields = arg_types
350            .iter()
351            .map(|dt| Field::new("f", dt.clone(), true))
352            .map(Arc::new)
353            .collect::<Vec<_>>();
354        let return_types = sresult_return!(fields_with_udf(&arg_fields, udaf.as_ref()))
355            .into_iter()
356            .map(|f| f.data_type().to_owned())
357            .collect::<Vec<_>>();
358
359        sresult!(vec_datatype_to_rvec_wrapped(&return_types))
360    }
361}
362
363unsafe extern "C" fn release_fn_wrapper(udaf: &mut FFI_AggregateUDF) {
364    unsafe {
365        debug_assert!(!udaf.private_data.is_null());
366        let private_data =
367            Box::from_raw(udaf.private_data as *mut AggregateUDFPrivateData);
368        drop(private_data);
369        udaf.private_data = std::ptr::null_mut();
370    }
371}
372
373unsafe extern "C" fn clone_fn_wrapper(udaf: &FFI_AggregateUDF) -> FFI_AggregateUDF {
374    unsafe { Arc::clone(udaf.inner()).into() }
375}
376
377impl Clone for FFI_AggregateUDF {
378    fn clone(&self) -> Self {
379        unsafe { (self.clone)(self) }
380    }
381}
382
383impl From<Arc<AggregateUDF>> for FFI_AggregateUDF {
384    fn from(udaf: Arc<AggregateUDF>) -> Self {
385        if let Some(udaf) = udaf.inner().downcast_ref::<ForeignAggregateUDF>() {
386            return udaf.udaf.clone();
387        }
388
389        let name = udaf.name().into();
390        let aliases = udaf.aliases().iter().map(|a| a.to_owned().into()).collect();
391        let is_nullable = udaf.is_nullable();
392        let volatility = udaf.signature().volatility.into();
393
394        let private_data = Box::new(AggregateUDFPrivateData { udaf });
395
396        Self {
397            name,
398            is_nullable,
399            volatility,
400            aliases,
401            return_field: return_field_fn_wrapper,
402            accumulator: accumulator_fn_wrapper,
403            create_sliding_accumulator: create_sliding_accumulator_fn_wrapper,
404            create_groups_accumulator: create_groups_accumulator_fn_wrapper,
405            groups_accumulator_supported: groups_accumulator_supported_fn_wrapper,
406            with_beneficial_ordering: with_beneficial_ordering_fn_wrapper,
407            state_fields: state_fields_fn_wrapper,
408            order_sensitivity: order_sensitivity_fn_wrapper,
409            coerce_types: coerce_types_fn_wrapper,
410            clone: clone_fn_wrapper,
411            release: release_fn_wrapper,
412            private_data: Box::into_raw(private_data) as *mut c_void,
413            library_marker_id: crate::get_library_marker_id,
414            supports_null_handling_clause: supports_null_handling_clause_fn_wrapper,
415        }
416    }
417}
418
419impl Drop for FFI_AggregateUDF {
420    fn drop(&mut self) {
421        unsafe { (self.release)(self) }
422    }
423}
424
425/// This struct is used to access an UDF provided by a foreign
426/// library across a FFI boundary.
427///
428/// The ForeignAggregateUDF is to be used by the caller of the UDF, so it has
429/// no knowledge or access to the private data. All interaction with the UDF
430/// must occur through the functions defined in FFI_AggregateUDF.
431#[derive(Debug)]
432pub struct ForeignAggregateUDF {
433    signature: Signature,
434    aliases: Vec<String>,
435    udaf: FFI_AggregateUDF,
436}
437
438unsafe impl Send for ForeignAggregateUDF {}
439unsafe impl Sync for ForeignAggregateUDF {}
440
441impl PartialEq for ForeignAggregateUDF {
442    fn eq(&self, other: &Self) -> bool {
443        // FFI_AggregateUDF cannot be compared, so identity equality is the best we can do.
444        std::ptr::eq(self, other)
445    }
446}
447impl Eq for ForeignAggregateUDF {}
448impl Hash for ForeignAggregateUDF {
449    fn hash<H: Hasher>(&self, state: &mut H) {
450        std::ptr::hash(self, state)
451    }
452}
453
454impl From<&FFI_AggregateUDF> for Arc<dyn AggregateUDFImpl> {
455    fn from(udaf: &FFI_AggregateUDF) -> Self {
456        if (udaf.library_marker_id)() == crate::get_library_marker_id() {
457            return Arc::clone(unsafe { udaf.inner().inner() });
458        }
459
460        let signature = Signature::user_defined((&udaf.volatility).into());
461        let aliases = udaf.aliases.iter().map(|s| s.to_string()).collect();
462
463        Arc::new(ForeignAggregateUDF {
464            udaf: udaf.clone(),
465            signature,
466            aliases,
467        })
468    }
469}
470
471impl AggregateUDFImpl for ForeignAggregateUDF {
472    fn name(&self) -> &str {
473        self.udaf.name.as_str()
474    }
475
476    fn signature(&self) -> &Signature {
477        &self.signature
478    }
479
480    fn return_type(&self, _arg_types: &[DataType]) -> Result<DataType> {
481        unimplemented!()
482    }
483
484    fn return_field(&self, arg_fields: &[FieldRef]) -> Result<FieldRef> {
485        let arg_fields = vec_fieldref_to_rvec_wrapped(arg_fields)?;
486
487        let result = unsafe { (self.udaf.return_field)(&self.udaf, arg_fields) };
488
489        let result = df_result!(result);
490
491        result.and_then(|r| {
492            Field::try_from(&r.0)
493                .map(Arc::new)
494                .map_err(DataFusionError::from)
495        })
496    }
497
498    fn is_nullable(&self) -> bool {
499        self.udaf.is_nullable
500    }
501
502    fn accumulator(&self, acc_args: AccumulatorArgs) -> Result<Box<dyn Accumulator>> {
503        let args = acc_args.try_into()?;
504        unsafe {
505            df_result!((self.udaf.accumulator)(&self.udaf, args))
506                .map(<Box<dyn Accumulator>>::from)
507        }
508    }
509
510    fn state_fields(&self, args: StateFieldsArgs) -> Result<Vec<FieldRef>> {
511        unsafe {
512            let name = SStr::from(args.name);
513            let input_fields = vec_fieldref_to_rvec_wrapped(args.input_fields)?;
514            let return_field =
515                WrappedSchema(FFI_ArrowSchema::try_from(args.return_field.as_ref())?);
516            let ordering_fields = args
517                .ordering_fields
518                .iter()
519                .map(|f| f.as_ref())
520                .map(datafusion_proto::protobuf::Field::try_from)
521                .map(|v| v.map_err(DataFusionError::from))
522                .collect::<Result<Vec<_>>>()?
523                .into_iter()
524                .map(|proto_field| proto_field.encode_to_vec().into_iter().collect())
525                .collect();
526
527            let fields = df_result!((self.udaf.state_fields)(
528                &self.udaf,
529                &name,
530                input_fields,
531                return_field,
532                ordering_fields,
533                args.is_distinct
534            ))?;
535            let fields = fields
536                .into_iter()
537                .map(|field_bytes| {
538                    datafusion_proto_common::Field::decode(field_bytes.as_ref())
539                        .map_err(|e| ffi_datafusion_err!("{e}"))
540                })
541                .collect::<Result<Vec<_>>>()?;
542
543            parse_proto_fields_to_fields(fields.iter())
544                .map(|fields| fields.into_iter().map(Arc::new).collect())
545                .map_err(|e| ffi_datafusion_err!("{e}"))
546        }
547    }
548
549    fn groups_accumulator_supported(&self, args: AccumulatorArgs) -> bool {
550        let args = match FFI_AccumulatorArgs::try_from(args) {
551            Ok(v) => v,
552            Err(e) => {
553                log::warn!("Attempting to convert accumulator arguments: {e}");
554                return false;
555            }
556        };
557
558        unsafe { (self.udaf.groups_accumulator_supported)(&self.udaf, args) }
559    }
560
561    fn create_groups_accumulator(
562        &self,
563        args: AccumulatorArgs,
564    ) -> Result<Box<dyn GroupsAccumulator>> {
565        let args = FFI_AccumulatorArgs::try_from(args)?;
566
567        unsafe {
568            df_result!((self.udaf.create_groups_accumulator)(&self.udaf, args))
569                .map(<Box<dyn GroupsAccumulator>>::from)
570        }
571    }
572
573    fn aliases(&self) -> &[String] {
574        &self.aliases
575    }
576
577    fn create_sliding_accumulator(
578        &self,
579        args: AccumulatorArgs,
580    ) -> Result<Box<dyn Accumulator>> {
581        let args = args.try_into()?;
582        unsafe {
583            df_result!((self.udaf.create_sliding_accumulator)(&self.udaf, args))
584                .map(<Box<dyn Accumulator>>::from)
585        }
586    }
587
588    fn with_beneficial_ordering(
589        self: Arc<Self>,
590        beneficial_ordering: bool,
591    ) -> Result<Option<Arc<dyn AggregateUDFImpl>>> {
592        unsafe {
593            let result = df_result!((self.udaf.with_beneficial_ordering)(
594                &self.udaf,
595                beneficial_ordering
596            ))?
597            .into_option();
598
599            let result = result.map(|func| <Arc<dyn AggregateUDFImpl>>::from(&func));
600
601            Ok(result)
602        }
603    }
604
605    fn order_sensitivity(&self) -> AggregateOrderSensitivity {
606        unsafe { (self.udaf.order_sensitivity)(&self.udaf).into() }
607    }
608
609    fn supports_null_handling_clause(&self) -> bool {
610        unsafe { (self.udaf.supports_null_handling_clause)(&self.udaf) }
611    }
612
613    fn simplify(&self) -> Option<AggregateFunctionSimplification> {
614        None
615    }
616
617    fn coerce_types(&self, arg_types: &[DataType]) -> Result<Vec<DataType>> {
618        unsafe {
619            let arg_types = vec_datatype_to_rvec_wrapped(arg_types)?;
620            let result_types =
621                df_result!((self.udaf.coerce_types)(&self.udaf, arg_types))?;
622            Ok(rvec_wrapped_to_vec_datatype(&result_types)?)
623        }
624    }
625}
626
627#[repr(C)]
628#[derive(Debug)]
629pub enum FFI_AggregateOrderSensitivity {
630    Insensitive,
631    HardRequirement,
632    SoftRequirement,
633    Beneficial,
634}
635
636impl From<FFI_AggregateOrderSensitivity> for AggregateOrderSensitivity {
637    fn from(value: FFI_AggregateOrderSensitivity) -> Self {
638        match value {
639            FFI_AggregateOrderSensitivity::Insensitive => Self::Insensitive,
640            FFI_AggregateOrderSensitivity::HardRequirement => Self::HardRequirement,
641            FFI_AggregateOrderSensitivity::SoftRequirement => Self::SoftRequirement,
642            FFI_AggregateOrderSensitivity::Beneficial => Self::Beneficial,
643        }
644    }
645}
646
647impl From<AggregateOrderSensitivity> for FFI_AggregateOrderSensitivity {
648    fn from(value: AggregateOrderSensitivity) -> Self {
649        match value {
650            AggregateOrderSensitivity::Insensitive => Self::Insensitive,
651            AggregateOrderSensitivity::HardRequirement => Self::HardRequirement,
652            AggregateOrderSensitivity::SoftRequirement => Self::SoftRequirement,
653            AggregateOrderSensitivity::Beneficial => Self::Beneficial,
654        }
655    }
656}
657
658#[cfg(test)]
659mod tests {
660    use std::collections::HashMap;
661
662    use arrow::datatypes::Schema;
663    use datafusion::common::create_array;
664    use datafusion::functions_aggregate::sum::Sum;
665    use datafusion::physical_expr::PhysicalSortExpr;
666    use datafusion::physical_plan::expressions::col;
667    use datafusion::scalar::ScalarValue;
668
669    use super::*;
670
671    #[derive(Default, Debug, Hash, Eq, PartialEq)]
672    struct SumWithCopiedMetadata {
673        inner: Sum,
674    }
675
676    impl AggregateUDFImpl for SumWithCopiedMetadata {
677        fn name(&self) -> &str {
678            self.inner.name()
679        }
680
681        fn signature(&self) -> &Signature {
682            self.inner.signature()
683        }
684
685        fn return_type(&self, _arg_types: &[DataType]) -> Result<DataType> {
686            unimplemented!()
687        }
688
689        fn return_field(&self, arg_fields: &[FieldRef]) -> Result<FieldRef> {
690            // Copy the input field, so any metadata gets returned
691            Ok(Arc::clone(&arg_fields[0]))
692        }
693
694        fn accumulator(&self, acc_args: AccumulatorArgs) -> Result<Box<dyn Accumulator>> {
695            self.inner.accumulator(acc_args)
696        }
697    }
698
699    fn create_test_foreign_udaf(
700        original_udaf: impl AggregateUDFImpl + 'static,
701    ) -> Result<AggregateUDF> {
702        let original_udaf = Arc::new(AggregateUDF::from(original_udaf));
703
704        let mut local_udaf: FFI_AggregateUDF = Arc::clone(&original_udaf).into();
705        local_udaf.library_marker_id = crate::mock_foreign_marker_id;
706
707        let foreign_udaf: Arc<dyn AggregateUDFImpl> = (&local_udaf).into();
708        Ok(AggregateUDF::new_from_shared_impl(foreign_udaf))
709    }
710
711    #[test]
712    fn test_round_trip_udaf() -> Result<()> {
713        let original_udaf = Sum::new();
714        let original_name = original_udaf.name().to_owned();
715        let original_udaf = Arc::new(AggregateUDF::from(original_udaf));
716
717        // Convert to FFI format
718        let mut local_udaf: FFI_AggregateUDF = Arc::clone(&original_udaf).into();
719        local_udaf.library_marker_id = crate::mock_foreign_marker_id;
720
721        // Convert back to native format
722        let foreign_udaf: Arc<dyn AggregateUDFImpl> = (&local_udaf).into();
723        let foreign_udaf = AggregateUDF::new_from_shared_impl(foreign_udaf);
724
725        assert_eq!(original_name, foreign_udaf.name());
726        Ok(())
727    }
728
729    #[test]
730    fn test_foreign_udaf_aliases() -> Result<()> {
731        let foreign_udaf =
732            create_test_foreign_udaf(Sum::new())?.with_aliases(["my_function"]);
733
734        let return_field =
735            foreign_udaf
736                .return_field(&[Field::new("a", DataType::Float64, true).into()])?;
737        let return_type = return_field.data_type();
738        assert_eq!(return_type, &DataType::Float64);
739        Ok(())
740    }
741
742    #[test]
743    fn test_foreign_udaf_accumulator() -> Result<()> {
744        let foreign_udaf = create_test_foreign_udaf(Sum::new())?;
745
746        let schema = Schema::new(vec![Field::new("a", DataType::Float64, true)]);
747        let acc_args = AccumulatorArgs {
748            return_field: Field::new("f", DataType::Float64, true).into(),
749            schema: &schema,
750            expr_fields: &[Field::new("a", DataType::Float64, true).into()],
751            ignore_nulls: true,
752            order_bys: &[PhysicalSortExpr::new_default(col("a", &schema)?)],
753            is_reversed: false,
754            name: "round_trip",
755            is_distinct: true,
756            exprs: &[col("a", &schema)?],
757        };
758        let mut accumulator = foreign_udaf.accumulator(acc_args)?;
759        let values = create_array!(Float64, vec![10., 20., 30., 40., 50.]);
760        accumulator.update_batch(&[values])?;
761        let resultant_value = accumulator.evaluate()?;
762        assert_eq!(resultant_value, ScalarValue::Float64(Some(150.)));
763
764        Ok(())
765    }
766
767    #[test]
768    fn test_round_trip_udaf_metadata() -> Result<()> {
769        let original_udaf = SumWithCopiedMetadata::default();
770        let original_udaf = Arc::new(AggregateUDF::from(original_udaf));
771
772        // Convert to FFI format
773        let local_udaf: FFI_AggregateUDF = Arc::clone(&original_udaf).into();
774
775        // Convert back to native format
776        let foreign_udaf: Arc<dyn AggregateUDFImpl> = (&local_udaf).into();
777        let foreign_udaf = AggregateUDF::new_from_shared_impl(foreign_udaf);
778
779        let metadata: HashMap<String, String> =
780            [("a_key".to_string(), "a_value".to_string())]
781                .into_iter()
782                .collect();
783        let input_field = Arc::new(
784            Field::new("a", DataType::Float64, false).with_metadata(metadata.clone()),
785        );
786        let return_field = foreign_udaf.return_field(&[input_field])?;
787
788        assert_eq!(&metadata, return_field.metadata());
789        Ok(())
790    }
791
792    #[test]
793    fn test_supports_null_handling_clause() -> Result<()> {
794        let first_value = create_test_foreign_udaf(
795            datafusion::functions_aggregate::first_last::FirstValue::new(),
796        )?;
797        assert!(first_value.supports_null_handling_clause());
798
799        let sum = create_test_foreign_udaf(Sum::new())?;
800        assert!(!sum.supports_null_handling_clause());
801
802        Ok(())
803    }
804
805    #[test]
806    fn test_beneficial_ordering() -> Result<()> {
807        let foreign_udaf = create_test_foreign_udaf(
808            datafusion::functions_aggregate::first_last::FirstValue::new(),
809        )?;
810
811        let foreign_udaf = foreign_udaf.with_beneficial_ordering(true)?.unwrap();
812
813        assert_eq!(
814            foreign_udaf.order_sensitivity(),
815            AggregateOrderSensitivity::Beneficial
816        );
817
818        let a_field = Arc::new(Field::new("a", DataType::Float64, true));
819        let state_fields = foreign_udaf.state_fields(StateFieldsArgs {
820            name: "a",
821            input_fields: &[Field::new("f", DataType::Float64, true).into()],
822            return_field: Field::new("f", DataType::Float64, true).into(),
823            ordering_fields: &[Arc::clone(&a_field)],
824            is_distinct: false,
825        })?;
826
827        assert_eq!(state_fields.len(), 3);
828        assert_eq!(state_fields[1], a_field);
829        Ok(())
830    }
831
832    #[test]
833    fn test_sliding_accumulator() -> Result<()> {
834        let foreign_udaf = create_test_foreign_udaf(Sum::new())?;
835
836        let schema = Schema::new(vec![Field::new("a", DataType::Float64, true)]);
837        // Note: sum distinct is only support Int64 until now
838        let acc_args = AccumulatorArgs {
839            return_field: Field::new("f", DataType::Float64, true).into(),
840            schema: &schema,
841            expr_fields: &[Field::new("a", DataType::Float64, true).into()],
842            ignore_nulls: true,
843            order_bys: &[PhysicalSortExpr::new_default(col("a", &schema)?)],
844            is_reversed: false,
845            name: "round_trip",
846            is_distinct: false,
847            exprs: &[col("a", &schema)?],
848        };
849
850        let mut accumulator = foreign_udaf.create_sliding_accumulator(acc_args)?;
851        let values = create_array!(Float64, vec![10., 20., 30., 40., 50.]);
852        accumulator.update_batch(&[values])?;
853        let resultant_value = accumulator.evaluate()?;
854        assert_eq!(resultant_value, ScalarValue::Float64(Some(150.)));
855
856        Ok(())
857    }
858
859    fn test_round_trip_order_sensitivity(sensitivity: AggregateOrderSensitivity) {
860        let ffi_sensitivity: FFI_AggregateOrderSensitivity = sensitivity.into();
861        let round_trip_sensitivity: AggregateOrderSensitivity = ffi_sensitivity.into();
862
863        assert_eq!(sensitivity, round_trip_sensitivity);
864    }
865
866    #[test]
867    fn test_round_trip_all_order_sensitivities() {
868        test_round_trip_order_sensitivity(AggregateOrderSensitivity::Insensitive);
869        test_round_trip_order_sensitivity(AggregateOrderSensitivity::HardRequirement);
870        test_round_trip_order_sensitivity(AggregateOrderSensitivity::SoftRequirement);
871        test_round_trip_order_sensitivity(AggregateOrderSensitivity::Beneficial);
872    }
873
874    #[test]
875    fn test_ffi_udaf_local_bypass() -> Result<()> {
876        let original_udaf = Sum::new();
877        let original_udaf = Arc::new(AggregateUDF::from(original_udaf));
878
879        let mut ffi_udaf = FFI_AggregateUDF::from(original_udaf);
880
881        // Verify local libraries can be downcast to their original
882        let foreign_udaf: Arc<dyn AggregateUDFImpl> = (&ffi_udaf).into();
883        assert!(foreign_udaf.is::<Sum>());
884
885        // Verify different library markers generate foreign providers
886        ffi_udaf.library_marker_id = crate::mock_foreign_marker_id;
887        let foreign_udaf: Arc<dyn AggregateUDFImpl> = (&ffi_udaf).into();
888        assert!(foreign_udaf.is::<ForeignAggregateUDF>());
889
890        Ok(())
891    }
892}