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_with_extension_codec,
57 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 logical_codec: Arc<dyn LogicalExtensionCodec> =
250 (&session.logical_codec).into();
251 let session = session.inner();
252 let task_ctx = session.task_ctx();
253
254 let logical_plan =
255 sresult_return!(logical_plan_from_bytes_with_extension_codec(
256 logical_plan_serialized.as_slice(),
257 task_ctx.as_ref(),
258 logical_codec.as_ref(),
259 ));
260
261 let physical_plan = session.create_physical_plan(&logical_plan).await;
262
263 sresult!(physical_plan.map(|plan| FFI_ExecutionPlan::new(plan, runtime)))
264 }
265 .into_ffi()
266 }
267}
268
269unsafe extern "C" fn create_physical_expr_fn_wrapper(
270 session: &FFI_SessionRef,
271 expr_serialized: SVec<u8>,
272 schema: WrappedSchema,
273) -> FFI_Result<FFI_PhysicalExpr> {
274 let codec: Arc<dyn LogicalExtensionCodec> = (&session.logical_codec).into();
275 let session = session.inner();
276
277 let logical_expr = LogicalExprNode::decode(expr_serialized.as_slice()).unwrap();
278 let logical_expr =
279 parse_expr(&logical_expr, session.task_ctx().as_ref(), codec.as_ref()).unwrap();
280 let schema: SchemaRef = schema.into();
281 let schema: DFSchema = sresult_return!(schema.try_into());
282
283 let physical_expr =
284 sresult_return!(session.create_physical_expr(logical_expr, &schema));
285
286 FFI_Result::Ok(physical_expr.into())
287}
288
289unsafe extern "C" fn scalar_functions_fn_wrapper(
290 session: &FFI_SessionRef,
291) -> SVec<(SString, FFI_ScalarUDF)> {
292 let session = session.inner();
293 session
294 .scalar_functions()
295 .iter()
296 .map(|(name, udf)| (name.clone().into(), FFI_ScalarUDF::from(Arc::clone(udf))))
297 .collect()
298}
299
300unsafe extern "C" fn aggregate_functions_fn_wrapper(
301 session: &FFI_SessionRef,
302) -> SVec<(SString, FFI_AggregateUDF)> {
303 let session = session.inner();
304 session
305 .aggregate_functions()
306 .iter()
307 .map(|(name, udaf)| {
308 (
309 name.clone().into(),
310 FFI_AggregateUDF::from(Arc::clone(udaf)),
311 )
312 })
313 .collect()
314}
315
316unsafe extern "C" fn window_functions_fn_wrapper(
317 session: &FFI_SessionRef,
318) -> SVec<(SString, FFI_WindowUDF)> {
319 let session = session.inner();
320 session
321 .window_functions()
322 .iter()
323 .map(|(name, udwf)| (name.clone().into(), FFI_WindowUDF::from(Arc::clone(udwf))))
324 .collect()
325}
326
327fn table_options_to_rhash(mut options: TableOptions) -> SVec<(SString, SString)> {
328 let current_format = options.current_format.take();
333 let mut options: HashMap<SString, SString> = options
334 .entries()
335 .into_iter()
336 .filter_map(|entry| entry.value.map(|v| (entry.key.into(), v.into())))
337 .collect();
338 if let Some(current_format) = current_format {
339 options.insert(
340 "datafusion_ffi.table_current_format".into(),
341 match current_format {
342 ConfigFileType::JSON => "json",
343 #[cfg(feature = "parquet")]
344 ConfigFileType::PARQUET => "parquet",
345 ConfigFileType::CSV => "csv",
346 }
347 .into(),
348 );
349 }
350
351 options.into_iter().collect()
352}
353
354unsafe extern "C" fn table_options_fn_wrapper(
355 session: &FFI_SessionRef,
356) -> SVec<(SString, SString)> {
357 let session = session.inner();
358 let table_options = session.table_options();
359 table_options_to_rhash(table_options.clone())
360}
361
362unsafe extern "C" fn default_table_options_fn_wrapper(
363 session: &FFI_SessionRef,
364) -> SVec<(SString, SString)> {
365 let session = session.inner();
366 let table_options = session.default_table_options();
367
368 table_options_to_rhash(table_options)
369}
370
371unsafe extern "C" fn task_ctx_fn_wrapper(session: &FFI_SessionRef) -> FFI_TaskContext {
372 session.inner().task_ctx().into()
373}
374
375unsafe extern "C" fn physical_optimizers_fn_wrapper(
376 session: &FFI_SessionRef,
377) -> SVec<FFI_PhysicalOptimizerRule> {
378 let runtime = unsafe { session.runtime().clone() };
379 session
380 .inner()
381 .physical_optimizers()
382 .iter()
383 .map(|rule| FFI_PhysicalOptimizerRule::new(Arc::clone(rule), runtime.clone()))
384 .collect()
385}
386
387unsafe extern "C" fn release_fn_wrapper(provider: &mut FFI_SessionRef) {
388 unsafe {
389 let private_data =
390 Box::from_raw(provider.private_data as *mut SessionPrivateData);
391 drop(private_data);
392 }
393}
394
395unsafe extern "C" fn clone_fn_wrapper(provider: &FFI_SessionRef) -> FFI_SessionRef {
396 unsafe {
397 let old_private_data = provider.private_data as *const SessionPrivateData;
398
399 let private_data = Box::into_raw(Box::new(SessionPrivateData {
400 session: (*old_private_data).session,
401 runtime: (*old_private_data).runtime.clone(),
402 })) as *mut c_void;
403
404 FFI_SessionRef {
405 session_id: session_id_fn_wrapper,
406 config: config_fn_wrapper,
407 catalog_list: catalog_list_fn_wrapper,
408 query_planner: query_planner_fn_wrapper,
409 optimize: optimize_fn_wrapper,
410 create_physical_plan: create_physical_plan_fn_wrapper,
411 create_physical_expr: create_physical_expr_fn_wrapper,
412 scalar_functions: scalar_functions_fn_wrapper,
413 aggregate_functions: aggregate_functions_fn_wrapper,
414 window_functions: window_functions_fn_wrapper,
415 table_options: table_options_fn_wrapper,
416 default_table_options: default_table_options_fn_wrapper,
417 task_ctx: task_ctx_fn_wrapper,
418 physical_optimizers: physical_optimizers_fn_wrapper,
419 logical_codec: provider.logical_codec.clone(),
420 physical_codec: provider.physical_codec.clone(),
421
422 clone: clone_fn_wrapper,
423 release: release_fn_wrapper,
424 version: super::version,
425 private_data,
426 library_marker_id: crate::get_library_marker_id,
427 }
428 }
429}
430
431impl Drop for FFI_SessionRef {
432 fn drop(&mut self) {
433 unsafe { (self.release)(self) }
434 }
435}
436
437impl FFI_SessionRef {
438 pub fn new(
456 session: &dyn Session,
457 runtime: Option<Handle>,
458 logical_codec: FFI_LogicalExtensionCodec,
459 ) -> Self {
460 let physical_codec = FFI_PhysicalExtensionCodec::new(
464 Arc::new(DefaultPhysicalExtensionCodec {}),
465 runtime.clone(),
466 logical_codec.task_ctx_provider.clone(),
467 );
468 Self::new_with_ffi_codecs(session, runtime, logical_codec, physical_codec)
469 }
470
471 pub fn new_with_ffi_codecs(
482 session: &dyn Session,
483 runtime: Option<Handle>,
484 logical_codec: FFI_LogicalExtensionCodec,
485 physical_codec: FFI_PhysicalExtensionCodec,
486 ) -> Self {
487 if let Some(session) = session.as_any().downcast_ref::<ForeignSession>() {
488 let mut session = session.session.clone();
489 session.logical_codec = logical_codec;
490 session.physical_codec = physical_codec;
491 return session;
492 }
493
494 let private_data = Box::new(SessionPrivateData { session, runtime });
495
496 Self {
497 session_id: session_id_fn_wrapper,
498 config: config_fn_wrapper,
499 catalog_list: catalog_list_fn_wrapper,
500 query_planner: query_planner_fn_wrapper,
501 optimize: optimize_fn_wrapper,
502 create_physical_plan: create_physical_plan_fn_wrapper,
503 create_physical_expr: create_physical_expr_fn_wrapper,
504 scalar_functions: scalar_functions_fn_wrapper,
505 aggregate_functions: aggregate_functions_fn_wrapper,
506 window_functions: window_functions_fn_wrapper,
507 table_options: table_options_fn_wrapper,
508 default_table_options: default_table_options_fn_wrapper,
509 task_ctx: task_ctx_fn_wrapper,
510 physical_optimizers: physical_optimizers_fn_wrapper,
511 logical_codec,
512 physical_codec,
513
514 clone: clone_fn_wrapper,
515 release: release_fn_wrapper,
516 version: super::version,
517 private_data: Box::into_raw(private_data) as *mut c_void,
518 library_marker_id: crate::get_library_marker_id,
519 }
520 }
521}
522
523#[derive(Debug)]
537pub struct ForeignSession {
538 session: FFI_SessionRef,
539 config: SessionConfig,
540 catalog_list: Arc<dyn CatalogProviderList>,
541 scalar_functions: HashMap<String, Arc<ScalarUDF>>,
542 higher_order_functions: HashMap<String, Arc<HigherOrderUDF>>,
543 aggregate_functions: HashMap<String, Arc<AggregateUDF>>,
544 window_functions: HashMap<String, Arc<WindowUDF>>,
545 extension_types: ExtensionTypeRegistryRef,
546 table_options: TableOptions,
547 runtime_env: Arc<RuntimeEnv>,
548 props: ExecutionProps,
549 query_planner: OnceLock<Arc<dyn QueryPlanner + Send + Sync>>,
550 physical_optimizers: OnceLock<Vec<Arc<dyn PhysicalOptimizerRule + Send + Sync>>>,
551}
552
553unsafe impl Send for ForeignSession {}
554unsafe impl Sync for ForeignSession {}
555
556impl FFI_SessionRef {
557 pub fn as_local(&self) -> Option<&dyn Session> {
558 if (self.library_marker_id)() == crate::get_library_marker_id() {
559 return Some(self.inner());
560 }
561 None
562 }
563}
564
565impl TryFrom<&FFI_SessionRef> for ForeignSession {
566 type Error = DataFusionError;
567 fn try_from(session: &FFI_SessionRef) -> Result<Self, Self::Error> {
568 unsafe {
569 let table_options =
570 table_options_from_rhashmap((session.table_options)(session));
571
572 let config = (session.config)(session);
573 let config = SessionConfig::try_from(&config)?;
574
575 let ffi_catalog_list = (session.catalog_list)(session);
576 let catalog_list = (&ffi_catalog_list).into();
577
578 let scalar_functions = (session.scalar_functions)(session)
579 .into_iter()
580 .map(|kv_pair| {
581 let udf = <Arc<dyn ScalarUDFImpl>>::from(&kv_pair.1);
582
583 (
584 kv_pair.0.to_string(),
585 Arc::new(ScalarUDF::new_from_shared_impl(udf)),
586 )
587 })
588 .collect();
589 let aggregate_functions = (session.aggregate_functions)(session)
590 .into_iter()
591 .map(|kv_pair| {
592 let udaf = <Arc<dyn AggregateUDFImpl>>::from(&kv_pair.1);
593
594 (
595 kv_pair.0.to_string(),
596 Arc::new(AggregateUDF::new_from_shared_impl(udaf)),
597 )
598 })
599 .collect();
600 let window_functions = (session.window_functions)(session)
601 .into_iter()
602 .map(|kv_pair| {
603 let udwf = <Arc<dyn WindowUDFImpl>>::from(&kv_pair.1);
604
605 (
606 kv_pair.0.to_string(),
607 Arc::new(WindowUDF::new_from_shared_impl(udwf)),
608 )
609 })
610 .collect();
611 Ok(Self {
612 session: session.clone(),
613 config,
614 catalog_list,
615 table_options,
616 scalar_functions,
617 higher_order_functions: HashMap::new(),
618 aggregate_functions,
619 window_functions,
620 extension_types: Arc::new(MemoryExtensionTypeRegistry::default()),
621 runtime_env: Default::default(),
622 props: Default::default(),
623 query_planner: OnceLock::new(),
624 physical_optimizers: OnceLock::new(),
625 })
626 }
627 }
628}
629
630impl Clone for FFI_SessionRef {
631 fn clone(&self) -> Self {
632 unsafe { (self.clone)(self) }
633 }
634}
635
636fn table_options_from_rhashmap(options: SVec<(SString, SString)>) -> TableOptions {
637 let mut options: HashMap<String, String> = options
638 .into_iter()
639 .map(|kv_pair| (kv_pair.0.to_string(), kv_pair.1.to_string()))
640 .collect();
641 let current_format = options.remove("datafusion_ffi.table_current_format");
642
643 let mut table_options = TableOptions::default();
644 let formats = [
645 ConfigFileType::CSV,
646 ConfigFileType::JSON,
647 #[cfg(feature = "parquet")]
648 ConfigFileType::PARQUET,
649 ];
650 for format in formats {
651 let format_name = match &format {
654 ConfigFileType::CSV => "csv",
655 #[cfg(feature = "parquet")]
656 ConfigFileType::PARQUET => "parquet",
657 ConfigFileType::JSON => "json",
658 };
659 let format_options: HashMap<String, String> = options
660 .iter()
661 .filter_map(|(k, v)| {
662 let (prefix, key) = k.split_once(".")?;
663 if prefix == format_name {
664 Some((format!("format.{key}"), v.to_owned()))
665 } else {
666 None
667 }
668 })
669 .collect();
670 if !format_options.is_empty() {
671 table_options.current_format = Some(format.clone());
672 table_options
673 .alter_with_string_hash_map(&format_options)
674 .unwrap_or_else(|err| log::warn!("Error parsing table options: {err}"));
675 }
676 }
677 let extension_options: HashMap<String, String> = options
678 .iter()
679 .filter_map(|(k, v)| {
680 let (prefix, _) = k.split_once(".")?;
681 if !["json", "parquet", "csv"].contains(&prefix) {
682 Some((k.to_owned(), v.to_owned()))
683 } else {
684 None
685 }
686 })
687 .collect();
688 if !extension_options.is_empty() {
689 table_options
690 .alter_with_string_hash_map(&extension_options)
691 .unwrap_or_else(|err| log::warn!("Error parsing table options: {err}"));
692 }
693
694 table_options.current_format =
695 current_format.and_then(|format| match format.as_str() {
696 "csv" => Some(ConfigFileType::CSV),
697 #[cfg(feature = "parquet")]
698 "parquet" => Some(ConfigFileType::PARQUET),
699 "json" => Some(ConfigFileType::JSON),
700 _ => None,
701 });
702 table_options
703}
704
705#[async_trait]
706impl Session for ForeignSession {
707 fn session_id(&self) -> &str {
708 unsafe { (self.session.session_id)(&self.session).as_str() }
709 }
710
711 fn config(&self) -> &SessionConfig {
712 &self.config
713 }
714
715 fn config_options(&self) -> &ConfigOptions {
716 self.config.options()
717 }
718
719 fn catalog_list(&self) -> Arc<dyn CatalogProviderList> {
720 Arc::clone(&self.catalog_list)
721 }
722
723 fn query_planner(&self) -> Arc<dyn QueryPlanner + Send + Sync> {
724 Arc::clone(self.query_planner.get_or_init(|| unsafe {
725 let planner = (self.session.query_planner)(&self.session);
726 (&planner).into()
727 }))
728 }
729
730 fn optimize(&self, plan: &LogicalPlan) -> datafusion_common::Result<LogicalPlan> {
731 unsafe {
732 let codec: Arc<dyn LogicalExtensionCodec> =
733 (&self.session.logical_codec).into();
734 let logical_plan =
735 logical_plan_to_bytes_with_extension_codec(plan, codec.as_ref())?;
736 let optimized_plan = df_result!((self.session.optimize)(
737 &self.session,
738 SVec::from(logical_plan.as_ref()),
739 ))?;
740 logical_plan_from_bytes_with_extension_codec(
741 optimized_plan.as_slice(),
742 self.task_ctx().as_ref(),
743 codec.as_ref(),
744 )
745 }
746 }
747
748 async fn create_physical_plan(
749 &self,
750 logical_plan: &LogicalPlan,
751 ) -> datafusion_common::Result<Arc<dyn ExecutionPlan>> {
752 unsafe {
753 let codec: Arc<dyn LogicalExtensionCodec> =
754 (&self.session.logical_codec).into();
755 let logical_plan =
756 logical_plan_to_bytes_with_extension_codec(logical_plan, codec.as_ref())?;
757 let physical_plan = df_result!(
758 (self.session.create_physical_plan)(
759 &self.session,
760 logical_plan.as_ref().into()
761 )
762 .await
763 )?;
764 let physical_plan = <Arc<dyn ExecutionPlan>>::try_from(&physical_plan)?;
765
766 Ok(physical_plan)
767 }
768 }
769
770 fn create_physical_expr(
771 &self,
772 expr: Expr,
773 df_schema: &DFSchema,
774 ) -> datafusion_common::Result<Arc<dyn PhysicalExpr>> {
775 unsafe {
776 let codec: Arc<dyn LogicalExtensionCodec> =
777 (&self.session.logical_codec).into();
778 let logical_expr = serialize_expr(&expr, codec.as_ref())?.encode_to_vec();
779 let schema = WrappedSchema(FFI_ArrowSchema::try_from(df_schema.as_arrow())?);
780
781 let physical_expr = df_result!((self.session.create_physical_expr)(
782 &self.session,
783 logical_expr.into_iter().collect(),
784 schema
785 ))?;
786
787 Ok((&physical_expr).into())
788 }
789 }
790
791 fn physical_optimizers(&self) -> &[Arc<dyn PhysicalOptimizerRule + Send + Sync>] {
792 self.physical_optimizers.get_or_init(|| unsafe {
793 (self.session.physical_optimizers)(&self.session)
794 .into_iter()
795 .map(|rule| (&rule).into())
796 .collect()
797 })
798 }
799
800 fn scalar_functions(&self) -> &HashMap<String, Arc<ScalarUDF>> {
801 &self.scalar_functions
802 }
803
804 fn higher_order_functions(&self) -> &HashMap<String, Arc<HigherOrderUDF>> {
805 &self.higher_order_functions
806 }
807
808 fn aggregate_functions(&self) -> &HashMap<String, Arc<AggregateUDF>> {
809 &self.aggregate_functions
810 }
811
812 fn window_functions(&self) -> &HashMap<String, Arc<WindowUDF>> {
813 &self.window_functions
814 }
815
816 fn extension_type_registry(&self) -> &ExtensionTypeRegistryRef {
817 &self.extension_types
818 }
819
820 fn runtime_env(&self) -> &Arc<RuntimeEnv> {
821 &self.runtime_env
822 }
823
824 fn execution_props(&self) -> &ExecutionProps {
825 &self.props
826 }
827
828 fn as_any(&self) -> &dyn Any {
829 self
830 }
831
832 fn table_options(&self) -> &TableOptions {
833 &self.table_options
834 }
835
836 fn default_table_options(&self) -> TableOptions {
837 unsafe {
838 table_options_from_rhashmap((self.session.default_table_options)(
839 &self.session,
840 ))
841 }
842 }
843
844 fn table_options_mut(&mut self) -> &mut TableOptions {
845 log::warn!(
846 "Mutating table options is not supported via FFI. Changes will not have an effect."
847 );
848 &mut self.table_options
849 }
850
851 fn task_ctx(&self) -> Arc<TaskContext> {
852 unsafe { (self.session.task_ctx)(&self.session).into() }
853 }
854}
855
856#[cfg(test)]
857mod tests {
858 use std::sync::Arc;
859 use std::sync::atomic::{AtomicUsize, Ordering};
860
861 use arrow::array::record_batch;
862 use arrow_schema::{DataType, Field, Schema};
863 use datafusion::catalog::{MemTable, MemoryCatalogProvider};
864 use datafusion::execution::SessionStateBuilder;
865 use datafusion_common::DataFusionError;
866 use datafusion_expr::col;
867 use datafusion_expr::registry::FunctionRegistry;
868 use datafusion_proto::logical_plan::DefaultLogicalExtensionCodec;
869
870 use super::*;
871 use crate::proto::physical_extension_codec::tests::TestExtensionCodec;
872
873 static QUERY_PLANNER_CALLS: AtomicUsize = AtomicUsize::new(0);
874 static PHYSICAL_OPTIMIZER_CALLS: AtomicUsize = AtomicUsize::new(0);
875
876 unsafe extern "C" fn counting_query_planner(
877 session: &FFI_SessionRef,
878 ) -> FFI_QueryPlanner {
879 QUERY_PLANNER_CALLS.fetch_add(1, Ordering::Relaxed);
880 unsafe { query_planner_fn_wrapper(session) }
881 }
882
883 unsafe extern "C" fn counting_physical_optimizers(
884 session: &FFI_SessionRef,
885 ) -> SVec<FFI_PhysicalOptimizerRule> {
886 PHYSICAL_OPTIMIZER_CALLS.fetch_add(1, Ordering::Relaxed);
887 unsafe { physical_optimizers_fn_wrapper(session) }
888 }
889
890 #[test]
891 fn test_foreign_session_lazily_loads_planning_state() -> Result<(), DataFusionError> {
892 QUERY_PLANNER_CALLS.store(0, Ordering::Relaxed);
893 PHYSICAL_OPTIMIZER_CALLS.store(0, Ordering::Relaxed);
894
895 let (ctx, task_ctx_provider) = crate::util::tests::test_session_and_ctx();
896 let logical_codec = FFI_LogicalExtensionCodec::new(
897 Arc::new(DefaultLogicalExtensionCodec {}),
898 None,
899 task_ctx_provider,
900 );
901 let state = ctx.state();
902 let mut local_session = FFI_SessionRef::new(&state, None, logical_codec);
903 local_session.query_planner = counting_query_planner;
904 local_session.physical_optimizers = counting_physical_optimizers;
905
906 let mut foreign_session = ForeignSession::try_from(&local_session)?;
907 assert_eq!(QUERY_PLANNER_CALLS.load(Ordering::Relaxed), 0);
908 assert_eq!(PHYSICAL_OPTIMIZER_CALLS.load(Ordering::Relaxed), 0);
909
910 foreign_session.session.query_planner = counting_query_planner;
913 foreign_session.session.physical_optimizers = counting_physical_optimizers;
914
915 foreign_session.query_planner();
916 foreign_session.query_planner();
917 assert_eq!(QUERY_PLANNER_CALLS.load(Ordering::Relaxed), 1);
918
919 foreign_session.physical_optimizers();
920 foreign_session.physical_optimizers();
921 assert_eq!(PHYSICAL_OPTIMIZER_CALLS.load(Ordering::Relaxed), 1);
922
923 Ok(())
924 }
925
926 #[tokio::test]
927 async fn test_ffi_session() -> Result<(), DataFusionError> {
928 let (ctx, task_ctx_provider) = crate::util::tests::test_session_and_ctx();
929 let mut table_options = TableOptions::default();
930 table_options.csv.has_header = Some(true);
931 table_options.json.schema_infer_max_rec = Some(10);
932 #[cfg(feature = "parquet")]
933 {
934 table_options.parquet.global.coerce_int96 = Some("123456789".into());
935 }
936 table_options.current_format = Some(ConfigFileType::JSON);
937
938 let state = SessionStateBuilder::new_from_existing(ctx.state())
939 .with_table_options(table_options)
940 .build();
941
942 let logical_codec = FFI_LogicalExtensionCodec::new(
943 Arc::new(DefaultLogicalExtensionCodec {}),
944 None,
945 task_ctx_provider,
946 );
947
948 let local_session = FFI_SessionRef::new(&state, None, logical_codec);
949 let foreign_session = ForeignSession::try_from(&local_session)?;
950
951 let schema = Arc::new(Schema::new(vec![Field::new("a", DataType::Int32, false)]));
952 let df_schema = schema.try_into()?;
953 let physical_expr = foreign_session.create_physical_expr(col("a"), &df_schema)?;
954 assert_eq!(
955 format!("{physical_expr:?}"),
956 "Column { name: \"a\", index: 0 }"
957 );
958
959 assert_eq!(foreign_session.session_id(), state.session_id());
960
961 let foreign_catalog_list = foreign_session.catalog_list();
962 assert_eq!(
963 foreign_catalog_list.catalog_names(),
964 state.catalog_list().catalog_names()
965 );
966 foreign_catalog_list.register_catalog(
967 "foreign_registered".to_owned(),
968 Arc::new(MemoryCatalogProvider::new()),
969 );
970 assert!(state.catalog_list().catalog("foreign_registered").is_some());
971
972 let logical_plan = LogicalPlan::default();
973 assert_eq!(foreign_session.optimize(&logical_plan)?, logical_plan);
974 assert_eq!(
975 foreign_session.physical_optimizers().len(),
976 state.physical_optimizers().len()
977 );
978 assert!(foreign_session.statistics_registry().is_none());
979 let planned = foreign_session
980 .query_planner()
981 .create_physical_plan(&logical_plan, &foreign_session)
982 .await?;
983 assert_eq!(planned.name(), "EmptyExec");
984
985 let physical_plan = foreign_session.create_physical_plan(&logical_plan).await?;
986 assert_eq!(
987 format!("{physical_plan:?}"),
988 "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 } }"
989 );
990
991 assert_eq!(
992 format!("{:?}", foreign_session.default_table_options()),
993 format!("{:?}", state.default_table_options())
994 );
995
996 assert_eq!(
997 format!("{:?}", foreign_session.table_options()),
998 format!("{:?}", state.table_options())
999 );
1000
1001 let local_udfs = state.udfs();
1002 for udf in foreign_session.scalar_functions().keys() {
1003 assert!(local_udfs.contains(udf));
1004 }
1005 let local_udafs = state.udafs();
1006 for udaf in foreign_session.aggregate_functions().keys() {
1007 assert!(local_udafs.contains(udaf));
1008 }
1009 let local_udwfs = state.udwfs();
1010 for udwf in foreign_session.window_functions().keys() {
1011 assert!(local_udwfs.contains(udwf));
1012 }
1013
1014 Ok(())
1015 }
1016
1017 #[tokio::test]
1021 async fn test_create_physical_plan_uses_logical_codec() -> Result<(), DataFusionError>
1022 {
1023 let (ctx, task_ctx_provider) = crate::util::tests::test_session_and_ctx();
1024
1025 let schema = Arc::new(Schema::new(vec![Field::new("a", DataType::Int32, true)]));
1026 let batch = record_batch!(("a", Int32, [1, 2, 3]))?;
1027 let table = MemTable::try_new(schema, vec![vec![batch]])?;
1028 ctx.register_table("test_table", Arc::new(table))?;
1029
1030 let logical_codec = FFI_LogicalExtensionCodec::new(
1031 Arc::new(TestExtensionCodec),
1032 None,
1033 task_ctx_provider,
1034 );
1035
1036 let state = ctx.state();
1037 let local_session = FFI_SessionRef::new(&state, None, logical_codec);
1038 let foreign_session = ForeignSession::try_from(&local_session)?;
1039
1040 let logical_plan = ctx.table("test_table").await?.into_optimized_plan()?;
1041 let physical_plan = foreign_session.create_physical_plan(&logical_plan).await?;
1042
1043 assert_eq!(physical_plan.name(), "DataSourceExec");
1044 assert_eq!(physical_plan.schema().field(0).name(), "a");
1045
1046 Ok(())
1047 }
1048}