Skip to main content

datafusion_ffi/udwf/
partition_evaluator.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::Range;
21
22use arrow::array::ArrayRef;
23use arrow::error::ArrowError;
24use datafusion_common::scalar::ScalarValue;
25use datafusion_common::{DataFusionError, Result};
26use datafusion_expr::PartitionEvaluator;
27use datafusion_expr::window_state::WindowAggState;
28use prost::Message;
29
30use stabby::vec::Vec as SVec;
31
32use super::range::FFI_Range;
33use crate::arrow_wrappers::WrappedArray;
34use crate::util::FFI_Result;
35use crate::{df_result, sresult, sresult_return};
36
37/// A stable struct for sharing [`PartitionEvaluator`] across FFI boundaries.
38/// For an explanation of each field, see the corresponding function
39/// defined in [`PartitionEvaluator`].
40#[repr(C)]
41#[derive(Debug)]
42pub struct FFI_PartitionEvaluator {
43    pub evaluate_all: unsafe extern "C" fn(
44        evaluator: &mut Self,
45        values: SVec<WrappedArray>,
46        num_rows: usize,
47    ) -> FFI_Result<WrappedArray>,
48
49    pub evaluate: unsafe extern "C" fn(
50        evaluator: &mut Self,
51        values: SVec<WrappedArray>,
52        range: FFI_Range,
53    ) -> FFI_Result<SVec<u8>>,
54
55    pub evaluate_all_with_rank: unsafe extern "C" fn(
56        evaluator: &Self,
57        num_rows: usize,
58        ranks_in_partition: SVec<FFI_Range>,
59    ) -> FFI_Result<WrappedArray>,
60
61    pub get_range: unsafe extern "C" fn(
62        evaluator: &Self,
63        idx: usize,
64        n_rows: usize,
65    ) -> FFI_Result<FFI_Range>,
66
67    pub is_causal: bool,
68
69    pub supports_bounded_execution: bool,
70    pub uses_window_frame: bool,
71    pub include_rank: bool,
72
73    /// Release the memory of the private data when it is no longer being used.
74    pub release: unsafe extern "C" fn(evaluator: &mut Self),
75
76    /// Internal data. This is only to be accessed by the provider of the evaluator.
77    /// A [`ForeignPartitionEvaluator`] should never attempt to access this data.
78    pub private_data: *mut c_void,
79
80    /// Utility to identify when FFI objects are accessed locally through
81    /// the foreign interface. See [`crate::get_library_marker_id`] and
82    /// the crate's `README.md` for more information.
83    pub library_marker_id: extern "C" fn() -> usize,
84}
85
86unsafe impl Send for FFI_PartitionEvaluator {}
87unsafe impl Sync for FFI_PartitionEvaluator {}
88
89pub struct PartitionEvaluatorPrivateData {
90    pub evaluator: Box<dyn PartitionEvaluator>,
91}
92
93impl FFI_PartitionEvaluator {
94    unsafe fn inner_mut(&mut self) -> &mut Box<dyn PartitionEvaluator + 'static> {
95        unsafe {
96            let private_data = self.private_data as *mut PartitionEvaluatorPrivateData;
97            &mut (*private_data).evaluator
98        }
99    }
100
101    unsafe fn inner(&self) -> &(dyn PartitionEvaluator + 'static) {
102        unsafe {
103            let private_data = self.private_data as *mut PartitionEvaluatorPrivateData;
104            (*private_data).evaluator.as_ref()
105        }
106    }
107}
108
109unsafe extern "C" fn evaluate_all_fn_wrapper(
110    evaluator: &mut FFI_PartitionEvaluator,
111    values: SVec<WrappedArray>,
112    num_rows: usize,
113) -> FFI_Result<WrappedArray> {
114    unsafe {
115        let inner = evaluator.inner_mut();
116
117        let values_arrays = values
118            .into_iter()
119            .map(|v| v.try_into().map_err(DataFusionError::from))
120            .collect::<Result<Vec<ArrayRef>>>();
121        let values_arrays = sresult_return!(values_arrays);
122
123        let return_array =
124            inner
125                .evaluate_all(&values_arrays, num_rows)
126                .and_then(|array| {
127                    WrappedArray::try_from(&array).map_err(DataFusionError::from)
128                });
129
130        sresult!(return_array)
131    }
132}
133
134unsafe extern "C" fn evaluate_fn_wrapper(
135    evaluator: &mut FFI_PartitionEvaluator,
136    values: SVec<WrappedArray>,
137    range: FFI_Range,
138) -> FFI_Result<SVec<u8>> {
139    unsafe {
140        let inner = evaluator.inner_mut();
141
142        let values_arrays = values
143            .into_iter()
144            .map(|v| v.try_into().map_err(DataFusionError::from))
145            .collect::<Result<Vec<ArrayRef>>>();
146        let values_arrays = sresult_return!(values_arrays);
147
148        // let return_array = (inner.evaluate(&values_arrays, &range.into()));
149        // .and_then(|array| WrappedArray::try_from(&array).map_err(DataFusionError::from));
150        let scalar_result =
151            sresult_return!(inner.evaluate(&values_arrays, &range.into()));
152        let proto_result: datafusion_proto::protobuf::ScalarValue =
153            sresult_return!((&scalar_result).try_into());
154
155        FFI_Result::Ok(proto_result.encode_to_vec().into_iter().collect())
156    }
157}
158
159unsafe extern "C" fn evaluate_all_with_rank_fn_wrapper(
160    evaluator: &FFI_PartitionEvaluator,
161    num_rows: usize,
162    ranks_in_partition: SVec<FFI_Range>,
163) -> FFI_Result<WrappedArray> {
164    unsafe {
165        let inner = evaluator.inner();
166
167        let ranks_in_partition = ranks_in_partition
168            .into_iter()
169            .map(Range::from)
170            .collect::<Vec<_>>();
171
172        let return_array = inner
173            .evaluate_all_with_rank(num_rows, &ranks_in_partition)
174            .and_then(|array| {
175                WrappedArray::try_from(&array).map_err(DataFusionError::from)
176            });
177
178        sresult!(return_array)
179    }
180}
181
182unsafe extern "C" fn get_range_fn_wrapper(
183    evaluator: &FFI_PartitionEvaluator,
184    idx: usize,
185    n_rows: usize,
186) -> FFI_Result<FFI_Range> {
187    unsafe {
188        let inner = evaluator.inner();
189        let range = inner.get_range(idx, n_rows).map(FFI_Range::from);
190
191        sresult!(range)
192    }
193}
194
195unsafe extern "C" fn release_fn_wrapper(evaluator: &mut FFI_PartitionEvaluator) {
196    unsafe {
197        if !evaluator.private_data.is_null() {
198            let private_data = Box::from_raw(
199                evaluator.private_data as *mut PartitionEvaluatorPrivateData,
200            );
201            drop(private_data);
202            evaluator.private_data = std::ptr::null_mut();
203        }
204    }
205}
206
207impl From<Box<dyn PartitionEvaluator>> for FFI_PartitionEvaluator {
208    fn from(evaluator: Box<dyn PartitionEvaluator>) -> Self {
209        if (evaluator.as_ref() as &dyn Any).is::<ForeignPartitionEvaluator>() {
210            let evaluator = (evaluator as Box<dyn Any>)
211                .downcast::<ForeignPartitionEvaluator>()
212                .expect("already checked type");
213            return evaluator.evaluator;
214        }
215
216        let is_causal = evaluator.is_causal();
217        let supports_bounded_execution = evaluator.supports_bounded_execution();
218        let include_rank = evaluator.include_rank();
219        let uses_window_frame = evaluator.uses_window_frame();
220
221        let private_data = PartitionEvaluatorPrivateData { evaluator };
222
223        Self {
224            evaluate: evaluate_fn_wrapper,
225            evaluate_all: evaluate_all_fn_wrapper,
226            evaluate_all_with_rank: evaluate_all_with_rank_fn_wrapper,
227            get_range: get_range_fn_wrapper,
228            is_causal,
229            supports_bounded_execution,
230            include_rank,
231            uses_window_frame,
232            release: release_fn_wrapper,
233            private_data: Box::into_raw(Box::new(private_data)) as *mut c_void,
234            library_marker_id: crate::get_library_marker_id,
235        }
236    }
237}
238
239impl Drop for FFI_PartitionEvaluator {
240    fn drop(&mut self) {
241        unsafe { (self.release)(self) }
242    }
243}
244
245/// This struct is used to access an UDF provided by a foreign
246/// library across a FFI boundary.
247///
248/// The ForeignPartitionEvaluator is to be used by the caller of the UDF, so it has
249/// no knowledge or access to the private data. All interaction with the UDF
250/// must occur through the functions defined in FFI_PartitionEvaluator.
251#[derive(Debug)]
252pub struct ForeignPartitionEvaluator {
253    evaluator: FFI_PartitionEvaluator,
254}
255
256impl From<FFI_PartitionEvaluator> for Box<dyn PartitionEvaluator> {
257    fn from(mut evaluator: FFI_PartitionEvaluator) -> Self {
258        if (evaluator.library_marker_id)() == crate::get_library_marker_id() {
259            unsafe {
260                let private_data = Box::from_raw(
261                    evaluator.private_data as *mut PartitionEvaluatorPrivateData,
262                );
263                // We must set this to null to avoid a double free
264                evaluator.private_data = std::ptr::null_mut();
265                private_data.evaluator
266            }
267        } else {
268            Box::new(ForeignPartitionEvaluator { evaluator })
269        }
270    }
271}
272
273impl PartitionEvaluator for ForeignPartitionEvaluator {
274    fn memoize(&mut self, _state: &mut WindowAggState) -> Result<()> {
275        // Exposing `memoize` increases the surface are of the FFI work
276        // so for now we dot support it.
277        Ok(())
278    }
279
280    fn get_range(&self, idx: usize, n_rows: usize) -> Result<Range<usize>> {
281        let range = unsafe { (self.evaluator.get_range)(&self.evaluator, idx, n_rows) };
282        df_result!(range).map(Range::from)
283    }
284
285    /// Get whether evaluator needs future data for its result (if so returns `false`) or not
286    fn is_causal(&self) -> bool {
287        self.evaluator.is_causal
288    }
289
290    fn evaluate_all(&mut self, values: &[ArrayRef], num_rows: usize) -> Result<ArrayRef> {
291        let result = unsafe {
292            let values = values
293                .iter()
294                .map(WrappedArray::try_from)
295                .collect::<std::result::Result<SVec<_>, ArrowError>>()?;
296            (self.evaluator.evaluate_all)(&mut self.evaluator, values, num_rows)
297        };
298
299        let array = df_result!(result)?;
300
301        Ok(array.try_into()?)
302    }
303
304    fn evaluate(
305        &mut self,
306        values: &[ArrayRef],
307        range: &Range<usize>,
308    ) -> Result<ScalarValue> {
309        unsafe {
310            let values = values
311                .iter()
312                .map(WrappedArray::try_from)
313                .collect::<std::result::Result<SVec<_>, ArrowError>>()?;
314
315            let scalar_bytes = df_result!((self.evaluator.evaluate)(
316                &mut self.evaluator,
317                values,
318                range.to_owned().into()
319            ))?;
320
321            let proto_scalar =
322                datafusion_proto::protobuf::ScalarValue::decode(scalar_bytes.as_ref())
323                    .map_err(|e| DataFusionError::External(Box::new(e)))?;
324
325            ScalarValue::try_from(&proto_scalar).map_err(DataFusionError::from)
326        }
327    }
328
329    fn evaluate_all_with_rank(
330        &self,
331        num_rows: usize,
332        ranks_in_partition: &[Range<usize>],
333    ) -> Result<ArrayRef> {
334        let result = unsafe {
335            let ranks_in_partition = ranks_in_partition
336                .iter()
337                .map(|rank| FFI_Range::from(rank.to_owned()))
338                .collect();
339            (self.evaluator.evaluate_all_with_rank)(
340                &self.evaluator,
341                num_rows,
342                ranks_in_partition,
343            )
344        };
345
346        let array = df_result!(result)?;
347
348        Ok(array.try_into()?)
349    }
350
351    fn supports_bounded_execution(&self) -> bool {
352        self.evaluator.supports_bounded_execution
353    }
354
355    fn uses_window_frame(&self) -> bool {
356        self.evaluator.uses_window_frame
357    }
358
359    fn include_rank(&self) -> bool {
360        self.evaluator.include_rank
361    }
362}
363
364#[cfg(test)]
365mod tests {
366    use arrow::array::ArrayRef;
367    use datafusion::logical_expr::PartitionEvaluator;
368
369    use crate::udwf::partition_evaluator::{
370        FFI_PartitionEvaluator, ForeignPartitionEvaluator,
371    };
372
373    #[derive(Debug)]
374    struct TestPartitionEvaluator {}
375
376    impl PartitionEvaluator for TestPartitionEvaluator {
377        fn evaluate_all(
378            &mut self,
379            values: &[ArrayRef],
380            _num_rows: usize,
381        ) -> datafusion_common::Result<ArrayRef> {
382            Ok(values[0].to_owned())
383        }
384    }
385
386    #[test]
387    fn test_ffi_partition_evaluator_local_bypass_inner() -> datafusion_common::Result<()>
388    {
389        let original_accum = TestPartitionEvaluator {};
390        let boxed_accum: Box<dyn PartitionEvaluator> = Box::new(original_accum);
391
392        let ffi_accum: FFI_PartitionEvaluator = boxed_accum.into();
393
394        // Verify local libraries can be downcast to their original
395        let foreign_accum: Box<dyn PartitionEvaluator> = ffi_accum.into();
396        unsafe {
397            let concrete = &*(foreign_accum.as_ref() as *const dyn PartitionEvaluator
398                as *const TestPartitionEvaluator);
399            assert!(!concrete.uses_window_frame());
400        }
401
402        // Verify different library markers generate foreign accumulator
403        let original_accum = TestPartitionEvaluator {};
404        let boxed_accum: Box<dyn PartitionEvaluator> = Box::new(original_accum);
405        let mut ffi_accum: FFI_PartitionEvaluator = boxed_accum.into();
406        ffi_accum.library_marker_id = crate::mock_foreign_marker_id;
407        let foreign_accum: Box<dyn PartitionEvaluator> = ffi_accum.into();
408        unsafe {
409            let concrete = &*(foreign_accum.as_ref() as *const dyn PartitionEvaluator
410                as *const ForeignPartitionEvaluator);
411            assert!(!concrete.uses_window_frame());
412        }
413
414        Ok(())
415    }
416}