1use std::any::Any;
34use std::collections::HashMap;
35use std::ffi::c_void;
36use std::sync::{Arc, OnceLock};
37
38use arrow_schema::SchemaRef;
39use arrow_schema::ffi::FFI_ArrowSchema;
40use async_ffi::{FfiFuture, FutureExt};
41use async_trait::async_trait;
42use datafusion_common::config::{ConfigFileType, ConfigOptions, TableOptions};
43use datafusion_common::{DFSchema, DataFusionError};
44use datafusion_execution::TaskContext;
45use datafusion_execution::config::SessionConfig;
46use datafusion_execution::runtime_env::RuntimeEnv;
47use datafusion_expr::execution_props::ExecutionProps;
48use datafusion_expr::registry::{ExtensionTypeRegistryRef, MemoryExtensionTypeRegistry};
49use datafusion_expr::{
50 AggregateUDF, AggregateUDFImpl, Expr, HigherOrderUDF, LogicalPlan, ScalarUDF,
51 ScalarUDFImpl, WindowUDF, WindowUDFImpl,
52};
53use datafusion_physical_expr::PhysicalExpr;
54use datafusion_physical_plan::ExecutionPlan;
55use datafusion_proto::bytes::{
56 logical_plan_from_bytes, logical_plan_from_bytes_with_extension_codec,
57 logical_plan_to_bytes, logical_plan_to_bytes_with_extension_codec,
58};
59use datafusion_proto::logical_plan::LogicalExtensionCodec;
60use datafusion_proto::logical_plan::from_proto::parse_expr;
61use datafusion_proto::logical_plan::to_proto::serialize_expr;
62use datafusion_proto::physical_plan::DefaultPhysicalExtensionCodec;
63use datafusion_proto::protobuf::LogicalExprNode;
64use datafusion_session::{
65 CatalogProviderList, PhysicalOptimizerRule, QueryPlanner, Session,
66};
67use prost::Message;
68
69use stabby::str::Str as SStr;
70use stabby::string::String as SString;
71use stabby::vec::Vec as SVec;
72use tokio::runtime::Handle;
73
74use crate::arrow_wrappers::WrappedSchema;
75use crate::catalog_provider_list::FFI_CatalogProviderList;
76use crate::execution::FFI_TaskContext;
77use crate::execution_plan::FFI_ExecutionPlan;
78use crate::physical_expr::FFI_PhysicalExpr;
79use crate::physical_optimizer::FFI_PhysicalOptimizerRule;
80use crate::proto::logical_extension_codec::FFI_LogicalExtensionCodec;
81use crate::proto::physical_extension_codec::FFI_PhysicalExtensionCodec;
82use crate::query_planner::FFI_QueryPlanner;
83use crate::session::config::FFI_SessionConfig;
84use crate::udaf::FFI_AggregateUDF;
85use crate::udf::FFI_ScalarUDF;
86use crate::udwf::FFI_WindowUDF;
87use crate::util::FFI_Result;
88use crate::{df_result, sresult, sresult_return};
89
90pub mod config;
91
92#[repr(C)]
105#[derive(Debug)]
106pub(crate) struct FFI_SessionRef {
107 session_id: unsafe extern "C" fn(&Self) -> SStr,
108
109 config: unsafe extern "C" fn(&Self) -> FFI_SessionConfig,
110
111 catalog_list: unsafe extern "C" fn(&Self) -> FFI_CatalogProviderList,
112
113 query_planner: unsafe extern "C" fn(&Self) -> FFI_QueryPlanner,
114
115 optimize: unsafe extern "C" fn(
116 &Self,
117 logical_plan_serialized: SVec<u8>,
118 ) -> FFI_Result<SVec<u8>>,
119
120 create_physical_plan:
121 unsafe extern "C" fn(
122 &Self,
123 logical_plan_serialized: SVec<u8>,
124 ) -> FfiFuture<FFI_Result<FFI_ExecutionPlan>>,
125
126 create_physical_expr: unsafe extern "C" fn(
127 &Self,
128 expr_serialized: SVec<u8>,
129 schema: WrappedSchema,
130 ) -> FFI_Result<FFI_PhysicalExpr>,
131
132 scalar_functions: unsafe extern "C" fn(&Self) -> SVec<(SString, FFI_ScalarUDF)>,
133
134 aggregate_functions: unsafe extern "C" fn(&Self) -> SVec<(SString, FFI_AggregateUDF)>,
135
136 window_functions: unsafe extern "C" fn(&Self) -> SVec<(SString, FFI_WindowUDF)>,
137
138 table_options: unsafe extern "C" fn(&Self) -> SVec<(SString, SString)>,
139
140 default_table_options: unsafe extern "C" fn(&Self) -> SVec<(SString, SString)>,
141
142 task_ctx: unsafe extern "C" fn(&Self) -> FFI_TaskContext,
143
144 physical_optimizers: unsafe extern "C" fn(&Self) -> SVec<FFI_PhysicalOptimizerRule>,
145
146 logical_codec: FFI_LogicalExtensionCodec,
147
148 physical_codec: FFI_PhysicalExtensionCodec,
149
150 clone: unsafe extern "C" fn(plan: &Self) -> Self,
153
154 release: unsafe extern "C" fn(arg: &mut Self),
156
157 pub version: unsafe extern "C" fn() -> u64,
159
160 private_data: *mut c_void,
163
164 pub library_marker_id: extern "C" fn() -> usize,
167}
168
169unsafe impl Send for FFI_SessionRef {}
170unsafe impl Sync for FFI_SessionRef {}
171
172struct SessionPrivateData<'a> {
173 session: &'a dyn Session,
174 runtime: Option<Handle>,
175}
176
177impl FFI_SessionRef {
178 fn inner(&self) -> &dyn Session {
179 let private_data = self.private_data as *const SessionPrivateData;
180 unsafe { (*private_data).session }
181 }
182
183 unsafe fn runtime(&self) -> &Option<Handle> {
184 unsafe {
185 let private_data = self.private_data as *const SessionPrivateData;
186 &(*private_data).runtime
187 }
188 }
189}
190
191unsafe extern "C" fn session_id_fn_wrapper(session: &FFI_SessionRef) -> SStr<'_> {
192 let session = session.inner();
193 session.session_id().into()
194}
195
196unsafe extern "C" fn config_fn_wrapper(session: &FFI_SessionRef) -> FFI_SessionConfig {
197 let session = session.inner();
198 session.config().into()
199}
200
201unsafe extern "C" fn catalog_list_fn_wrapper(
202 session: &FFI_SessionRef,
203) -> FFI_CatalogProviderList {
204 FFI_CatalogProviderList::new_with_ffi_codec(
205 session.inner().catalog_list(),
206 unsafe { session.runtime() }.clone(),
207 session.logical_codec.clone(),
208 )
209}
210
211unsafe extern "C" fn query_planner_fn_wrapper(
212 session: &FFI_SessionRef,
213) -> FFI_QueryPlanner {
214 FFI_QueryPlanner::new_with_ffi_codecs(
215 session.inner().query_planner(),
216 session.logical_codec.clone(),
217 session.physical_codec.clone(),
218 )
219}
220
221unsafe extern "C" fn optimize_fn_wrapper(
222 session: &FFI_SessionRef,
223 logical_plan_serialized: SVec<u8>,
224) -> FFI_Result<SVec<u8>> {
225 let logical_codec: Arc<dyn LogicalExtensionCodec> = (&session.logical_codec).into();
226 let inner = session.inner();
227 let logical_plan = sresult_return!(logical_plan_from_bytes_with_extension_codec(
228 logical_plan_serialized.as_slice(),
229 inner.task_ctx().as_ref(),
230 logical_codec.as_ref(),
231 ));
232 let optimized_plan = sresult_return!(inner.optimize(&logical_plan));
233 let optimized_plan = sresult_return!(logical_plan_to_bytes_with_extension_codec(
234 &optimized_plan,
235 logical_codec.as_ref(),
236 ));
237
238 FFI_Result::Ok(SVec::from(optimized_plan.as_ref()))
239}
240
241unsafe extern "C" fn create_physical_plan_fn_wrapper(
242 session: &FFI_SessionRef,
243 logical_plan_serialized: SVec<u8>,
244) -> FfiFuture<FFI_Result<FFI_ExecutionPlan>> {
245 unsafe {
246 let runtime = session.runtime().clone();
247 let session = session.clone();
248 async move {
249 let session = session.inner();
250 let task_ctx = session.task_ctx();
251
252 let logical_plan = sresult_return!(logical_plan_from_bytes(
253 logical_plan_serialized.as_slice(),
254 task_ctx.as_ref(),
255 ));
256
257 let physical_plan = session.create_physical_plan(&logical_plan).await;
258
259 sresult!(physical_plan.map(|plan| FFI_ExecutionPlan::new(plan, runtime)))
260 }
261 .into_ffi()
262 }
263}
264
265unsafe extern "C" fn create_physical_expr_fn_wrapper(
266 session: &FFI_SessionRef,
267 expr_serialized: SVec<u8>,
268 schema: WrappedSchema,
269) -> FFI_Result<FFI_PhysicalExpr> {
270 let codec: Arc<dyn LogicalExtensionCodec> = (&session.logical_codec).into();
271 let session = session.inner();
272
273 let logical_expr = LogicalExprNode::decode(expr_serialized.as_slice()).unwrap();
274 let logical_expr =
275 parse_expr(&logical_expr, session.task_ctx().as_ref(), codec.as_ref()).unwrap();
276 let schema: SchemaRef = schema.into();
277 let schema: DFSchema = sresult_return!(schema.try_into());
278
279 let physical_expr =
280 sresult_return!(session.create_physical_expr(logical_expr, &schema));
281
282 FFI_Result::Ok(physical_expr.into())
283}
284
285unsafe extern "C" fn scalar_functions_fn_wrapper(
286 session: &FFI_SessionRef,
287) -> SVec<(SString, FFI_ScalarUDF)> {
288 let session = session.inner();
289 session
290 .scalar_functions()
291 .iter()
292 .map(|(name, udf)| (name.clone().into(), FFI_ScalarUDF::from(Arc::clone(udf))))
293 .collect()
294}
295
296unsafe extern "C" fn aggregate_functions_fn_wrapper(
297 session: &FFI_SessionRef,
298) -> SVec<(SString, FFI_AggregateUDF)> {
299 let session = session.inner();
300 session
301 .aggregate_functions()
302 .iter()
303 .map(|(name, udaf)| {
304 (
305 name.clone().into(),
306 FFI_AggregateUDF::from(Arc::clone(udaf)),
307 )
308 })
309 .collect()
310}
311
312unsafe extern "C" fn window_functions_fn_wrapper(
313 session: &FFI_SessionRef,
314) -> SVec<(SString, FFI_WindowUDF)> {
315 let session = session.inner();
316 session
317 .window_functions()
318 .iter()
319 .map(|(name, udwf)| (name.clone().into(), FFI_WindowUDF::from(Arc::clone(udwf))))
320 .collect()
321}
322
323fn table_options_to_rhash(mut options: TableOptions) -> SVec<(SString, SString)> {
324 let current_format = options.current_format.take();
329 let mut options: HashMap<SString, SString> = options
330 .entries()
331 .into_iter()
332 .filter_map(|entry| entry.value.map(|v| (entry.key.into(), v.into())))
333 .collect();
334 if let Some(current_format) = current_format {
335 options.insert(
336 "datafusion_ffi.table_current_format".into(),
337 match current_format {
338 ConfigFileType::JSON => "json",
339 #[cfg(feature = "parquet")]
340 ConfigFileType::PARQUET => "parquet",
341 ConfigFileType::CSV => "csv",
342 }
343 .into(),
344 );
345 }
346
347 options.into_iter().collect()
348}
349
350unsafe extern "C" fn table_options_fn_wrapper(
351 session: &FFI_SessionRef,
352) -> SVec<(SString, SString)> {
353 let session = session.inner();
354 let table_options = session.table_options();
355 table_options_to_rhash(table_options.clone())
356}
357
358unsafe extern "C" fn default_table_options_fn_wrapper(
359 session: &FFI_SessionRef,
360) -> SVec<(SString, SString)> {
361 let session = session.inner();
362 let table_options = session.default_table_options();
363
364 table_options_to_rhash(table_options)
365}
366
367unsafe extern "C" fn task_ctx_fn_wrapper(session: &FFI_SessionRef) -> FFI_TaskContext {
368 session.inner().task_ctx().into()
369}
370
371unsafe extern "C" fn physical_optimizers_fn_wrapper(
372 session: &FFI_SessionRef,
373) -> SVec<FFI_PhysicalOptimizerRule> {
374 let runtime = unsafe { session.runtime().clone() };
375 session
376 .inner()
377 .physical_optimizers()
378 .iter()
379 .map(|rule| FFI_PhysicalOptimizerRule::new(Arc::clone(rule), runtime.clone()))
380 .collect()
381}
382
383unsafe extern "C" fn release_fn_wrapper(provider: &mut FFI_SessionRef) {
384 unsafe {
385 let private_data =
386 Box::from_raw(provider.private_data as *mut SessionPrivateData);
387 drop(private_data);
388 }
389}
390
391unsafe extern "C" fn clone_fn_wrapper(provider: &FFI_SessionRef) -> FFI_SessionRef {
392 unsafe {
393 let old_private_data = provider.private_data as *const SessionPrivateData;
394
395 let private_data = Box::into_raw(Box::new(SessionPrivateData {
396 session: (*old_private_data).session,
397 runtime: (*old_private_data).runtime.clone(),
398 })) as *mut c_void;
399
400 FFI_SessionRef {
401 session_id: session_id_fn_wrapper,
402 config: config_fn_wrapper,
403 catalog_list: catalog_list_fn_wrapper,
404 query_planner: query_planner_fn_wrapper,
405 optimize: optimize_fn_wrapper,
406 create_physical_plan: create_physical_plan_fn_wrapper,
407 create_physical_expr: create_physical_expr_fn_wrapper,
408 scalar_functions: scalar_functions_fn_wrapper,
409 aggregate_functions: aggregate_functions_fn_wrapper,
410 window_functions: window_functions_fn_wrapper,
411 table_options: table_options_fn_wrapper,
412 default_table_options: default_table_options_fn_wrapper,
413 task_ctx: task_ctx_fn_wrapper,
414 physical_optimizers: physical_optimizers_fn_wrapper,
415 logical_codec: provider.logical_codec.clone(),
416 physical_codec: provider.physical_codec.clone(),
417
418 clone: clone_fn_wrapper,
419 release: release_fn_wrapper,
420 version: super::version,
421 private_data,
422 library_marker_id: crate::get_library_marker_id,
423 }
424 }
425}
426
427impl Drop for FFI_SessionRef {
428 fn drop(&mut self) {
429 unsafe { (self.release)(self) }
430 }
431}
432
433impl FFI_SessionRef {
434 pub fn new(
452 session: &dyn Session,
453 runtime: Option<Handle>,
454 logical_codec: FFI_LogicalExtensionCodec,
455 ) -> Self {
456 let physical_codec = FFI_PhysicalExtensionCodec::new(
460 Arc::new(DefaultPhysicalExtensionCodec {}),
461 runtime.clone(),
462 logical_codec.task_ctx_provider.clone(),
463 );
464 Self::new_with_ffi_codecs(session, runtime, logical_codec, physical_codec)
465 }
466
467 pub fn new_with_ffi_codecs(
478 session: &dyn Session,
479 runtime: Option<Handle>,
480 logical_codec: FFI_LogicalExtensionCodec,
481 physical_codec: FFI_PhysicalExtensionCodec,
482 ) -> Self {
483 if let Some(session) = session.as_any().downcast_ref::<ForeignSession>() {
484 let mut session = session.session.clone();
485 session.logical_codec = logical_codec;
486 session.physical_codec = physical_codec;
487 return session;
488 }
489
490 let private_data = Box::new(SessionPrivateData { session, runtime });
491
492 Self {
493 session_id: session_id_fn_wrapper,
494 config: config_fn_wrapper,
495 catalog_list: catalog_list_fn_wrapper,
496 query_planner: query_planner_fn_wrapper,
497 optimize: optimize_fn_wrapper,
498 create_physical_plan: create_physical_plan_fn_wrapper,
499 create_physical_expr: create_physical_expr_fn_wrapper,
500 scalar_functions: scalar_functions_fn_wrapper,
501 aggregate_functions: aggregate_functions_fn_wrapper,
502 window_functions: window_functions_fn_wrapper,
503 table_options: table_options_fn_wrapper,
504 default_table_options: default_table_options_fn_wrapper,
505 task_ctx: task_ctx_fn_wrapper,
506 physical_optimizers: physical_optimizers_fn_wrapper,
507 logical_codec,
508 physical_codec,
509
510 clone: clone_fn_wrapper,
511 release: release_fn_wrapper,
512 version: super::version,
513 private_data: Box::into_raw(private_data) as *mut c_void,
514 library_marker_id: crate::get_library_marker_id,
515 }
516 }
517}
518
519#[derive(Debug)]
533pub struct ForeignSession {
534 session: FFI_SessionRef,
535 config: SessionConfig,
536 catalog_list: Arc<dyn CatalogProviderList>,
537 scalar_functions: HashMap<String, Arc<ScalarUDF>>,
538 higher_order_functions: HashMap<String, Arc<HigherOrderUDF>>,
539 aggregate_functions: HashMap<String, Arc<AggregateUDF>>,
540 window_functions: HashMap<String, Arc<WindowUDF>>,
541 extension_types: ExtensionTypeRegistryRef,
542 table_options: TableOptions,
543 runtime_env: Arc<RuntimeEnv>,
544 props: ExecutionProps,
545 query_planner: OnceLock<Arc<dyn QueryPlanner + Send + Sync>>,
546 physical_optimizers: OnceLock<Vec<Arc<dyn PhysicalOptimizerRule + Send + Sync>>>,
547}
548
549unsafe impl Send for ForeignSession {}
550unsafe impl Sync for ForeignSession {}
551
552impl FFI_SessionRef {
553 pub fn as_local(&self) -> Option<&dyn Session> {
554 if (self.library_marker_id)() == crate::get_library_marker_id() {
555 return Some(self.inner());
556 }
557 None
558 }
559}
560
561impl TryFrom<&FFI_SessionRef> for ForeignSession {
562 type Error = DataFusionError;
563 fn try_from(session: &FFI_SessionRef) -> Result<Self, Self::Error> {
564 unsafe {
565 let table_options =
566 table_options_from_rhashmap((session.table_options)(session));
567
568 let config = (session.config)(session);
569 let config = SessionConfig::try_from(&config)?;
570
571 let ffi_catalog_list = (session.catalog_list)(session);
572 let catalog_list = (&ffi_catalog_list).into();
573
574 let scalar_functions = (session.scalar_functions)(session)
575 .into_iter()
576 .map(|kv_pair| {
577 let udf = <Arc<dyn ScalarUDFImpl>>::from(&kv_pair.1);
578
579 (
580 kv_pair.0.to_string(),
581 Arc::new(ScalarUDF::new_from_shared_impl(udf)),
582 )
583 })
584 .collect();
585 let aggregate_functions = (session.aggregate_functions)(session)
586 .into_iter()
587 .map(|kv_pair| {
588 let udaf = <Arc<dyn AggregateUDFImpl>>::from(&kv_pair.1);
589
590 (
591 kv_pair.0.to_string(),
592 Arc::new(AggregateUDF::new_from_shared_impl(udaf)),
593 )
594 })
595 .collect();
596 let window_functions = (session.window_functions)(session)
597 .into_iter()
598 .map(|kv_pair| {
599 let udwf = <Arc<dyn WindowUDFImpl>>::from(&kv_pair.1);
600
601 (
602 kv_pair.0.to_string(),
603 Arc::new(WindowUDF::new_from_shared_impl(udwf)),
604 )
605 })
606 .collect();
607 Ok(Self {
608 session: session.clone(),
609 config,
610 catalog_list,
611 table_options,
612 scalar_functions,
613 higher_order_functions: HashMap::new(),
614 aggregate_functions,
615 window_functions,
616 extension_types: Arc::new(MemoryExtensionTypeRegistry::default()),
617 runtime_env: Default::default(),
618 props: Default::default(),
619 query_planner: OnceLock::new(),
620 physical_optimizers: OnceLock::new(),
621 })
622 }
623 }
624}
625
626impl Clone for FFI_SessionRef {
627 fn clone(&self) -> Self {
628 unsafe { (self.clone)(self) }
629 }
630}
631
632fn table_options_from_rhashmap(options: SVec<(SString, SString)>) -> TableOptions {
633 let mut options: HashMap<String, String> = options
634 .into_iter()
635 .map(|kv_pair| (kv_pair.0.to_string(), kv_pair.1.to_string()))
636 .collect();
637 let current_format = options.remove("datafusion_ffi.table_current_format");
638
639 let mut table_options = TableOptions::default();
640 let formats = [
641 ConfigFileType::CSV,
642 ConfigFileType::JSON,
643 #[cfg(feature = "parquet")]
644 ConfigFileType::PARQUET,
645 ];
646 for format in formats {
647 let format_name = match &format {
650 ConfigFileType::CSV => "csv",
651 #[cfg(feature = "parquet")]
652 ConfigFileType::PARQUET => "parquet",
653 ConfigFileType::JSON => "json",
654 };
655 let format_options: HashMap<String, String> = options
656 .iter()
657 .filter_map(|(k, v)| {
658 let (prefix, key) = k.split_once(".")?;
659 if prefix == format_name {
660 Some((format!("format.{key}"), v.to_owned()))
661 } else {
662 None
663 }
664 })
665 .collect();
666 if !format_options.is_empty() {
667 table_options.current_format = Some(format.clone());
668 table_options
669 .alter_with_string_hash_map(&format_options)
670 .unwrap_or_else(|err| log::warn!("Error parsing table options: {err}"));
671 }
672 }
673 let extension_options: HashMap<String, String> = options
674 .iter()
675 .filter_map(|(k, v)| {
676 let (prefix, _) = k.split_once(".")?;
677 if !["json", "parquet", "csv"].contains(&prefix) {
678 Some((k.to_owned(), v.to_owned()))
679 } else {
680 None
681 }
682 })
683 .collect();
684 if !extension_options.is_empty() {
685 table_options
686 .alter_with_string_hash_map(&extension_options)
687 .unwrap_or_else(|err| log::warn!("Error parsing table options: {err}"));
688 }
689
690 table_options.current_format =
691 current_format.and_then(|format| match format.as_str() {
692 "csv" => Some(ConfigFileType::CSV),
693 #[cfg(feature = "parquet")]
694 "parquet" => Some(ConfigFileType::PARQUET),
695 "json" => Some(ConfigFileType::JSON),
696 _ => None,
697 });
698 table_options
699}
700
701#[async_trait]
702impl Session for ForeignSession {
703 fn session_id(&self) -> &str {
704 unsafe { (self.session.session_id)(&self.session).as_str() }
705 }
706
707 fn config(&self) -> &SessionConfig {
708 &self.config
709 }
710
711 fn config_options(&self) -> &ConfigOptions {
712 self.config.options()
713 }
714
715 fn catalog_list(&self) -> Arc<dyn CatalogProviderList> {
716 Arc::clone(&self.catalog_list)
717 }
718
719 fn query_planner(&self) -> Arc<dyn QueryPlanner + Send + Sync> {
720 Arc::clone(self.query_planner.get_or_init(|| unsafe {
721 let planner = (self.session.query_planner)(&self.session);
722 (&planner).into()
723 }))
724 }
725
726 fn optimize(&self, plan: &LogicalPlan) -> datafusion_common::Result<LogicalPlan> {
727 unsafe {
728 let codec: Arc<dyn LogicalExtensionCodec> =
729 (&self.session.logical_codec).into();
730 let logical_plan =
731 logical_plan_to_bytes_with_extension_codec(plan, codec.as_ref())?;
732 let optimized_plan = df_result!((self.session.optimize)(
733 &self.session,
734 SVec::from(logical_plan.as_ref()),
735 ))?;
736 logical_plan_from_bytes_with_extension_codec(
737 optimized_plan.as_slice(),
738 self.task_ctx().as_ref(),
739 codec.as_ref(),
740 )
741 }
742 }
743
744 async fn create_physical_plan(
745 &self,
746 logical_plan: &LogicalPlan,
747 ) -> datafusion_common::Result<Arc<dyn ExecutionPlan>> {
748 unsafe {
749 let logical_plan = logical_plan_to_bytes(logical_plan)?;
750 let physical_plan = df_result!(
751 (self.session.create_physical_plan)(
752 &self.session,
753 logical_plan.as_ref().into()
754 )
755 .await
756 )?;
757 let physical_plan = <Arc<dyn ExecutionPlan>>::try_from(&physical_plan)?;
758
759 Ok(physical_plan)
760 }
761 }
762
763 fn create_physical_expr(
764 &self,
765 expr: Expr,
766 df_schema: &DFSchema,
767 ) -> datafusion_common::Result<Arc<dyn PhysicalExpr>> {
768 unsafe {
769 let codec: Arc<dyn LogicalExtensionCodec> =
770 (&self.session.logical_codec).into();
771 let logical_expr = serialize_expr(&expr, codec.as_ref())?.encode_to_vec();
772 let schema = WrappedSchema(FFI_ArrowSchema::try_from(df_schema.as_arrow())?);
773
774 let physical_expr = df_result!((self.session.create_physical_expr)(
775 &self.session,
776 logical_expr.into_iter().collect(),
777 schema
778 ))?;
779
780 Ok((&physical_expr).into())
781 }
782 }
783
784 fn physical_optimizers(&self) -> &[Arc<dyn PhysicalOptimizerRule + Send + Sync>] {
785 self.physical_optimizers.get_or_init(|| unsafe {
786 (self.session.physical_optimizers)(&self.session)
787 .into_iter()
788 .map(|rule| (&rule).into())
789 .collect()
790 })
791 }
792
793 fn scalar_functions(&self) -> &HashMap<String, Arc<ScalarUDF>> {
794 &self.scalar_functions
795 }
796
797 fn higher_order_functions(&self) -> &HashMap<String, Arc<HigherOrderUDF>> {
798 &self.higher_order_functions
799 }
800
801 fn aggregate_functions(&self) -> &HashMap<String, Arc<AggregateUDF>> {
802 &self.aggregate_functions
803 }
804
805 fn window_functions(&self) -> &HashMap<String, Arc<WindowUDF>> {
806 &self.window_functions
807 }
808
809 fn extension_type_registry(&self) -> &ExtensionTypeRegistryRef {
810 &self.extension_types
811 }
812
813 fn runtime_env(&self) -> &Arc<RuntimeEnv> {
814 &self.runtime_env
815 }
816
817 fn execution_props(&self) -> &ExecutionProps {
818 &self.props
819 }
820
821 fn as_any(&self) -> &dyn Any {
822 self
823 }
824
825 fn table_options(&self) -> &TableOptions {
826 &self.table_options
827 }
828
829 fn default_table_options(&self) -> TableOptions {
830 unsafe {
831 table_options_from_rhashmap((self.session.default_table_options)(
832 &self.session,
833 ))
834 }
835 }
836
837 fn table_options_mut(&mut self) -> &mut TableOptions {
838 log::warn!(
839 "Mutating table options is not supported via FFI. Changes will not have an effect."
840 );
841 &mut self.table_options
842 }
843
844 fn task_ctx(&self) -> Arc<TaskContext> {
845 unsafe { (self.session.task_ctx)(&self.session).into() }
846 }
847}
848
849#[cfg(test)]
850mod tests {
851 use std::sync::Arc;
852 use std::sync::atomic::{AtomicUsize, Ordering};
853
854 use arrow_schema::{DataType, Field, Schema};
855 use datafusion::catalog::MemoryCatalogProvider;
856 use datafusion::execution::SessionStateBuilder;
857 use datafusion_common::DataFusionError;
858 use datafusion_expr::col;
859 use datafusion_expr::registry::FunctionRegistry;
860 use datafusion_proto::logical_plan::DefaultLogicalExtensionCodec;
861
862 use super::*;
863
864 static QUERY_PLANNER_CALLS: AtomicUsize = AtomicUsize::new(0);
865 static PHYSICAL_OPTIMIZER_CALLS: AtomicUsize = AtomicUsize::new(0);
866
867 unsafe extern "C" fn counting_query_planner(
868 session: &FFI_SessionRef,
869 ) -> FFI_QueryPlanner {
870 QUERY_PLANNER_CALLS.fetch_add(1, Ordering::Relaxed);
871 unsafe { query_planner_fn_wrapper(session) }
872 }
873
874 unsafe extern "C" fn counting_physical_optimizers(
875 session: &FFI_SessionRef,
876 ) -> SVec<FFI_PhysicalOptimizerRule> {
877 PHYSICAL_OPTIMIZER_CALLS.fetch_add(1, Ordering::Relaxed);
878 unsafe { physical_optimizers_fn_wrapper(session) }
879 }
880
881 #[test]
882 fn test_foreign_session_lazily_loads_planning_state() -> Result<(), DataFusionError> {
883 QUERY_PLANNER_CALLS.store(0, Ordering::Relaxed);
884 PHYSICAL_OPTIMIZER_CALLS.store(0, Ordering::Relaxed);
885
886 let (ctx, task_ctx_provider) = crate::util::tests::test_session_and_ctx();
887 let logical_codec = FFI_LogicalExtensionCodec::new(
888 Arc::new(DefaultLogicalExtensionCodec {}),
889 None,
890 task_ctx_provider,
891 );
892 let state = ctx.state();
893 let mut local_session = FFI_SessionRef::new(&state, None, logical_codec);
894 local_session.query_planner = counting_query_planner;
895 local_session.physical_optimizers = counting_physical_optimizers;
896
897 let mut foreign_session = ForeignSession::try_from(&local_session)?;
898 assert_eq!(QUERY_PLANNER_CALLS.load(Ordering::Relaxed), 0);
899 assert_eq!(PHYSICAL_OPTIMIZER_CALLS.load(Ordering::Relaxed), 0);
900
901 foreign_session.session.query_planner = counting_query_planner;
904 foreign_session.session.physical_optimizers = counting_physical_optimizers;
905
906 foreign_session.query_planner();
907 foreign_session.query_planner();
908 assert_eq!(QUERY_PLANNER_CALLS.load(Ordering::Relaxed), 1);
909
910 foreign_session.physical_optimizers();
911 foreign_session.physical_optimizers();
912 assert_eq!(PHYSICAL_OPTIMIZER_CALLS.load(Ordering::Relaxed), 1);
913
914 Ok(())
915 }
916
917 #[tokio::test]
918 async fn test_ffi_session() -> Result<(), DataFusionError> {
919 let (ctx, task_ctx_provider) = crate::util::tests::test_session_and_ctx();
920 let mut table_options = TableOptions::default();
921 table_options.csv.has_header = Some(true);
922 table_options.json.schema_infer_max_rec = Some(10);
923 #[cfg(feature = "parquet")]
924 {
925 table_options.parquet.global.coerce_int96 = Some("123456789".into());
926 }
927 table_options.current_format = Some(ConfigFileType::JSON);
928
929 let state = SessionStateBuilder::new_from_existing(ctx.state())
930 .with_table_options(table_options)
931 .build();
932
933 let logical_codec = FFI_LogicalExtensionCodec::new(
934 Arc::new(DefaultLogicalExtensionCodec {}),
935 None,
936 task_ctx_provider,
937 );
938
939 let local_session = FFI_SessionRef::new(&state, None, logical_codec);
940 let foreign_session = ForeignSession::try_from(&local_session)?;
941
942 let schema = Arc::new(Schema::new(vec![Field::new("a", DataType::Int32, false)]));
943 let df_schema = schema.try_into()?;
944 let physical_expr = foreign_session.create_physical_expr(col("a"), &df_schema)?;
945 assert_eq!(
946 format!("{physical_expr:?}"),
947 "Column { name: \"a\", index: 0 }"
948 );
949
950 assert_eq!(foreign_session.session_id(), state.session_id());
951
952 let foreign_catalog_list = foreign_session.catalog_list();
953 assert_eq!(
954 foreign_catalog_list.catalog_names(),
955 state.catalog_list().catalog_names()
956 );
957 foreign_catalog_list.register_catalog(
958 "foreign_registered".to_owned(),
959 Arc::new(MemoryCatalogProvider::new()),
960 );
961 assert!(state.catalog_list().catalog("foreign_registered").is_some());
962
963 let logical_plan = LogicalPlan::default();
964 assert_eq!(foreign_session.optimize(&logical_plan)?, logical_plan);
965 assert_eq!(
966 foreign_session.physical_optimizers().len(),
967 state.physical_optimizers().len()
968 );
969 assert!(foreign_session.statistics_registry().is_none());
970 let planned = foreign_session
971 .query_planner()
972 .create_physical_plan(&logical_plan, &foreign_session)
973 .await?;
974 assert_eq!(planned.name(), "EmptyExec");
975
976 let physical_plan = foreign_session.create_physical_plan(&logical_plan).await?;
977 assert_eq!(
978 format!("{physical_plan:?}"),
979 "EmptyExec { schema: Schema { fields: [], metadata: {} }, partitions: 1, cache: PlanProperties { eq_properties: EquivalenceProperties { eq_group: EquivalenceGroup { map: {}, classes: [] }, oeq_class: OrderingEquivalenceClass { orderings: [] }, oeq_cache: OrderingEquivalenceCache { normal_cls: OrderingEquivalenceClass { orderings: [] }, leading_map: {} }, constraints: Constraints { inner: [] }, schema: Schema { fields: [], metadata: {} } }, partitioning: UnknownPartitioning(1), emission_type: Incremental, boundedness: Bounded, evaluation_type: Lazy, scheduling_type: Cooperative, output_ordering: None } }"
980 );
981
982 assert_eq!(
983 format!("{:?}", foreign_session.default_table_options()),
984 format!("{:?}", state.default_table_options())
985 );
986
987 assert_eq!(
988 format!("{:?}", foreign_session.table_options()),
989 format!("{:?}", state.table_options())
990 );
991
992 let local_udfs = state.udfs();
993 for udf in foreign_session.scalar_functions().keys() {
994 assert!(local_udfs.contains(udf));
995 }
996 let local_udafs = state.udafs();
997 for udaf in foreign_session.aggregate_functions().keys() {
998 assert!(local_udafs.contains(udaf));
999 }
1000 let local_udwfs = state.udwfs();
1001 for udwf in foreign_session.window_functions().keys() {
1002 assert!(local_udwfs.contains(udwf));
1003 }
1004
1005 Ok(())
1006 }
1007}