1use std::ffi::c_void;
19use std::sync::Arc;
20
21use async_trait::async_trait;
22use datafusion_common::config::ConfigOptions;
23use datafusion_common::error::Result;
24use datafusion_physical_optimizer::{PhysicalOptimizerContext, PhysicalOptimizerRule};
25use datafusion_physical_plan::ExecutionPlan;
26use stabby::string::String as SString;
27use tokio::runtime::Handle;
28
29use crate::config::FFI_ConfigOptions;
30use crate::execution_plan::FFI_ExecutionPlan;
31use crate::util::FFI_Result;
32use crate::{df_result, sresult_return};
33
34#[repr(C)]
39#[derive(Debug)]
40pub struct FFI_PhysicalOptimizerContext {
41 pub config_options:
42 unsafe extern "C" fn(&FFI_PhysicalOptimizerContext) -> FFI_ConfigOptions,
43
44 pub release: unsafe extern "C" fn(&mut FFI_PhysicalOptimizerContext),
46
47 pub private_data: *const c_void,
49}
50
51unsafe impl Send for FFI_PhysicalOptimizerContext {}
52unsafe impl Sync for FFI_PhysicalOptimizerContext {}
53
54struct OptimizerContextPrivateData {
55 config: ConfigOptions,
56}
57
58impl FFI_PhysicalOptimizerContext {
59 pub fn new(context: &dyn PhysicalOptimizerContext) -> Self {
60 let private_data = Box::new(OptimizerContextPrivateData {
61 config: context.config_options().clone(),
62 });
63 let private_data = Box::into_raw(private_data) as *const c_void;
64
65 Self {
66 config_options: context_config_options_fn,
67 release: context_release_fn,
68 private_data,
69 }
70 }
71
72 fn inner(&self) -> &OptimizerContextPrivateData {
73 unsafe { &*(self.private_data as *const OptimizerContextPrivateData) }
74 }
75}
76
77impl Drop for FFI_PhysicalOptimizerContext {
78 fn drop(&mut self) {
79 unsafe { (self.release)(self) }
80 }
81}
82
83unsafe extern "C" fn context_config_options_fn(
84 ctx: &FFI_PhysicalOptimizerContext,
85) -> FFI_ConfigOptions {
86 FFI_ConfigOptions::from(&ctx.inner().config)
87}
88
89unsafe extern "C" fn context_release_fn(ctx: &mut FFI_PhysicalOptimizerContext) {
90 if !ctx.private_data.is_null() {
91 unsafe {
92 let _ = Box::from_raw(ctx.private_data as *mut OptimizerContextPrivateData);
93 }
94 ctx.private_data = std::ptr::null();
95 }
96}
97
98struct ForeignOptimizerContext {
103 config: ConfigOptions,
104}
105
106impl PhysicalOptimizerContext for ForeignOptimizerContext {
107 fn config_options(&self) -> &ConfigOptions {
108 &self.config
109 }
110}
111
112#[repr(C)]
114#[derive(Debug)]
115pub struct FFI_PhysicalOptimizerRule {
116 pub optimize: unsafe extern "C" fn(
117 &Self,
118 plan: &FFI_ExecutionPlan,
119 config: FFI_ConfigOptions,
120 ) -> FFI_Result<FFI_ExecutionPlan>,
121
122 pub name: unsafe extern "C" fn(&Self) -> SString,
123
124 pub schema_check: unsafe extern "C" fn(&Self) -> bool,
125
126 pub clone: unsafe extern "C" fn(plan: &Self) -> Self,
129
130 pub release: unsafe extern "C" fn(arg: &mut Self),
132
133 pub version: unsafe extern "C" fn() -> u64,
135
136 pub optimize_with_context: unsafe extern "C" fn(
137 &Self,
138 plan: &FFI_ExecutionPlan,
139 context: &FFI_PhysicalOptimizerContext,
140 ) -> FFI_Result<FFI_ExecutionPlan>,
141
142 pub private_data: *mut c_void,
145
146 pub library_marker_id: extern "C" fn() -> usize,
149}
150
151unsafe impl Send for FFI_PhysicalOptimizerRule {}
152unsafe impl Sync for FFI_PhysicalOptimizerRule {}
153
154struct RulePrivateData {
155 rule: Arc<dyn PhysicalOptimizerRule + Send + Sync>,
156 runtime: Option<Handle>,
157}
158
159impl FFI_PhysicalOptimizerRule {
160 fn inner(&self) -> &Arc<dyn PhysicalOptimizerRule + Send + Sync> {
161 let private_data = self.private_data as *const RulePrivateData;
162 unsafe { &(*private_data).rule }
163 }
164
165 fn runtime(&self) -> Option<Handle> {
166 let private_data = self.private_data as *const RulePrivateData;
167 unsafe { (*private_data).runtime.clone() }
168 }
169}
170
171unsafe extern "C" fn optimize_fn_wrapper(
172 rule: &FFI_PhysicalOptimizerRule,
173 plan: &FFI_ExecutionPlan,
174 config: FFI_ConfigOptions,
175) -> FFI_Result<FFI_ExecutionPlan> {
176 let runtime = rule.runtime();
177 let rule = rule.inner();
178 let plan: Arc<dyn ExecutionPlan> = sresult_return!(plan.try_into());
179 let config = sresult_return!(ConfigOptions::try_from(config));
180 let optimized_plan = sresult_return!(rule.optimize(plan, &config));
181
182 FFI_Result::Ok(FFI_ExecutionPlan::new(optimized_plan, runtime))
183}
184
185unsafe extern "C" fn optimize_with_context_fn_wrapper(
186 rule: &FFI_PhysicalOptimizerRule,
187 plan: &FFI_ExecutionPlan,
188 context: &FFI_PhysicalOptimizerContext,
189) -> FFI_Result<FFI_ExecutionPlan> {
190 let runtime = rule.runtime();
191 let inner = rule.inner();
192 let plan: Arc<dyn ExecutionPlan> = sresult_return!(plan.try_into());
193 let config = sresult_return!(ConfigOptions::try_from(unsafe {
194 (context.config_options)(context)
195 }));
196 let foreign_ctx = ForeignOptimizerContext { config };
197 let optimized_plan = sresult_return!(inner.optimize_with_context(plan, &foreign_ctx));
198
199 FFI_Result::Ok(FFI_ExecutionPlan::new(optimized_plan, runtime))
200}
201
202unsafe extern "C" fn name_fn_wrapper(rule: &FFI_PhysicalOptimizerRule) -> SString {
203 let rule = rule.inner();
204 rule.name().into()
205}
206
207unsafe extern "C" fn schema_check_fn_wrapper(rule: &FFI_PhysicalOptimizerRule) -> bool {
208 rule.inner().schema_check()
209}
210
211unsafe extern "C" fn release_fn_wrapper(rule: &mut FFI_PhysicalOptimizerRule) {
212 unsafe {
213 debug_assert!(!rule.private_data.is_null());
214 let private_data = Box::from_raw(rule.private_data as *mut RulePrivateData);
215 drop(private_data);
216 rule.private_data = std::ptr::null_mut();
217 }
218}
219
220unsafe extern "C" fn clone_fn_wrapper(
221 rule: &FFI_PhysicalOptimizerRule,
222) -> FFI_PhysicalOptimizerRule {
223 let runtime = rule.runtime();
224 let rule = Arc::clone(rule.inner());
225
226 let private_data =
227 Box::into_raw(Box::new(RulePrivateData { rule, runtime })) as *mut c_void;
228
229 FFI_PhysicalOptimizerRule {
230 optimize: optimize_fn_wrapper,
231 optimize_with_context: optimize_with_context_fn_wrapper,
232 name: name_fn_wrapper,
233 schema_check: schema_check_fn_wrapper,
234 clone: clone_fn_wrapper,
235 release: release_fn_wrapper,
236 version: super::version,
237 private_data,
238 library_marker_id: crate::get_library_marker_id,
239 }
240}
241
242impl Drop for FFI_PhysicalOptimizerRule {
243 fn drop(&mut self) {
244 unsafe { (self.release)(self) }
245 }
246}
247
248impl FFI_PhysicalOptimizerRule {
249 pub fn new(
251 rule: Arc<dyn PhysicalOptimizerRule + Send + Sync>,
252 runtime: Option<Handle>,
253 ) -> Self {
254 if let Some(rule) = (Arc::clone(&rule) as Arc<dyn std::any::Any>)
255 .downcast_ref::<ForeignPhysicalOptimizerRule>()
256 {
257 return rule.rule.clone();
258 }
259
260 let private_data = Box::new(RulePrivateData { rule, runtime });
261 let private_data = Box::into_raw(private_data) as *mut c_void;
262
263 Self {
264 optimize: optimize_fn_wrapper,
265 optimize_with_context: optimize_with_context_fn_wrapper,
266 name: name_fn_wrapper,
267 schema_check: schema_check_fn_wrapper,
268 clone: clone_fn_wrapper,
269 release: release_fn_wrapper,
270 version: super::version,
271 private_data,
272 library_marker_id: crate::get_library_marker_id,
273 }
274 }
275}
276
277#[derive(Debug)]
282pub struct ForeignPhysicalOptimizerRule {
283 name: String,
284 rule: FFI_PhysicalOptimizerRule,
285}
286
287unsafe impl Send for ForeignPhysicalOptimizerRule {}
288unsafe impl Sync for ForeignPhysicalOptimizerRule {}
289
290impl From<&FFI_PhysicalOptimizerRule> for Arc<dyn PhysicalOptimizerRule + Send + Sync> {
291 fn from(rule: &FFI_PhysicalOptimizerRule) -> Self {
292 if (rule.library_marker_id)() == crate::get_library_marker_id() {
293 return Arc::clone(rule.inner());
294 }
295
296 let name: String = unsafe { (rule.name)(rule).into() };
297 Arc::new(ForeignPhysicalOptimizerRule {
298 name,
299 rule: rule.clone(),
300 }) as Arc<dyn PhysicalOptimizerRule + Send + Sync>
301 }
302}
303
304impl Clone for FFI_PhysicalOptimizerRule {
305 fn clone(&self) -> Self {
306 unsafe { (self.clone)(self) }
307 }
308}
309
310#[async_trait]
311impl PhysicalOptimizerRule for ForeignPhysicalOptimizerRule {
312 fn optimize(
313 &self,
314 plan: Arc<dyn ExecutionPlan>,
315 config: &ConfigOptions,
316 ) -> Result<Arc<dyn ExecutionPlan>> {
317 let config_options: FFI_ConfigOptions = config.into();
318 let plan = FFI_ExecutionPlan::new(plan, None);
319
320 let optimized_plan = unsafe {
321 df_result!((self.rule.optimize)(&self.rule, &plan, config_options))?
322 };
323 (&optimized_plan).try_into()
324 }
325
326 fn optimize_with_context(
327 &self,
328 plan: Arc<dyn ExecutionPlan>,
329 context: &dyn PhysicalOptimizerContext,
330 ) -> Result<Arc<dyn ExecutionPlan>> {
331 let ffi_context = FFI_PhysicalOptimizerContext::new(context);
332 let plan = FFI_ExecutionPlan::new(plan, None);
333
334 let optimized_plan = unsafe {
335 df_result!((self.rule.optimize_with_context)(
336 &self.rule,
337 &plan,
338 &ffi_context
339 ))?
340 };
341 (&optimized_plan).try_into()
342 }
343
344 fn name(&self) -> &str {
345 &self.name
346 }
347
348 fn schema_check(&self) -> bool {
349 unsafe { (self.rule.schema_check)(&self.rule) }
350 }
351}
352
353#[cfg(test)]
354mod tests {
355 use std::sync::Arc;
356
357 use arrow::datatypes::{DataType, Field, Schema};
358 use datafusion_common::config::ConfigOptions;
359 use datafusion_common::error::Result;
360 use datafusion_physical_optimizer::{
361 ConfigOnlyContext, PhysicalOptimizerContext, PhysicalOptimizerRule,
362 };
363 use datafusion_physical_plan::ExecutionPlan;
364 use datafusion_physical_plan::operator_statistics::StatisticsRegistry;
365
366 use super::*;
367 use crate::execution_plan::tests::EmptyExec;
368
369 #[derive(Debug)]
370 struct NoOpRule {
371 schema_check: bool,
372 }
373
374 impl PhysicalOptimizerRule for NoOpRule {
375 fn optimize(
376 &self,
377 plan: Arc<dyn ExecutionPlan>,
378 _config: &ConfigOptions,
379 ) -> Result<Arc<dyn ExecutionPlan>> {
380 Ok(plan)
381 }
382
383 fn name(&self) -> &str {
384 "no_op_rule"
385 }
386
387 fn schema_check(&self) -> bool {
388 self.schema_check
389 }
390 }
391
392 #[derive(Debug)]
395 struct ContextAwareRule;
396
397 impl PhysicalOptimizerRule for ContextAwareRule {
398 fn optimize(
399 &self,
400 _plan: Arc<dyn ExecutionPlan>,
401 _config: &ConfigOptions,
402 ) -> Result<Arc<dyn ExecutionPlan>> {
403 Err(datafusion_common::DataFusionError::Plan(
404 "optimize should not be called directly".to_string(),
405 ))
406 }
407
408 fn optimize_with_context(
409 &self,
410 plan: Arc<dyn ExecutionPlan>,
411 _context: &dyn PhysicalOptimizerContext,
412 ) -> Result<Arc<dyn ExecutionPlan>> {
413 Ok(plan)
414 }
415
416 fn name(&self) -> &str {
417 "context_aware_rule"
418 }
419
420 fn schema_check(&self) -> bool {
421 true
422 }
423 }
424
425 fn create_test_plan() -> Arc<dyn ExecutionPlan> {
426 let schema =
427 Arc::new(Schema::new(vec![Field::new("a", DataType::Float32, false)]));
428 Arc::new(EmptyExec::new(schema))
429 }
430
431 #[test]
432 fn test_round_trip_ffi_physical_optimizer_rule() -> Result<()> {
433 for expected_schema_check in [true, false] {
434 let rule: Arc<dyn PhysicalOptimizerRule + Send + Sync> = Arc::new(NoOpRule {
435 schema_check: expected_schema_check,
436 });
437
438 let mut ffi_rule = FFI_PhysicalOptimizerRule::new(rule, None);
439 ffi_rule.library_marker_id = crate::mock_foreign_marker_id;
440
441 let foreign_rule: Arc<dyn PhysicalOptimizerRule + Send + Sync> =
442 (&ffi_rule).into();
443
444 assert_eq!(foreign_rule.name(), "no_op_rule");
445 assert_eq!(foreign_rule.schema_check(), expected_schema_check);
446 }
447
448 Ok(())
449 }
450
451 #[test]
452 fn test_round_trip_optimize() -> Result<()> {
453 let rule: Arc<dyn PhysicalOptimizerRule + Send + Sync> =
454 Arc::new(NoOpRule { schema_check: true });
455
456 let mut ffi_rule = FFI_PhysicalOptimizerRule::new(rule, None);
457 ffi_rule.library_marker_id = crate::mock_foreign_marker_id;
458
459 let foreign_rule: Arc<dyn PhysicalOptimizerRule + Send + Sync> =
460 (&ffi_rule).into();
461
462 let plan = create_test_plan();
463 let config = ConfigOptions::new();
464
465 let optimized = foreign_rule.optimize(plan, &config)?;
466 assert_eq!(optimized.name(), "empty-exec");
467
468 Ok(())
469 }
470
471 #[test]
472 fn test_local_bypass() -> Result<()> {
473 let rule: Arc<dyn PhysicalOptimizerRule + Send + Sync> =
474 Arc::new(NoOpRule { schema_check: true });
475
476 let ffi_rule = FFI_PhysicalOptimizerRule::new(rule, None);
478 let recovered: Arc<dyn PhysicalOptimizerRule + Send + Sync> = (&ffi_rule).into();
479 let any_ref: &dyn std::any::Any = &*recovered;
480 assert!(any_ref.downcast_ref::<NoOpRule>().is_some());
481
482 let rule2: Arc<dyn PhysicalOptimizerRule + Send + Sync> =
484 Arc::new(NoOpRule { schema_check: true });
485 let mut ffi_rule2 = FFI_PhysicalOptimizerRule::new(rule2, None);
486 ffi_rule2.library_marker_id = crate::mock_foreign_marker_id;
487 let recovered2: Arc<dyn PhysicalOptimizerRule + Send + Sync> =
488 (&ffi_rule2).into();
489 let any_ref2: &dyn std::any::Any = &*recovered2;
490 assert!(
491 any_ref2
492 .downcast_ref::<ForeignPhysicalOptimizerRule>()
493 .is_some()
494 );
495
496 Ok(())
497 }
498
499 #[test]
500 fn test_clone() -> Result<()> {
501 let rule: Arc<dyn PhysicalOptimizerRule + Send + Sync> =
502 Arc::new(NoOpRule { schema_check: true });
503
504 let ffi_rule = FFI_PhysicalOptimizerRule::new(rule, None);
505 let cloned = ffi_rule.clone();
506
507 let name1: String = unsafe { (ffi_rule.name)(&ffi_rule).into() };
508 let name2: String = unsafe { (cloned.name)(&cloned).into() };
509 assert_eq!(name1, name2);
510
511 Ok(())
512 }
513
514 #[test]
515 fn test_foreign_rule_rewrap_bypass() -> Result<()> {
516 let rule: Arc<dyn PhysicalOptimizerRule + Send + Sync> =
519 Arc::new(NoOpRule { schema_check: true });
520
521 let mut ffi_rule = FFI_PhysicalOptimizerRule::new(rule, None);
522 ffi_rule.library_marker_id = crate::mock_foreign_marker_id;
523
524 let foreign_rule: Arc<dyn PhysicalOptimizerRule + Send + Sync> =
525 (&ffi_rule).into();
526
527 let re_wrapped = FFI_PhysicalOptimizerRule::new(foreign_rule, None);
529 let name: String = unsafe { (re_wrapped.name)(&re_wrapped).into() };
530 assert_eq!(name, "no_op_rule");
531
532 Ok(())
533 }
534
535 #[test]
536 fn test_optimize_with_context_round_trip() -> Result<()> {
537 let rule: Arc<dyn PhysicalOptimizerRule + Send + Sync> =
538 Arc::new(ContextAwareRule);
539
540 let mut ffi_rule = FFI_PhysicalOptimizerRule::new(rule, None);
541 ffi_rule.library_marker_id = crate::mock_foreign_marker_id;
542
543 let foreign_rule: Arc<dyn PhysicalOptimizerRule + Send + Sync> =
544 (&ffi_rule).into();
545
546 let plan = create_test_plan();
547 let config = ConfigOptions::new();
548 let context = ConfigOnlyContext::new(&config);
549
550 let optimized = foreign_rule.optimize_with_context(plan, &context)?;
551 assert_eq!(optimized.name(), "empty-exec");
552
553 Ok(())
554 }
555
556 #[test]
562 fn test_optimize_with_context_with_registry() -> Result<()> {
563 let rule: Arc<dyn PhysicalOptimizerRule + Send + Sync> =
564 Arc::new(ContextAwareRule);
565
566 let mut ffi_rule = FFI_PhysicalOptimizerRule::new(rule, None);
567 ffi_rule.library_marker_id = crate::mock_foreign_marker_id;
568
569 let foreign_rule: Arc<dyn PhysicalOptimizerRule + Send + Sync> =
570 (&ffi_rule).into();
571
572 struct ContextWithRegistry {
573 config: ConfigOptions,
574 registry: StatisticsRegistry,
575 }
576
577 impl PhysicalOptimizerContext for ContextWithRegistry {
578 fn config_options(&self) -> &ConfigOptions {
579 &self.config
580 }
581
582 fn statistics_registry(&self) -> Option<&StatisticsRegistry> {
583 Some(&self.registry)
584 }
585 }
586
587 let ctx = ContextWithRegistry {
588 config: ConfigOptions::new(),
589 registry: StatisticsRegistry::default_with_builtin_providers(),
590 };
591
592 let plan = create_test_plan();
593 let optimized = foreign_rule.optimize_with_context(plan, &ctx)?;
596 assert_eq!(optimized.name(), "empty-exec");
597
598 Ok(())
599 }
600}