Skip to main content

datafusion_ffi/
execution_plan.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::ffi::c_void;
19use std::pin::Pin;
20use std::sync::Arc;
21
22use datafusion_common::config::ConfigOptions;
23use datafusion_common::tree_node::TreeNodeRecursion;
24use datafusion_common::{DataFusionError, Result, Statistics};
25use datafusion_execution::{SendableRecordBatchStream, TaskContext};
26use datafusion_physical_expr_common::metrics::MetricsSet;
27use datafusion_physical_plan::{
28    ChildrenPropertiesMode, DisplayAs, DisplayFormatType, ExecutionPlan, PlanProperties,
29    ReplaceChildrenOptions, StatisticsArgs, StatisticsContext,
30};
31use stabby::string::String as SString;
32use stabby::vec::Vec as SVec;
33use tokio::runtime::Handle;
34
35use crate::config::FFI_ConfigOptions;
36use crate::execution::FFI_TaskContext;
37use crate::physical_expr::FFI_PhysicalExpr;
38use crate::physical_expr::metrics::FFI_MetricsSet;
39use crate::plan_properties::FFI_PlanProperties;
40use crate::record_batch_stream::FFI_RecordBatchStream;
41use crate::statistics::{deserialize_statistics, serialize_statistics};
42use crate::util::{FFI_Option, FFI_Result};
43use crate::{df_result, sresult, sresult_return};
44
45/// A stable struct for sharing a [`ExecutionPlan`] across FFI boundaries.
46#[repr(C)]
47#[derive(Debug)]
48pub struct FFI_ExecutionPlan {
49    /// Return the plan properties
50    pub properties: unsafe extern "C" fn(plan: &Self) -> FFI_PlanProperties,
51
52    /// Return a vector of children plans
53    pub children: unsafe extern "C" fn(plan: &Self) -> SVec<FFI_ExecutionPlan>,
54
55    /// Return the physical expression roots owned by this plan node.
56    pub apply_expressions:
57        unsafe extern "C" fn(plan: &Self) -> FFI_Result<SVec<FFI_PhysicalExpr>>,
58
59    /// Return the dynamic expressions produced by this plan node.
60    pub dynamic_expressions_produced:
61        unsafe extern "C" fn(plan: &Self) -> SVec<FFI_PhysicalExpr>,
62
63    pub with_new_children:
64        unsafe extern "C" fn(plan: &Self, children: SVec<Self>) -> FFI_Result<Self>,
65
66    /// Return the plan name.
67    pub name: unsafe extern "C" fn(plan: &Self) -> SString,
68
69    /// Execute the plan and return a record batch stream. Errors
70    /// will be returned as a string.
71    pub execute: unsafe extern "C" fn(
72        plan: &Self,
73        partition: usize,
74        context: FFI_TaskContext,
75    ) -> FFI_Result<FFI_RecordBatchStream>,
76
77    pub repartitioned: unsafe extern "C" fn(
78        plan: &Self,
79        target_partitions: usize,
80        config: FFI_ConfigOptions,
81    )
82        -> FFI_Result<FFI_Option<FFI_ExecutionPlan>>,
83
84    /// Snapshot the plan's execution metrics. Returns `None` when the
85    /// underlying [`ExecutionPlan::metrics`] returned `None`.
86    pub metrics: unsafe extern "C" fn(plan: &Self) -> FFI_Option<FFI_MetricsSet>,
87
88    /// Snapshot partition statistics. `partition == None` corresponds to
89    /// statistics over all partitions; `Some(idx)` corresponds to a specific
90    /// partition. The returned bytes are a prost-encoded
91    /// `datafusion_proto_common::Statistics`.
92    pub partition_statistics: unsafe extern "C" fn(
93        plan: &Self,
94        partition: FFI_Option<usize>,
95    ) -> FFI_Result<SVec<u8>>,
96
97    /// Used to create a clone on the provider of the execution plan. This should
98    /// only need to be called by the receiver of the plan.
99    pub clone: unsafe extern "C" fn(plan: &Self) -> Self,
100
101    /// Release the memory of the private data when it is no longer being used.
102    pub release: unsafe extern "C" fn(arg: &mut Self),
103
104    /// Return the major DataFusion version number of this provider.
105    pub version: unsafe extern "C" fn() -> u64,
106
107    /// Internal data. This is only to be accessed by the provider of the plan.
108    /// A [`ForeignExecutionPlan`] should never attempt to access this data.
109    pub private_data: *mut c_void,
110
111    /// Utility to identify when FFI objects are accessed locally through
112    /// the foreign interface. See [`crate::get_library_marker_id`] and
113    /// the crate's `README.md` for more information.
114    pub library_marker_id: extern "C" fn() -> usize,
115}
116
117unsafe impl Send for FFI_ExecutionPlan {}
118unsafe impl Sync for FFI_ExecutionPlan {}
119
120pub struct ExecutionPlanPrivateData {
121    pub plan: Arc<dyn ExecutionPlan>,
122    pub runtime: Option<Handle>,
123}
124
125impl FFI_ExecutionPlan {
126    fn inner(&self) -> &Arc<dyn ExecutionPlan> {
127        let private_data = self.private_data as *const ExecutionPlanPrivateData;
128        unsafe { &(*private_data).plan }
129    }
130
131    fn runtime(&self) -> Option<Handle> {
132        let private_data = self.private_data as *const ExecutionPlanPrivateData;
133        unsafe { (*private_data).runtime.clone() }
134    }
135}
136
137unsafe extern "C" fn properties_fn_wrapper(
138    plan: &FFI_ExecutionPlan,
139) -> FFI_PlanProperties {
140    plan.inner().properties().as_ref().into()
141}
142
143unsafe extern "C" fn children_fn_wrapper(
144    plan: &FFI_ExecutionPlan,
145) -> SVec<FFI_ExecutionPlan> {
146    let runtime = plan.runtime();
147    plan.inner()
148        .children()
149        .into_iter()
150        .map(|child| FFI_ExecutionPlan::new(Arc::clone(child), runtime.clone()))
151        .collect()
152}
153
154unsafe extern "C" fn apply_expressions_fn_wrapper(
155    plan: &FFI_ExecutionPlan,
156) -> FFI_Result<SVec<FFI_PhysicalExpr>> {
157    let mut expressions = SVec::new();
158    let result = plan.inner().apply_expressions(&mut |expr| {
159        expressions.push(FFI_PhysicalExpr::from(Arc::clone(expr)));
160        Ok(TreeNodeRecursion::Continue)
161    });
162    sresult!(result.map(|_| expressions))
163}
164
165unsafe extern "C" fn dynamic_expressions_produced_fn_wrapper(
166    plan: &FFI_ExecutionPlan,
167) -> SVec<FFI_PhysicalExpr> {
168    plan.inner()
169        .dynamic_expressions_produced()
170        .into_iter()
171        .map(FFI_PhysicalExpr::from)
172        .collect()
173}
174
175unsafe extern "C" fn with_new_children_fn_wrapper(
176    plan: &FFI_ExecutionPlan,
177    children: SVec<FFI_ExecutionPlan>,
178) -> FFI_Result<FFI_ExecutionPlan> {
179    let runtime = plan.runtime();
180    let inner_plan = Arc::clone(plan.inner());
181
182    let children: Result<Vec<Arc<dyn ExecutionPlan>>> = children
183        .iter()
184        .map(<Arc<dyn ExecutionPlan>>::try_from)
185        .collect();
186
187    let children = sresult_return!(children);
188    let new_plan = sresult_return!(inner_plan.replace_children(
189        children,
190        ReplaceChildrenOptions::new(ChildrenPropertiesMode::Recompute)
191    ));
192
193    FFI_Result::Ok(FFI_ExecutionPlan::new(new_plan, runtime))
194}
195
196unsafe extern "C" fn execute_fn_wrapper(
197    plan: &FFI_ExecutionPlan,
198    partition: usize,
199    context: FFI_TaskContext,
200) -> FFI_Result<FFI_RecordBatchStream> {
201    let ctx = context.into();
202    let runtime = plan.runtime();
203    let plan = plan.inner();
204
205    let _runtime_guard = runtime.as_ref().map(|rt| rt.enter());
206
207    sresult!(
208        plan.execute(partition, ctx)
209            .map(|rbs| FFI_RecordBatchStream::new(rbs, runtime))
210    )
211}
212
213unsafe extern "C" fn repartitioned_fn_wrapper(
214    plan: &FFI_ExecutionPlan,
215    target_partitions: usize,
216    config: FFI_ConfigOptions,
217) -> FFI_Result<FFI_Option<FFI_ExecutionPlan>> {
218    let maybe_config: Result<ConfigOptions, DataFusionError> = config.try_into();
219    let config = sresult_return!(maybe_config);
220    let runtime = plan.runtime();
221    let plan = plan.inner();
222
223    sresult!(
224        plan.repartitioned(target_partitions, &config)
225            .map(|maybe_plan| maybe_plan
226                .map(|plan| FFI_ExecutionPlan::new(plan, runtime))
227                .into())
228    )
229}
230
231unsafe extern "C" fn name_fn_wrapper(plan: &FFI_ExecutionPlan) -> SString {
232    plan.inner().name().into()
233}
234
235unsafe extern "C" fn metrics_fn_wrapper(
236    plan: &FFI_ExecutionPlan,
237) -> FFI_Option<FFI_MetricsSet> {
238    plan.inner()
239        .metrics()
240        .as_ref()
241        .map(FFI_MetricsSet::from)
242        .into()
243}
244
245unsafe extern "C" fn partition_statistics_fn_wrapper(
246    plan: &FFI_ExecutionPlan,
247    partition: FFI_Option<usize>,
248) -> FFI_Result<SVec<u8>> {
249    let partition: Option<usize> = partition.into();
250    StatisticsContext::new()
251        .compute(
252            plan.inner().as_ref(),
253            &StatisticsArgs::new().with_partition(partition),
254        )
255        .map(|stats| SVec::from(serialize_statistics(stats.as_ref()).as_slice()))
256        .into()
257}
258
259unsafe extern "C" fn release_fn_wrapper(plan: &mut FFI_ExecutionPlan) {
260    unsafe {
261        debug_assert!(!plan.private_data.is_null());
262        let private_data =
263            Box::from_raw(plan.private_data as *mut ExecutionPlanPrivateData);
264        drop(private_data);
265        plan.private_data = std::ptr::null_mut();
266    }
267}
268
269unsafe extern "C" fn clone_fn_wrapper(plan: &FFI_ExecutionPlan) -> FFI_ExecutionPlan {
270    let runtime = plan.runtime();
271    let plan = plan.inner();
272
273    FFI_ExecutionPlan::new(Arc::clone(plan), runtime)
274}
275
276impl Clone for FFI_ExecutionPlan {
277    fn clone(&self) -> Self {
278        unsafe { (self.clone)(self) }
279    }
280}
281
282/// Helper function to recursively identify any children that do not
283/// have a runtime set but should because they are local to this same
284/// library. This does imply a restriction that all execution plans
285/// in this chain that are within the same library use the same runtime.
286fn pass_runtime_to_children(
287    plan: &Arc<dyn ExecutionPlan>,
288    runtime: &Handle,
289) -> Result<Option<Arc<dyn ExecutionPlan>>> {
290    let mut updated_children = false;
291    let plan_is_foreign = plan.is::<ForeignExecutionPlan>();
292
293    let children = plan
294        .children()
295        .into_iter()
296        .map(|child| {
297            let child = match pass_runtime_to_children(child, runtime)? {
298                Some(child) => {
299                    updated_children = true;
300                    child
301                }
302                None => Arc::clone(child),
303            };
304
305            // If the parent is foreign and the child is local to this library, then when
306            // we called `children()` above we will get something other than a
307            // `ForeignExecutionPlan`. In this case wrap the plan in a `ForeignExecutionPlan`
308            // because when we call `replace_children` below it will extract the
309            // FFI plan that does contain the runtime.
310            if plan_is_foreign && !child.is::<ForeignExecutionPlan>() {
311                updated_children = true;
312                let ffi_child = FFI_ExecutionPlan::new(child, Some(runtime.clone()));
313                let foreign_child = ForeignExecutionPlan::try_from(ffi_child);
314                foreign_child.map(|c| Arc::new(c) as Arc<dyn ExecutionPlan>)
315            } else {
316                Ok(child)
317            }
318        })
319        .collect::<Result<Vec<_>>>()?;
320    if updated_children {
321        Arc::clone(plan)
322            .replace_children(
323                children,
324                ReplaceChildrenOptions::new(ChildrenPropertiesMode::Recompute),
325            )
326            .map(Some)
327    } else {
328        Ok(None)
329    }
330}
331
332impl FFI_ExecutionPlan {
333    /// This function is called on the provider's side.
334    pub fn new(mut plan: Arc<dyn ExecutionPlan>, runtime: Option<Handle>) -> Self {
335        // Note to developers: `pass_runtime_to_children` relies on the logic here to
336        // get the underlying FFI plan during calls to `new_with_children`.
337        if let Some(plan) = plan.downcast_ref::<ForeignExecutionPlan>() {
338            return plan.plan.clone();
339        }
340
341        if let Some(rt) = &runtime
342            && let Ok(Some(p)) = pass_runtime_to_children(&plan, rt)
343        {
344            plan = p;
345        }
346
347        let private_data = Box::new(ExecutionPlanPrivateData { plan, runtime });
348        Self {
349            properties: properties_fn_wrapper,
350            children: children_fn_wrapper,
351            apply_expressions: apply_expressions_fn_wrapper,
352            dynamic_expressions_produced: dynamic_expressions_produced_fn_wrapper,
353            with_new_children: with_new_children_fn_wrapper,
354            name: name_fn_wrapper,
355            execute: execute_fn_wrapper,
356            repartitioned: repartitioned_fn_wrapper,
357            metrics: metrics_fn_wrapper,
358            partition_statistics: partition_statistics_fn_wrapper,
359            clone: clone_fn_wrapper,
360            release: release_fn_wrapper,
361            version: crate::version,
362            private_data: Box::into_raw(private_data) as *mut c_void,
363            library_marker_id: crate::get_library_marker_id,
364        }
365    }
366}
367
368impl Drop for FFI_ExecutionPlan {
369    fn drop(&mut self) {
370        unsafe { (self.release)(self) }
371    }
372}
373
374/// This struct is used to access an execution plan provided by a foreign
375/// library across a FFI boundary.
376///
377/// The ForeignExecutionPlan is to be used by the caller of the plan, so it has
378/// no knowledge or access to the private data. All interaction with the plan
379/// must occur through the functions defined in FFI_ExecutionPlan.
380#[derive(Debug)]
381pub struct ForeignExecutionPlan {
382    name: String,
383    plan: FFI_ExecutionPlan,
384    properties: Arc<PlanProperties>,
385    children: Vec<Arc<dyn ExecutionPlan>>,
386}
387
388unsafe impl Send for ForeignExecutionPlan {}
389unsafe impl Sync for ForeignExecutionPlan {}
390
391impl DisplayAs for ForeignExecutionPlan {
392    fn fmt_as(
393        &self,
394        t: DisplayFormatType,
395        f: &mut std::fmt::Formatter,
396    ) -> std::fmt::Result {
397        match t {
398            DisplayFormatType::Default | DisplayFormatType::Verbose => {
399                write!(
400                    f,
401                    "FFI_ExecutionPlan: {}, number_of_children={}",
402                    self.name,
403                    self.children.len(),
404                )
405            }
406            DisplayFormatType::TreeRender => {
407                // TODO: collect info
408                write!(f, "")
409            }
410        }
411    }
412}
413
414impl TryFrom<&FFI_ExecutionPlan> for Arc<dyn ExecutionPlan> {
415    type Error = DataFusionError;
416
417    fn try_from(plan: &FFI_ExecutionPlan) -> Result<Self, Self::Error> {
418        if (plan.library_marker_id)() == crate::get_library_marker_id() {
419            Ok(Arc::clone(plan.inner()))
420        } else {
421            let plan = ForeignExecutionPlan::try_from(plan.clone())?;
422            Ok(Arc::new(plan))
423        }
424    }
425}
426
427impl TryFrom<FFI_ExecutionPlan> for ForeignExecutionPlan {
428    type Error = DataFusionError;
429    fn try_from(plan: FFI_ExecutionPlan) -> Result<Self, Self::Error> {
430        unsafe {
431            let name = (plan.name)(&plan).into();
432
433            let properties: PlanProperties = (plan.properties)(&plan).try_into()?;
434
435            let children_rvec = (plan.children)(&plan);
436            let children = children_rvec
437                .iter()
438                .map(<Arc<dyn ExecutionPlan>>::try_from)
439                .collect::<Result<Vec<_>>>()?;
440
441            Ok(ForeignExecutionPlan {
442                name,
443                plan,
444                properties: Arc::new(properties),
445                children,
446            })
447        }
448    }
449}
450
451impl ExecutionPlan for ForeignExecutionPlan {
452    fn name(&self) -> &str {
453        &self.name
454    }
455
456    fn properties(&self) -> &Arc<PlanProperties> {
457        &self.properties
458    }
459
460    fn children(&self) -> Vec<&Arc<dyn ExecutionPlan>> {
461        self.children.iter().collect()
462    }
463
464    fn replace_children(
465        self: Arc<Self>,
466        children: Vec<Arc<dyn ExecutionPlan>>,
467        _: ReplaceChildrenOptions,
468    ) -> Result<Arc<dyn ExecutionPlan>> {
469        let children = children
470            .into_iter()
471            .map(|child| FFI_ExecutionPlan::new(child, None))
472            .collect::<SVec<_>>();
473        let new_plan =
474            unsafe { df_result!((self.plan.with_new_children)(&self.plan, children))? };
475
476        (&new_plan).try_into()
477    }
478
479    fn with_new_children(
480        self: Arc<Self>,
481        children: Vec<Arc<dyn ExecutionPlan>>,
482    ) -> Result<Arc<dyn ExecutionPlan>> {
483        self.replace_children(
484            children,
485            ReplaceChildrenOptions::new(ChildrenPropertiesMode::Recompute),
486        )
487    }
488
489    fn execute(
490        &self,
491        partition: usize,
492        context: Arc<TaskContext>,
493    ) -> Result<SendableRecordBatchStream> {
494        let context = FFI_TaskContext::from(context);
495        unsafe {
496            df_result!((self.plan.execute)(&self.plan, partition, context))
497                .map(|stream| Pin::new(Box::new(stream)) as SendableRecordBatchStream)
498        }
499    }
500
501    fn apply_expressions(
502        &self,
503        f: &mut dyn FnMut(
504            &Arc<dyn datafusion_physical_plan::PhysicalExpr>,
505        ) -> Result<TreeNodeRecursion>,
506    ) -> Result<TreeNodeRecursion> {
507        let expressions =
508            df_result!(unsafe { (self.plan.apply_expressions)(&self.plan) })?;
509        datafusion_physical_plan::apply_expression_roots(
510            expressions.iter().map(|expression| {
511                let expression: Arc<dyn datafusion_physical_plan::PhysicalExpr> =
512                    expression.into();
513                expression
514            }),
515            f,
516        )
517    }
518
519    fn dynamic_expressions_produced(
520        &self,
521    ) -> Vec<Arc<dyn datafusion_physical_plan::PhysicalExpr>> {
522        unsafe { (self.plan.dynamic_expressions_produced)(&self.plan) }
523            .iter()
524            .map(<Arc<dyn datafusion_physical_plan::PhysicalExpr>>::from)
525            .collect()
526    }
527
528    fn repartitioned(
529        &self,
530        target_partitions: usize,
531        config: &ConfigOptions,
532    ) -> Result<Option<Arc<dyn ExecutionPlan>>> {
533        let config = config.into();
534        let maybe_plan: Option<FFI_ExecutionPlan> = df_result!(unsafe {
535            (self.plan.repartitioned)(&self.plan, target_partitions, config)
536        })?
537        .into();
538
539        maybe_plan
540            .map(|plan| <Arc<dyn ExecutionPlan>>::try_from(&plan))
541            .transpose()
542    }
543
544    fn metrics(&self) -> Option<MetricsSet> {
545        let ffi: Option<FFI_MetricsSet> =
546            unsafe { (self.plan.metrics)(&self.plan) }.into();
547        ffi.map(MetricsSet::from)
548    }
549
550    fn partition_statistics(&self, partition: Option<usize>) -> Result<Arc<Statistics>> {
551        let bytes = df_result!(unsafe {
552            (self.plan.partition_statistics)(&self.plan, partition.into())
553        })?;
554        Ok(Arc::new(deserialize_statistics(bytes.as_slice())?))
555    }
556}
557
558#[cfg(any(test, feature = "integration-tests"))]
559pub mod tests {
560    use datafusion_physical_expr::expressions::{DynamicFilterPhysicalExpr, lit};
561    use datafusion_physical_plan::execution_plan::{Boundedness, EmissionType};
562    use datafusion_physical_plan::{Partitioning, PhysicalExpr};
563
564    use super::*;
565
566    #[derive(Debug)]
567    pub struct EmptyExec {
568        props: Arc<PlanProperties>,
569        children: Vec<Arc<dyn ExecutionPlan>>,
570        expressions: Vec<Arc<dyn PhysicalExpr>>,
571        dynamic_expressions: Vec<Arc<dyn PhysicalExpr>>,
572        metrics: Option<MetricsSet>,
573        statistics: Option<Statistics>,
574    }
575
576    impl EmptyExec {
577        pub fn new(schema: arrow::datatypes::SchemaRef) -> Self {
578            Self {
579                props: Arc::new(PlanProperties::new(
580                    datafusion_physical_expr::EquivalenceProperties::new(schema),
581                    Partitioning::UnknownPartitioning(3),
582                    EmissionType::Incremental,
583                    Boundedness::Bounded,
584                )),
585                children: Vec::default(),
586                expressions: Vec::default(),
587                dynamic_expressions: Vec::default(),
588                metrics: None,
589                statistics: None,
590            }
591        }
592
593        pub fn with_metrics(mut self, metrics: MetricsSet) -> Self {
594            self.metrics = Some(metrics);
595            self
596        }
597
598        pub fn with_statistics(mut self, statistics: Statistics) -> Self {
599            self.statistics = Some(statistics);
600            self
601        }
602
603        pub fn with_expressions(
604            mut self,
605            expressions: Vec<Arc<dyn PhysicalExpr>>,
606        ) -> Self {
607            self.expressions = expressions;
608            self
609        }
610
611        pub fn with_dynamic_expressions(
612            mut self,
613            dynamic_expressions: Vec<Arc<dyn PhysicalExpr>>,
614        ) -> Self {
615            self.dynamic_expressions = dynamic_expressions;
616            self
617        }
618    }
619
620    impl DisplayAs for EmptyExec {
621        fn fmt_as(
622            &self,
623            _t: DisplayFormatType,
624            _f: &mut std::fmt::Formatter,
625        ) -> std::fmt::Result {
626            unimplemented!()
627        }
628    }
629
630    impl ExecutionPlan for EmptyExec {
631        fn name(&self) -> &'static str {
632            "empty-exec"
633        }
634
635        fn properties(&self) -> &Arc<PlanProperties> {
636            &self.props
637        }
638
639        fn children(&self) -> Vec<&Arc<dyn ExecutionPlan>> {
640            self.children.iter().collect()
641        }
642
643        fn replace_children(
644            self: Arc<Self>,
645            children: Vec<Arc<dyn ExecutionPlan>>,
646            _: ReplaceChildrenOptions,
647        ) -> Result<Arc<dyn ExecutionPlan>> {
648            Ok(Arc::new(EmptyExec {
649                props: Arc::clone(&self.props),
650                children,
651                expressions: self.expressions.clone(),
652                dynamic_expressions: self.dynamic_expressions.clone(),
653                metrics: self.metrics.clone(),
654                statistics: self.statistics.clone(),
655            }))
656        }
657
658        fn with_new_children(
659            self: Arc<Self>,
660            children: Vec<Arc<dyn ExecutionPlan>>,
661        ) -> Result<Arc<dyn ExecutionPlan>> {
662            self.replace_children(
663                children,
664                ReplaceChildrenOptions::new(ChildrenPropertiesMode::Recompute),
665            )
666        }
667
668        fn execute(
669            &self,
670            _partition: usize,
671            _context: Arc<TaskContext>,
672        ) -> Result<SendableRecordBatchStream> {
673            unimplemented!()
674        }
675
676        fn dynamic_expressions_produced(&self) -> Vec<Arc<dyn PhysicalExpr>> {
677            self.dynamic_expressions.iter().map(Arc::clone).collect()
678        }
679
680        fn metrics(&self) -> Option<MetricsSet> {
681            self.metrics.clone()
682        }
683
684        fn statistics_from_inputs(
685            &self,
686            _input_stats: &[Arc<Statistics>],
687            _args: &StatisticsArgs,
688        ) -> Result<Arc<Statistics>> {
689            Ok(Arc::new(self.statistics.clone().unwrap_or_else(|| {
690                Statistics::new_unknown(self.props.eq_properties.schema())
691            })))
692        }
693
694        fn apply_expressions(
695            &self,
696            f: &mut dyn FnMut(&Arc<dyn PhysicalExpr>) -> Result<TreeNodeRecursion>,
697        ) -> Result<TreeNodeRecursion> {
698            datafusion_physical_plan::apply_expression_roots(&self.expressions, f)
699        }
700    }
701
702    pub(crate) fn create_dynamic_filter() -> Arc<DynamicFilterPhysicalExpr> {
703        Arc::new(DynamicFilterPhysicalExpr::new(vec![], lit(true)))
704    }
705
706    #[test]
707    fn test_round_trip_ffi_execution_plan() -> Result<()> {
708        let schema = Arc::new(arrow::datatypes::Schema::new(vec![
709            arrow::datatypes::Field::new("a", arrow::datatypes::DataType::Float32, false),
710        ]));
711
712        let original_plan = Arc::new(EmptyExec::new(schema));
713        let original_name = original_plan.name().to_string();
714
715        let mut local_plan = FFI_ExecutionPlan::new(original_plan, None);
716        local_plan.library_marker_id = crate::mock_foreign_marker_id;
717
718        let foreign_plan: Arc<dyn ExecutionPlan> = (&local_plan).try_into()?;
719
720        assert_eq!(original_name, foreign_plan.name());
721
722        let display = datafusion_physical_plan::display::DisplayableExecutionPlan::new(
723            foreign_plan.as_ref(),
724        );
725
726        let buf = display.one_line().to_string();
727        assert_eq!(
728            buf.trim(),
729            "FFI_ExecutionPlan: empty-exec, number_of_children=0"
730        );
731
732        Ok(())
733    }
734
735    #[test]
736    fn test_ffi_execution_plan_apply_expressions() -> Result<()> {
737        let schema = Arc::new(arrow::datatypes::Schema::empty());
738        let dynamic_filter = create_dynamic_filter();
739        let expected_id = dynamic_filter
740            .expression_id()
741            .expect("dynamic filters always have an expression ID");
742        let expression: Arc<dyn PhysicalExpr> = Arc::clone(&dynamic_filter) as _;
743        let original_plan =
744            Arc::new(EmptyExec::new(schema).with_expressions(vec![expression]));
745
746        let mut ffi_plan = FFI_ExecutionPlan::new(original_plan, None);
747        ffi_plan.library_marker_id = crate::mock_foreign_marker_id;
748        let foreign_plan: Arc<dyn ExecutionPlan> = (&ffi_plan).try_into()?;
749
750        let mut retained = None;
751        foreign_plan.apply_expressions(&mut |expr| {
752            retained = Some(Arc::clone(expr));
753            Ok(TreeNodeRecursion::Continue)
754        })?;
755        drop(foreign_plan);
756
757        assert_eq!(
758            retained.and_then(|expr| expr.expression_id()),
759            Some(expected_id)
760        );
761        Ok(())
762    }
763
764    #[test]
765    fn test_ffi_execution_plan_dynamic_expressions_produced() -> Result<()> {
766        let schema = Arc::new(arrow::datatypes::Schema::empty());
767        let dynamic_filter = create_dynamic_filter();
768        let expected_id = dynamic_filter
769            .expression_id()
770            .expect("dynamic filters always have an expression ID");
771        let expression: Arc<dyn PhysicalExpr> = Arc::clone(&dynamic_filter) as _;
772        let original_plan =
773            Arc::new(EmptyExec::new(schema).with_dynamic_expressions(vec![expression]));
774
775        let mut ffi_plan = FFI_ExecutionPlan::new(original_plan, None);
776        ffi_plan.library_marker_id = crate::mock_foreign_marker_id;
777        let foreign_plan: Arc<dyn ExecutionPlan> = (&ffi_plan).try_into()?;
778        foreign_plan.check_invariants(
779            datafusion_physical_plan::execution_plan::InvariantLevel::Always,
780        )?;
781
782        let produced = foreign_plan.dynamic_expressions_produced();
783        assert_eq!(produced.len(), 1);
784        assert_eq!(produced[0].expression_id(), Some(expected_id));
785        drop(foreign_plan);
786        assert_eq!(produced[0].expression_id(), Some(expected_id));
787        Ok(())
788    }
789
790    #[test]
791    fn test_ffi_execution_plan_children() -> Result<()> {
792        let schema = Arc::new(arrow::datatypes::Schema::new(vec![
793            arrow::datatypes::Field::new("a", arrow::datatypes::DataType::Float32, false),
794        ]));
795
796        // Version 1: Adding child to the foreign plan
797        let child_plan = Arc::new(EmptyExec::new(Arc::clone(&schema)));
798        let mut child_local = FFI_ExecutionPlan::new(child_plan, None);
799        child_local.library_marker_id = crate::mock_foreign_marker_id;
800        let child_foreign = <Arc<dyn ExecutionPlan>>::try_from(&child_local)?;
801
802        let parent_plan = Arc::new(EmptyExec::new(Arc::clone(&schema)));
803        let mut parent_local = FFI_ExecutionPlan::new(parent_plan, None);
804        parent_local.library_marker_id = crate::mock_foreign_marker_id;
805        let parent_foreign = <Arc<dyn ExecutionPlan>>::try_from(&parent_local)?;
806
807        assert_eq!(parent_foreign.children().len(), 0);
808        assert_eq!(child_foreign.children().len(), 0);
809
810        let parent_foreign = parent_foreign.replace_children(
811            vec![child_foreign],
812            ReplaceChildrenOptions::new(ChildrenPropertiesMode::Recompute),
813        )?;
814        assert_eq!(parent_foreign.children().len(), 1);
815
816        // Version 2: Adding child to the local plan
817        let child_plan = Arc::new(EmptyExec::new(Arc::clone(&schema)));
818        let mut child_local = FFI_ExecutionPlan::new(child_plan, None);
819        child_local.library_marker_id = crate::mock_foreign_marker_id;
820        let child_foreign = <Arc<dyn ExecutionPlan>>::try_from(&child_local)?;
821
822        let parent_plan = Arc::new(EmptyExec::new(Arc::clone(&schema)));
823        let parent_plan = parent_plan.replace_children(
824            vec![child_foreign],
825            ReplaceChildrenOptions::new(ChildrenPropertiesMode::Recompute),
826        )?;
827        let mut parent_local = FFI_ExecutionPlan::new(parent_plan, None);
828        parent_local.library_marker_id = crate::mock_foreign_marker_id;
829        let parent_foreign = <Arc<dyn ExecutionPlan>>::try_from(&parent_local)?;
830
831        assert_eq!(parent_foreign.children().len(), 1);
832
833        Ok(())
834    }
835
836    #[test]
837    fn test_ffi_execution_plan_metrics_round_trip() -> Result<()> {
838        use datafusion_physical_expr_common::metrics::{Count, Metric, MetricValue};
839
840        let schema = Arc::new(arrow::datatypes::Schema::new(vec![
841            arrow::datatypes::Field::new("a", arrow::datatypes::DataType::Float32, false),
842        ]));
843
844        // Plans without metrics still return None across the boundary.
845        let bare_plan = Arc::new(EmptyExec::new(Arc::clone(&schema)));
846        let mut bare_local = FFI_ExecutionPlan::new(bare_plan, None);
847        bare_local.library_marker_id = crate::mock_foreign_marker_id;
848        let bare_foreign: Arc<dyn ExecutionPlan> = (&bare_local).try_into()?;
849        assert!(bare_foreign.metrics().is_none());
850
851        // Plans with metrics produce equivalent MetricsSets after a round trip.
852        let mut original_metrics = MetricsSet::new();
853        let c0 = Count::new();
854        c0.add(11);
855        original_metrics
856            .push(Arc::new(Metric::new(MetricValue::OutputRows(c0), Some(0))));
857        let c1 = Count::new();
858        c1.add(31);
859        original_metrics
860            .push(Arc::new(Metric::new(MetricValue::OutputRows(c1), Some(1))));
861
862        let metric_plan = Arc::new(EmptyExec::new(schema).with_metrics(original_metrics));
863        let mut metric_local = FFI_ExecutionPlan::new(metric_plan, None);
864        metric_local.library_marker_id = crate::mock_foreign_marker_id;
865        let metric_foreign: Arc<dyn ExecutionPlan> = (&metric_local).try_into()?;
866
867        let observed = metric_foreign.metrics().expect("metrics should be present");
868        assert_eq!(observed.output_rows(), Some(42));
869
870        Ok(())
871    }
872
873    /// Build an `EmptyExec` carrying `statistics`, then export it across the
874    /// (mock) FFI boundary and return the resulting foreign plan.
875    #[cfg(test)]
876    fn export_empty_exec_over_ffi(
877        schema: &arrow::datatypes::SchemaRef,
878        statistics: Option<Statistics>,
879    ) -> Result<Arc<dyn ExecutionPlan>> {
880        let mut plan = EmptyExec::new(Arc::clone(schema));
881        if let Some(statistics) = statistics {
882            plan = plan.with_statistics(statistics);
883        }
884        let mut local = FFI_ExecutionPlan::new(Arc::new(plan), None);
885        local.library_marker_id = crate::mock_foreign_marker_id;
886        let foreign: Arc<dyn ExecutionPlan> = (&local).try_into()?;
887        Ok(foreign)
888    }
889
890    /// Schema and a fully-populated `Statistics` (including `ScalarValue`-typed
891    /// min/max) shared by the FFI statistics round-trip tests.
892    #[cfg(test)]
893    fn stats_round_trip_fixture() -> (arrow::datatypes::SchemaRef, Statistics) {
894        use datafusion_common::stats::Precision;
895        use datafusion_common::{ColumnStatistics, ScalarValue};
896
897        let schema = Arc::new(arrow::datatypes::Schema::new(vec![
898            arrow::datatypes::Field::new("a", arrow::datatypes::DataType::Int32, true),
899        ]));
900        let statistics = Statistics {
901            num_rows: Precision::Exact(7),
902            total_byte_size: Precision::Inexact(128),
903            column_statistics: vec![ColumnStatistics {
904                null_count: Precision::Exact(1),
905                max_value: Precision::Exact(ScalarValue::Int32(Some(10))),
906                min_value: Precision::Exact(ScalarValue::Int32(Some(-3))),
907                sum_value: Precision::Absent,
908                distinct_count: Precision::Inexact(6),
909                byte_size: Precision::Exact(28),
910            }],
911        };
912        (schema, statistics)
913    }
914
915    /// Statistics survive an FFI round trip when queried through the
916    /// **deprecated** `partition_statistics` entry point on the foreign plan.
917    #[test]
918    #[expect(deprecated)]
919    fn test_ffi_execution_plan_partition_statistics_round_trip() -> Result<()> {
920        let (schema, original_stats) = stats_round_trip_fixture();
921
922        // A plan without explicit statistics reports new_unknown.
923        let bare = export_empty_exec_over_ffi(&schema, None)?;
924        assert_eq!(
925            bare.partition_statistics(None)?.as_ref(),
926            &Statistics::new_unknown(&schema)
927        );
928
929        // A plan with statistics round-trips them for overall and per-partition queries.
930        let with_stats =
931            export_empty_exec_over_ffi(&schema, Some(original_stats.clone()))?;
932        assert_eq!(
933            with_stats.partition_statistics(None)?.as_ref(),
934            &original_stats
935        );
936        assert_eq!(
937            with_stats.partition_statistics(Some(1))?.as_ref(),
938            &original_stats
939        );
940
941        Ok(())
942    }
943
944    /// Same round trip as
945    /// [`test_ffi_execution_plan_partition_statistics_round_trip`], but queried
946    /// through the **new** `StatisticsContext::compute` entry point.
947    #[test]
948    fn test_ffi_execution_plan_statistics_context_round_trip() -> Result<()> {
949        let (schema, original_stats) = stats_round_trip_fixture();
950
951        // A plan without explicit statistics reports new_unknown.
952        let bare = export_empty_exec_over_ffi(&schema, None)?;
953        assert_eq!(
954            StatisticsContext::new()
955                .compute(bare.as_ref(), &StatisticsArgs::new())?
956                .as_ref(),
957            &Statistics::new_unknown(&schema)
958        );
959
960        // A plan with statistics round-trips them for overall and per-partition queries.
961        let with_stats =
962            export_empty_exec_over_ffi(&schema, Some(original_stats.clone()))?;
963        assert_eq!(
964            StatisticsContext::new()
965                .compute(with_stats.as_ref(), &StatisticsArgs::new())?
966                .as_ref(),
967            &original_stats
968        );
969        assert_eq!(
970            StatisticsContext::new()
971                .compute(
972                    with_stats.as_ref(),
973                    &StatisticsArgs::new().with_partition(Some(1)),
974                )?
975                .as_ref(),
976            &original_stats
977        );
978
979        Ok(())
980    }
981
982    #[test]
983    fn test_ffi_execution_plan_local_bypass() {
984        let schema = Arc::new(arrow::datatypes::Schema::new(vec![
985            arrow::datatypes::Field::new("a", arrow::datatypes::DataType::Float32, false),
986        ]));
987
988        let plan = Arc::new(EmptyExec::new(schema));
989
990        let mut ffi_plan = FFI_ExecutionPlan::new(plan, None);
991
992        // Verify local libraries can be downcast to their original
993        let foreign_plan: Arc<dyn ExecutionPlan> = (&ffi_plan).try_into().unwrap();
994        assert!(foreign_plan.is::<EmptyExec>());
995
996        // Verify different library markers generate foreign providers
997        ffi_plan.library_marker_id = crate::mock_foreign_marker_id;
998        let foreign_plan: Arc<dyn ExecutionPlan> = (&ffi_plan).try_into().unwrap();
999        assert!(foreign_plan.is::<ForeignExecutionPlan>());
1000    }
1001}