1use std::ffi::c_void;
19use std::sync::Arc;
20
21use arrow::datatypes::SchemaRef;
22use async_ffi::{FfiFuture, FutureExt};
23use async_trait::async_trait;
24use datafusion_catalog::{Session, TableProvider};
25use datafusion_common::Statistics;
26use datafusion_common::error::{DataFusionError, Result};
27use datafusion_execution::TaskContext;
28use datafusion_expr::dml::InsertOp;
29use datafusion_expr::{Expr, TableProviderFilterPushDown, TableType};
30use datafusion_physical_plan::ExecutionPlan;
31use datafusion_proto::logical_plan::from_proto::parse_exprs;
32use datafusion_proto::logical_plan::to_proto::serialize_exprs;
33use datafusion_proto::logical_plan::{
34 DefaultLogicalExtensionCodec, LogicalExtensionCodec,
35};
36use datafusion_proto::protobuf::LogicalExprList;
37use prost::Message;
38
39use stabby::vec::Vec as SVec;
40use tokio::runtime::Handle;
41
42use super::execution_plan::FFI_ExecutionPlan;
43use super::insert_op::FFI_InsertOp;
44use crate::arrow_wrappers::WrappedSchema;
45use crate::execution::FFI_TaskContextProvider;
46use crate::proto::logical_extension_codec::FFI_LogicalExtensionCodec;
47use crate::session::{FFI_SessionRef, ForeignSession};
48use crate::statistics::{deserialize_statistics, serialize_statistics};
49use crate::table_source::{FFI_TableProviderFilterPushDown, FFI_TableType};
50use crate::util::{FFI_Option, FFI_Result};
51use crate::{df_result, sresult_return};
52
53#[repr(C)]
93#[derive(Debug)]
94pub struct FFI_TableProvider {
95 schema: unsafe extern "C" fn(provider: &Self) -> WrappedSchema,
97
98 scan: unsafe extern "C" fn(
110 provider: &Self,
111 session: FFI_SessionRef,
112 projections: FFI_Option<SVec<usize>>,
113 filters_serialized: SVec<u8>,
114 limit: FFI_Option<usize>,
115 ) -> FfiFuture<FFI_Result<FFI_ExecutionPlan>>,
116
117 table_type: unsafe extern "C" fn(provider: &Self) -> FFI_TableType,
119
120 supports_filters_pushdown: Option<
124 unsafe extern "C" fn(
125 provider: &FFI_TableProvider,
126 filters_serialized: SVec<u8>,
127 )
128 -> FFI_Result<SVec<FFI_TableProviderFilterPushDown>>,
129 >,
130
131 insert_into: unsafe extern "C" fn(
132 provider: &Self,
133 session: FFI_SessionRef,
134 input: &FFI_ExecutionPlan,
135 insert_op: FFI_InsertOp,
136 ) -> FfiFuture<FFI_Result<FFI_ExecutionPlan>>,
137
138 pub statistics: unsafe extern "C" fn(provider: &Self) -> FFI_Option<SVec<u8>>,
142
143 pub logical_codec: FFI_LogicalExtensionCodec,
144
145 clone: unsafe extern "C" fn(plan: &Self) -> Self,
148
149 release: unsafe extern "C" fn(arg: &mut Self),
151
152 pub version: unsafe extern "C" fn() -> u64,
154
155 private_data: *mut c_void,
158
159 pub library_marker_id: extern "C" fn() -> usize,
163}
164
165unsafe impl Send for FFI_TableProvider {}
166unsafe impl Sync for FFI_TableProvider {}
167
168struct ProviderPrivateData {
169 provider: Arc<dyn TableProvider>,
170 runtime: Option<Handle>,
171}
172
173impl FFI_TableProvider {
174 fn inner(&self) -> &Arc<dyn TableProvider> {
175 let private_data = self.private_data as *const ProviderPrivateData;
176 unsafe { &(*private_data).provider }
177 }
178
179 fn runtime(&self) -> &Option<Handle> {
180 let private_data = self.private_data as *const ProviderPrivateData;
181 unsafe { &(*private_data).runtime }
182 }
183}
184
185unsafe extern "C" fn schema_fn_wrapper(provider: &FFI_TableProvider) -> WrappedSchema {
186 provider.inner().schema().into()
187}
188
189unsafe extern "C" fn statistics_fn_wrapper(
190 provider: &FFI_TableProvider,
191) -> FFI_Option<SVec<u8>> {
192 let serialized: Option<SVec<u8>> = provider
193 .inner()
194 .statistics()
195 .map(|s| SVec::from(&*serialize_statistics(&s)));
196 serialized.into()
197}
198
199unsafe extern "C" fn table_type_fn_wrapper(
200 provider: &FFI_TableProvider,
201) -> FFI_TableType {
202 provider.inner().table_type().into()
203}
204
205fn supports_filters_pushdown_internal(
206 provider: &Arc<dyn TableProvider>,
207 filters_serialized: &[u8],
208 task_ctx: &Arc<TaskContext>,
209 codec: &dyn LogicalExtensionCodec,
210) -> Result<SVec<FFI_TableProviderFilterPushDown>> {
211 let filters = match filters_serialized.is_empty() {
212 true => vec![],
213 false => {
214 let proto_filters = LogicalExprList::decode(filters_serialized)
215 .map_err(|e| DataFusionError::Plan(e.to_string()))?;
216
217 parse_exprs(proto_filters.expr.iter(), task_ctx.as_ref(), codec)?
218 }
219 };
220 let filters_borrowed: Vec<&Expr> = filters.iter().collect();
221
222 let results: SVec<_> = provider
223 .supports_filters_pushdown(&filters_borrowed)?
224 .iter()
225 .map(|v| v.into())
226 .collect();
227
228 Ok(results)
229}
230
231unsafe extern "C" fn supports_filters_pushdown_fn_wrapper(
232 provider: &FFI_TableProvider,
233 filters_serialized: SVec<u8>,
234) -> FFI_Result<SVec<FFI_TableProviderFilterPushDown>> {
235 let logical_codec: Arc<dyn LogicalExtensionCodec> = (&provider.logical_codec).into();
236 let task_ctx = sresult_return!(<Arc<TaskContext>>::try_from(
237 &provider.logical_codec.task_ctx_provider
238 ));
239 supports_filters_pushdown_internal(
240 provider.inner(),
241 &filters_serialized,
242 &task_ctx,
243 logical_codec.as_ref(),
244 )
245 .into()
246}
247
248unsafe extern "C" fn scan_fn_wrapper(
249 provider: &FFI_TableProvider,
250 session: FFI_SessionRef,
251 projections: FFI_Option<SVec<usize>>,
252 filters_serialized: SVec<u8>,
253 limit: FFI_Option<usize>,
254) -> FfiFuture<FFI_Result<FFI_ExecutionPlan>> {
255 let task_ctx: Result<Arc<TaskContext>, DataFusionError> =
256 (&provider.logical_codec.task_ctx_provider).try_into();
257 let runtime = provider.runtime().clone();
258 let logical_codec: Arc<dyn LogicalExtensionCodec> = (&provider.logical_codec).into();
259 let internal_provider = Arc::clone(provider.inner());
260
261 async move {
262 let mut foreign_session = None;
263 let session = sresult_return!(
264 session
265 .as_local()
266 .map(Ok::<&dyn Session, DataFusionError>)
267 .unwrap_or_else(|| {
268 foreign_session = Some(ForeignSession::try_from(&session)?);
269 Ok(foreign_session.as_ref().unwrap())
270 })
271 );
272
273 let task_ctx = sresult_return!(task_ctx);
274 let filters = match filters_serialized.is_empty() {
275 true => vec![],
276 false => {
277 let proto_filters =
278 sresult_return!(LogicalExprList::decode(filters_serialized.as_ref()));
279
280 sresult_return!(parse_exprs(
281 proto_filters.expr.iter(),
282 task_ctx.as_ref(),
283 logical_codec.as_ref(),
284 ))
285 }
286 };
287
288 let projections: Option<Vec<usize>> =
289 projections.into_option().map(|p| p.into_iter().collect());
290
291 let plan = sresult_return!(
292 internal_provider
293 .scan(session, projections.as_ref(), &filters, limit.into())
294 .await
295 );
296
297 FFI_Result::Ok(FFI_ExecutionPlan::new(plan, runtime.clone()))
298 }
299 .into_ffi()
300}
301
302unsafe extern "C" fn insert_into_fn_wrapper(
303 provider: &FFI_TableProvider,
304 session: FFI_SessionRef,
305 input: &FFI_ExecutionPlan,
306 insert_op: FFI_InsertOp,
307) -> FfiFuture<FFI_Result<FFI_ExecutionPlan>> {
308 let runtime = provider.runtime().clone();
309 let internal_provider = Arc::clone(provider.inner());
310 let input = input.clone();
311
312 async move {
313 let mut foreign_session = None;
314 let session = sresult_return!(
315 session
316 .as_local()
317 .map(Ok::<&dyn Session, DataFusionError>)
318 .unwrap_or_else(|| {
319 foreign_session = Some(ForeignSession::try_from(&session)?);
320 Ok(foreign_session.as_ref().unwrap())
321 })
322 );
323
324 let input = sresult_return!(<Arc<dyn ExecutionPlan>>::try_from(&input));
325
326 let insert_op = InsertOp::from(insert_op);
327
328 let plan = sresult_return!(
329 internal_provider
330 .insert_into(session, input, insert_op)
331 .await
332 );
333
334 FFI_Result::Ok(FFI_ExecutionPlan::new(plan, runtime.clone()))
335 }
336 .into_ffi()
337}
338
339unsafe extern "C" fn release_fn_wrapper(provider: &mut FFI_TableProvider) {
340 unsafe {
341 debug_assert!(!provider.private_data.is_null());
342 let private_data =
343 Box::from_raw(provider.private_data as *mut ProviderPrivateData);
344 drop(private_data);
345 provider.private_data = std::ptr::null_mut();
346 }
347}
348
349unsafe extern "C" fn clone_fn_wrapper(provider: &FFI_TableProvider) -> FFI_TableProvider {
350 let runtime = provider.runtime().clone();
351 let old_provider = Arc::clone(provider.inner());
352
353 let private_data = Box::into_raw(Box::new(ProviderPrivateData {
354 provider: old_provider,
355 runtime,
356 })) as *mut c_void;
357
358 FFI_TableProvider {
359 schema: schema_fn_wrapper,
360 scan: scan_fn_wrapper,
361 table_type: table_type_fn_wrapper,
362 supports_filters_pushdown: provider.supports_filters_pushdown,
363 insert_into: provider.insert_into,
364 statistics: statistics_fn_wrapper,
365 logical_codec: provider.logical_codec.clone(),
366 clone: clone_fn_wrapper,
367 release: release_fn_wrapper,
368 version: super::version,
369 private_data,
370 library_marker_id: crate::get_library_marker_id,
371 }
372}
373
374impl Drop for FFI_TableProvider {
375 fn drop(&mut self) {
376 unsafe { (self.release)(self) }
377 }
378}
379
380impl FFI_TableProvider {
381 pub fn new(
383 provider: Arc<dyn TableProvider>,
384 can_support_pushdown_filters: bool,
385 runtime: Option<Handle>,
386 task_ctx_provider: impl Into<FFI_TaskContextProvider>,
387 logical_codec: Option<Arc<dyn LogicalExtensionCodec>>,
388 ) -> Self {
389 let task_ctx_provider = task_ctx_provider.into();
390 let logical_codec =
391 logical_codec.unwrap_or_else(|| Arc::new(DefaultLogicalExtensionCodec {}));
392 let logical_codec = FFI_LogicalExtensionCodec::new(
393 logical_codec,
394 runtime.clone(),
395 task_ctx_provider.clone(),
396 );
397 Self::new_with_ffi_codec(
398 provider,
399 can_support_pushdown_filters,
400 runtime,
401 logical_codec,
402 )
403 }
404
405 pub fn new_with_ffi_codec(
416 provider: Arc<dyn TableProvider>,
417 can_support_pushdown_filters: bool,
418 runtime: Option<Handle>,
419 logical_codec: FFI_LogicalExtensionCodec,
420 ) -> Self {
421 if let Some(provider) = provider.downcast_ref::<ForeignTableProvider>() {
422 let mut provider = provider.0.clone();
423 provider.logical_codec = logical_codec;
424 return provider;
425 }
426 let private_data = Box::new(ProviderPrivateData { provider, runtime });
427
428 Self {
429 schema: schema_fn_wrapper,
430 scan: scan_fn_wrapper,
431 table_type: table_type_fn_wrapper,
432 supports_filters_pushdown: match can_support_pushdown_filters {
433 true => Some(supports_filters_pushdown_fn_wrapper),
434 false => None,
435 },
436 insert_into: insert_into_fn_wrapper,
437 statistics: statistics_fn_wrapper,
438 logical_codec,
439 clone: clone_fn_wrapper,
440 release: release_fn_wrapper,
441 version: super::version,
442 private_data: Box::into_raw(private_data) as *mut c_void,
443 library_marker_id: crate::get_library_marker_id,
444 }
445 }
446}
447
448#[derive(Debug)]
453pub struct ForeignTableProvider(pub FFI_TableProvider);
454
455unsafe impl Send for ForeignTableProvider {}
456unsafe impl Sync for ForeignTableProvider {}
457
458impl From<&FFI_TableProvider> for Arc<dyn TableProvider> {
459 fn from(provider: &FFI_TableProvider) -> Self {
460 if (provider.library_marker_id)() == crate::get_library_marker_id() {
461 Arc::clone(provider.inner()) as Arc<dyn TableProvider>
462 } else {
463 Arc::new(ForeignTableProvider(provider.clone()))
464 }
465 }
466}
467
468impl Clone for FFI_TableProvider {
469 fn clone(&self) -> Self {
470 unsafe { (self.clone)(self) }
471 }
472}
473
474#[async_trait]
475impl TableProvider for ForeignTableProvider {
476 fn schema(&self) -> SchemaRef {
477 let wrapped_schema = unsafe { (self.0.schema)(&self.0) };
478 wrapped_schema.into()
479 }
480
481 fn table_type(&self) -> TableType {
482 unsafe { (self.0.table_type)(&self.0).into() }
483 }
484
485 fn statistics(&self) -> Option<Statistics> {
486 let ffi_opt = unsafe { (self.0.statistics)(&self.0) };
487 let bytes: Option<SVec<u8>> = ffi_opt.into();
488 let bytes = bytes?;
489 match deserialize_statistics(bytes.as_slice()) {
490 Ok(stats) => Some(stats),
491 Err(e) => {
492 log::warn!("Failed to deserialize FFI statistics: {e}");
493 debug_assert!(false, "Failed to deserialize FFI statistics: {e}");
495 None
496 }
497 }
498 }
499
500 async fn scan(
501 &self,
502 session: &dyn Session,
503 projection: Option<&Vec<usize>>,
504 filters: &[Expr],
505 limit: Option<usize>,
506 ) -> Result<Arc<dyn ExecutionPlan>> {
507 let session = FFI_SessionRef::new(session, None, self.0.logical_codec.clone());
508
509 let projections: FFI_Option<SVec<usize>> = projection
510 .map(|p| p.iter().map(|v| v.to_owned()).collect())
511 .into();
512
513 let codec: Arc<dyn LogicalExtensionCodec> = (&self.0.logical_codec).into();
514 let filter_list = LogicalExprList {
515 expr: serialize_exprs(filters, codec.as_ref())?,
516 };
517 let filters_serialized = filter_list.encode_to_vec().into_iter().collect();
518
519 let plan = unsafe {
520 let maybe_plan = (self.0.scan)(
521 &self.0,
522 session,
523 projections,
524 filters_serialized,
525 limit.into(),
526 )
527 .await;
528
529 <Arc<dyn ExecutionPlan>>::try_from(&df_result!(maybe_plan)?)?
530 };
531
532 Ok(plan)
533 }
534
535 fn supports_filters_pushdown(
538 &self,
539 filters: &[&Expr],
540 ) -> Result<Vec<TableProviderFilterPushDown>> {
541 unsafe {
542 let pushdown_fn = match self.0.supports_filters_pushdown {
543 Some(func) => func,
544 None => {
545 return Ok(vec![
546 TableProviderFilterPushDown::Unsupported;
547 filters.len()
548 ]);
549 }
550 };
551
552 let codec: Arc<dyn LogicalExtensionCodec> = (&self.0.logical_codec).into();
553
554 let expr_list = LogicalExprList {
555 expr: serialize_exprs(
556 filters.iter().map(|f| f.to_owned()),
557 codec.as_ref(),
558 )?,
559 };
560 let serialized_filters = expr_list.encode_to_vec();
561
562 let pushdowns = df_result!(pushdown_fn(
563 &self.0,
564 serialized_filters.into_iter().collect()
565 ))?;
566
567 Ok(pushdowns.iter().map(|v| v.into()).collect())
568 }
569 }
570
571 async fn insert_into(
572 &self,
573 session: &dyn Session,
574 input: Arc<dyn ExecutionPlan>,
575 insert_op: InsertOp,
576 ) -> Result<Arc<dyn ExecutionPlan>> {
577 let session = FFI_SessionRef::new(session, None, self.0.logical_codec.clone());
578
579 let rc = Handle::try_current().ok();
580 let input = FFI_ExecutionPlan::new(input, rc);
581 let insert_op: FFI_InsertOp = insert_op.into();
582
583 let plan = unsafe {
584 let maybe_plan =
585 (self.0.insert_into)(&self.0, session, &input, insert_op).await;
586
587 <Arc<dyn ExecutionPlan>>::try_from(&df_result!(maybe_plan)?)?
588 };
589
590 Ok(plan)
591 }
592}
593
594#[cfg(test)]
595mod tests {
596 use arrow::datatypes::Schema;
597 use datafusion::prelude::{SessionContext, col, lit};
598 use datafusion_execution::TaskContextProvider;
599
600 use super::*;
601
602 fn create_test_table_provider() -> Result<Arc<dyn TableProvider>> {
603 use arrow::datatypes::Field;
604 use datafusion::arrow::array::Float32Array;
605 use datafusion::arrow::datatypes::DataType;
606 use datafusion::arrow::record_batch::RecordBatch;
607 use datafusion::datasource::MemTable;
608
609 let schema =
610 Arc::new(Schema::new(vec![Field::new("a", DataType::Float32, false)]));
611
612 let batch1 = RecordBatch::try_new(
614 Arc::clone(&schema),
615 vec![Arc::new(Float32Array::from(vec![2.0, 4.0, 8.0]))],
616 )?;
617 let batch2 = RecordBatch::try_new(
618 Arc::clone(&schema),
619 vec![Arc::new(Float32Array::from(vec![64.0]))],
620 )?;
621
622 Ok(Arc::new(MemTable::try_new(
623 schema,
624 vec![vec![batch1], vec![batch2]],
625 )?))
626 }
627
628 #[tokio::test]
629 async fn test_round_trip_ffi_table_provider_scan() -> Result<()> {
630 let provider = create_test_table_provider()?;
631 let ctx = Arc::new(SessionContext::new());
632 let task_ctx_provider = Arc::clone(&ctx) as Arc<dyn TaskContextProvider>;
633 let task_ctx_provider = FFI_TaskContextProvider::from(&task_ctx_provider);
634
635 let mut ffi_provider =
636 FFI_TableProvider::new(provider, true, None, task_ctx_provider, None);
637 ffi_provider.library_marker_id = crate::mock_foreign_marker_id;
638
639 let foreign_table_provider: Arc<dyn TableProvider> = (&ffi_provider).into();
640
641 ctx.register_table("t", foreign_table_provider)?;
642
643 let df = ctx.table("t").await?;
644
645 df.select(vec![col("a")])?
646 .filter(col("a").gt(lit(3.0)))?
647 .show()
648 .await?;
649
650 Ok(())
651 }
652
653 #[tokio::test]
654 async fn test_round_trip_ffi_table_provider_insert_into() -> Result<()> {
655 let provider = create_test_table_provider()?;
656 let ctx = Arc::new(SessionContext::new());
657 let task_ctx_provider = Arc::clone(&ctx) as Arc<dyn TaskContextProvider>;
658 let task_ctx_provider = FFI_TaskContextProvider::from(&task_ctx_provider);
659
660 let mut ffi_provider =
661 FFI_TableProvider::new(provider, true, None, task_ctx_provider, None);
662 ffi_provider.library_marker_id = crate::mock_foreign_marker_id;
663
664 let foreign_table_provider: Arc<dyn TableProvider> = (&ffi_provider).into();
665
666 ctx.register_table("t", foreign_table_provider)?;
667
668 let result = ctx
669 .sql("INSERT INTO t VALUES (128.0);")
670 .await?
671 .collect()
672 .await?;
673
674 assert!(result.len() == 1 && result[0].num_rows() == 1);
675
676 ctx.table("t")
677 .await?
678 .select(vec![col("a")])?
679 .filter(col("a").gt(lit(3.0)))?
680 .show()
681 .await?;
682
683 Ok(())
684 }
685
686 #[tokio::test]
687 async fn test_aggregation() -> Result<()> {
688 use arrow::datatypes::Field;
689 use datafusion::arrow::array::Float32Array;
690 use datafusion::arrow::datatypes::DataType;
691 use datafusion::arrow::record_batch::RecordBatch;
692 use datafusion::common::assert_batches_eq;
693 use datafusion::datasource::MemTable;
694
695 let schema =
696 Arc::new(Schema::new(vec![Field::new("a", DataType::Float32, false)]));
697
698 let batch1 = RecordBatch::try_new(
700 Arc::clone(&schema),
701 vec![Arc::new(Float32Array::from(vec![2.0, 4.0, 8.0]))],
702 )?;
703
704 let ctx = Arc::new(SessionContext::new());
705 let task_ctx_provider = Arc::clone(&ctx) as Arc<dyn TaskContextProvider>;
706 let task_ctx_provider = FFI_TaskContextProvider::from(&task_ctx_provider);
707
708 let provider = Arc::new(MemTable::try_new(schema, vec![vec![batch1]])?);
709
710 let mut ffi_provider =
711 FFI_TableProvider::new(provider, true, None, task_ctx_provider, None);
712 ffi_provider.library_marker_id = crate::mock_foreign_marker_id;
713
714 let foreign_table_provider: Arc<dyn TableProvider> = (&ffi_provider).into();
715
716 ctx.register_table("t", foreign_table_provider)?;
717
718 let result = ctx
719 .sql("SELECT COUNT(*) as cnt FROM t")
720 .await?
721 .collect()
722 .await?;
723 #[rustfmt::skip]
724 let expected = [
725 "+-----+",
726 "| cnt |",
727 "+-----+",
728 "| 3 |",
729 "+-----+"
730 ];
731 assert_batches_eq!(expected, &result);
732 Ok(())
733 }
734
735 #[test]
736 fn test_ffi_table_provider_local_bypass() -> Result<()> {
737 let table_provider = create_test_table_provider()?;
738
739 let ctx = Arc::new(SessionContext::new()) as Arc<dyn TaskContextProvider>;
740 let task_ctx_provider = FFI_TaskContextProvider::from(&ctx);
741 let mut ffi_table =
742 FFI_TableProvider::new(table_provider, false, None, task_ctx_provider, None);
743
744 let foreign_table: Arc<dyn TableProvider> = (&ffi_table).into();
746 assert!(
747 foreign_table
748 .downcast_ref::<datafusion::datasource::MemTable>()
749 .is_some()
750 );
751
752 ffi_table.library_marker_id = crate::mock_foreign_marker_id;
754 let foreign_table: Arc<dyn TableProvider> = (&ffi_table).into();
755 assert!(
756 foreign_table
757 .downcast_ref::<ForeignTableProvider>()
758 .is_some()
759 );
760
761 Ok(())
762 }
763
764 #[tokio::test]
765 async fn test_scan_with_none_projection_returns_all_columns() -> Result<()> {
766 use arrow::datatypes::Field;
767 use datafusion::arrow::array::Float32Array;
768 use datafusion::arrow::datatypes::DataType;
769 use datafusion::arrow::record_batch::RecordBatch;
770 use datafusion::datasource::MemTable;
771 use datafusion::physical_plan::collect;
772
773 let schema = Arc::new(Schema::new(vec![
774 Field::new("a", DataType::Float32, false),
775 Field::new("b", DataType::Float32, false),
776 Field::new("c", DataType::Float32, false),
777 ]));
778
779 let batch = RecordBatch::try_new(
780 Arc::clone(&schema),
781 vec![
782 Arc::new(Float32Array::from(vec![1.0, 2.0])),
783 Arc::new(Float32Array::from(vec![3.0, 4.0])),
784 Arc::new(Float32Array::from(vec![5.0, 6.0])),
785 ],
786 )?;
787
788 let provider =
789 Arc::new(MemTable::try_new(Arc::clone(&schema), vec![vec![batch]])?);
790
791 let ctx = Arc::new(SessionContext::new());
792 let task_ctx_provider = Arc::clone(&ctx) as Arc<dyn TaskContextProvider>;
793 let task_ctx_provider = FFI_TaskContextProvider::from(&task_ctx_provider);
794
795 let mut ffi_provider =
797 FFI_TableProvider::new(provider, true, None, task_ctx_provider, None);
798 ffi_provider.library_marker_id = crate::mock_foreign_marker_id;
799
800 let foreign_table_provider: Arc<dyn TableProvider> = (&ffi_provider).into();
801
802 let plan = foreign_table_provider
804 .scan(&ctx.state(), None, &[], None)
805 .await?;
806 assert_eq!(
807 plan.schema().fields().len(),
808 3,
809 "scan(projection=None) should return all columns; got {}",
810 plan.schema().fields().len()
811 );
812
813 let batches = collect(plan, ctx.task_ctx()).await?;
815 assert_eq!(batches.len(), 1);
816 assert_eq!(batches[0].num_columns(), 3);
817 assert_eq!(batches[0].num_rows(), 2);
818
819 Ok(())
820 }
821
822 #[test]
823 fn test_ffi_table_provider_statistics_round_trip() -> Result<()> {
824 use arrow::datatypes::{DataType, Field};
825 use datafusion::arrow::array::Int32Array;
826 use datafusion::arrow::record_batch::RecordBatch;
827 use datafusion::datasource::MemTable;
828 use datafusion_common::stats::Precision;
829 use datafusion_common::{ColumnStatistics, ScalarValue};
830
831 #[derive(Debug)]
833 struct TableWithStats {
834 inner: Arc<dyn TableProvider>,
835 stats: Option<Statistics>,
836 }
837
838 #[async_trait]
839 impl TableProvider for TableWithStats {
840 fn schema(&self) -> SchemaRef {
841 self.inner.schema()
842 }
843 fn table_type(&self) -> TableType {
844 self.inner.table_type()
845 }
846 fn statistics(&self) -> Option<Statistics> {
847 self.stats.clone()
848 }
849 async fn scan(
850 &self,
851 session: &dyn Session,
852 projection: Option<&Vec<usize>>,
853 filters: &[Expr],
854 limit: Option<usize>,
855 ) -> Result<Arc<dyn ExecutionPlan>> {
856 self.inner.scan(session, projection, filters, limit).await
857 }
858 }
859
860 let schema = Arc::new(Schema::new(vec![Field::new("a", DataType::Int32, true)]));
861
862 let batch = RecordBatch::try_new(
863 Arc::clone(&schema),
864 vec![Arc::new(Int32Array::from(vec![1, 2, 3]))],
865 )?;
866
867 let ctx = Arc::new(SessionContext::new());
868 let task_ctx_provider = Arc::clone(&ctx) as Arc<dyn TaskContextProvider>;
869 let task_ctx_provider = FFI_TaskContextProvider::from(&task_ctx_provider);
870
871 let no_stats_inner = Arc::new(MemTable::try_new(
873 Arc::clone(&schema),
874 vec![vec![batch.clone()]],
875 )?);
876 let no_stats_provider = Arc::new(TableWithStats {
877 inner: no_stats_inner,
878 stats: None,
879 });
880 let mut ffi_provider = FFI_TableProvider::new(
881 no_stats_provider,
882 true,
883 None,
884 task_ctx_provider.clone(),
885 None,
886 );
887 ffi_provider.library_marker_id = crate::mock_foreign_marker_id;
888 let foreign: Arc<dyn TableProvider> = (&ffi_provider).into();
889 assert!(foreign.statistics().is_none());
890
891 let original_stats = Statistics {
893 num_rows: Precision::Exact(3),
894 total_byte_size: Precision::Inexact(12),
895 column_statistics: vec![ColumnStatistics {
896 null_count: Precision::Exact(0),
897 max_value: Precision::Exact(ScalarValue::Int32(Some(3))),
898 min_value: Precision::Exact(ScalarValue::Int32(Some(1))),
899 sum_value: Precision::Exact(ScalarValue::Int64(Some(6))),
900 distinct_count: Precision::Exact(3),
901 byte_size: Precision::Exact(12),
902 }],
903 };
904 let stats_inner =
905 Arc::new(MemTable::try_new(Arc::clone(&schema), vec![vec![batch]])?);
906 let stats_provider = Arc::new(TableWithStats {
907 inner: stats_inner,
908 stats: Some(original_stats.clone()),
909 });
910 let mut ffi_provider =
911 FFI_TableProvider::new(stats_provider, true, None, task_ctx_provider, None);
912 ffi_provider.library_marker_id = crate::mock_foreign_marker_id;
913 let foreign: Arc<dyn TableProvider> = (&ffi_provider).into();
914 assert_eq!(foreign.statistics().as_ref(), Some(&original_stats));
915
916 Ok(())
917 }
918
919 #[test]
922 fn test_rebind_foreign_table_provider_adopts_logical_codec() -> Result<()> {
923 let (_ctx_a, provider_a) = crate::util::tests::test_session_and_ctx();
924 let (ctx_b, provider_b) = crate::util::tests::test_session_and_ctx();
925
926 let mut ffi_provider = FFI_TableProvider::new(
927 create_test_table_provider()?,
928 true,
929 None,
930 provider_a,
931 None,
932 );
933 ffi_provider.library_marker_id = crate::mock_foreign_marker_id;
934
935 let imported: Arc<dyn TableProvider> = (&ffi_provider).into();
936 assert!(imported.downcast_ref::<ForeignTableProvider>().is_some());
937
938 let codec_b = FFI_LogicalExtensionCodec::new(
940 Arc::new(DefaultLogicalExtensionCodec {}),
941 None,
942 provider_b,
943 );
944 let rebound =
945 FFI_TableProvider::new_with_ffi_codec(imported, true, None, codec_b);
946
947 let task_ctx: Arc<TaskContext> = (&rebound.logical_codec.task_ctx_provider)
948 .try_into()
949 .expect("rebound provider's codec resolves");
950 assert_eq!(task_ctx.session_id(), ctx_b.task_ctx().session_id());
951
952 Ok(())
953 }
954}