1use 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#[repr(C)]
47#[derive(Debug)]
48pub struct FFI_ExecutionPlan {
49 pub properties: unsafe extern "C" fn(plan: &Self) -> FFI_PlanProperties,
51
52 pub children: unsafe extern "C" fn(plan: &Self) -> SVec<FFI_ExecutionPlan>,
54
55 pub apply_expressions:
57 unsafe extern "C" fn(plan: &Self) -> FFI_Result<SVec<FFI_PhysicalExpr>>,
58
59 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 pub name: unsafe extern "C" fn(plan: &Self) -> SString,
68
69 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 pub metrics: unsafe extern "C" fn(plan: &Self) -> FFI_Option<FFI_MetricsSet>,
87
88 pub partition_statistics: unsafe extern "C" fn(
93 plan: &Self,
94 partition: FFI_Option<usize>,
95 ) -> FFI_Result<SVec<u8>>,
96
97 pub clone: unsafe extern "C" fn(plan: &Self) -> Self,
100
101 pub release: unsafe extern "C" fn(arg: &mut Self),
103
104 pub version: unsafe extern "C" fn() -> u64,
106
107 pub private_data: *mut c_void,
110
111 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
282fn 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 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 pub fn new(mut plan: Arc<dyn ExecutionPlan>, runtime: Option<Handle>) -> Self {
335 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#[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 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 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 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 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 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 #[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 #[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 #[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 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 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 #[test]
948 fn test_ffi_execution_plan_statistics_context_round_trip() -> Result<()> {
949 let (schema, original_stats) = stats_round_trip_fixture();
950
951 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 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 let foreign_plan: Arc<dyn ExecutionPlan> = (&ffi_plan).try_into().unwrap();
994 assert!(foreign_plan.is::<EmptyExec>());
995
996 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}