Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
0c32832
add_stabby
coderfender Mar 18, 2026
65aa893
fix_api
coderfender Mar 19, 2026
9834a59
implement_custom_stabby_friendly_ffi
coderfender Mar 20, 2026
d899199
fix_clippy_naming_issues
coderfender Mar 20, 2026
2a84141
cargo_fmt
coderfender Mar 20, 2026
0319ade
Merge branch 'main' into feat_migrate_ffi_to_stabby
coderfender Mar 20, 2026
9f7d00a
cargo_fmt_rebase_main
coderfender Mar 20, 2026
349b624
cargo_fmt_rebase_main
coderfender Mar 20, 2026
d0ab1fb
cargo_fmt_rebase_main
coderfender Mar 20, 2026
d6194b8
cargo_fmt_fix_test_accessor_method
coderfender Mar 20, 2026
21246e2
Merge branch 'main' into feat_migrate_ffi_to_stabby
coderfender Mar 20, 2026
48926f9
migrate_abi_to_stabby_fix_review_comments
coderfender Mar 25, 2026
afa0502
Merge branch 'main' into feat_migrate_ffi_to_stabby
coderfender Mar 25, 2026
ca9dbdf
Merge branch 'main' into feat_migrate_ffi_to_stabby
coderfender Mar 26, 2026
d13fd4f
ffi_physical_optimizer_stabby_upgrade
coderfender Mar 27, 2026
08b4117
ffi_physical_optimizer_stabby_upgrade
coderfender Mar 27, 2026
ef10249
Merge branch 'main' into feat_migrate_ffi_to_stabby
coderfender Mar 27, 2026
6cc66cf
Merge branch 'main' into feat_migrate_ffi_to_stabby
coderfender Mar 27, 2026
af7939f
Merge branch 'main' into feat_migrate_ffi_to_stabby
coderfender Mar 28, 2026
510526e
Merge branch 'main' into feat_migrate_ffi_to_stabby
coderfender Mar 28, 2026
155ccea
Merge branch 'main' into feat_migrate_ffi_to_stabby
coderfender Mar 29, 2026
a022a91
Merge branch 'main' into feat_migrate_ffi_to_stabby
coderfender Mar 30, 2026
ddb7193
Merge branch 'main' into feat_migrate_ffi_to_stabby
coderfender Apr 22, 2026
3b22836
rebase_main
coderfender Apr 22, 2026
c3e66f7
Merge branch 'main' into feat_migrate_ffi_to_stabby
coderfender Apr 22, 2026
de389be
rebase_main
coderfender Apr 22, 2026
71f1fde
rebase_main
coderfender Apr 22, 2026
494dc35
Merge branch 'main' into feat_migrate_ffi_to_stabby
coderfender Apr 22, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
598 changes: 246 additions & 352 deletions Cargo.lock

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ edition = { workspace = true }
publish = false

[dependencies]
abi_stable = "0.11.3"
arrow = { workspace = true }
datafusion = { workspace = true }
datafusion-ffi = { workspace = true }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,12 @@

use std::sync::Arc;

use abi_stable::{export_root_module, prefix_type::PrefixTypeTrait};
use arrow::array::RecordBatch;
use arrow::datatypes::{DataType, Field, Schema};
use datafusion::{common::record_batch, datasource::MemTable};
use datafusion_ffi::proto::logical_extension_codec::FFI_LogicalExtensionCodec;
use datafusion_ffi::table_provider::FFI_TableProvider;
use ffi_module_interface::{TableProviderModule, TableProviderModuleRef};
use ffi_module_interface::TableProviderModule;

fn create_record_batch(start_value: i32, num_values: usize) -> RecordBatch {
let end_value = start_value + num_values as i32;
Expand Down Expand Up @@ -56,11 +55,10 @@ extern "C" fn construct_simple_table_provider(
FFI_TableProvider::new_with_ffi_codec(Arc::new(table_provider), true, None, codec)
}

#[export_root_module]
#[unsafe(no_mangle)]
Comment thread
coderfender marked this conversation as resolved.
/// This defines the entry point for using the module.
pub fn get_simple_memory_table() -> TableProviderModuleRef {
pub extern "C" fn ffi_example_get_module() -> TableProviderModule {
TableProviderModule {
create_table: construct_simple_table_provider,
}
.leak_into_prefix()
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,4 @@ publish = false
workspace = true

[dependencies]
abi_stable = "0.11.3"
datafusion-ffi = { workspace = true }
21 changes: 1 addition & 20 deletions datafusion-examples/examples/ffi/ffi_module_interface/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,36 +15,17 @@
// specific language governing permissions and limitations
// under the License.

use abi_stable::{
StableAbi, declare_root_module_statics,
library::{LibraryError, RootModule},
package_version_strings,
sabi_types::VersionStrings,
};
use datafusion_ffi::proto::logical_extension_codec::FFI_LogicalExtensionCodec;
use datafusion_ffi::table_provider::FFI_TableProvider;

#[repr(C)]
#[derive(StableAbi)]
#[sabi(kind(Prefix(prefix_ref = TableProviderModuleRef)))]
/// This struct defines the module interfaces. It is to be shared by
/// both the module loading program and library that implements the
/// module. It is possible to move this definition into the loading
/// program and reference it in the modules, but this example shows
/// how a user may wish to separate these concerns.
#[repr(C)]
Comment thread
coderfender marked this conversation as resolved.
pub struct TableProviderModule {
/// Constructs the table provider
pub create_table:
extern "C" fn(codec: FFI_LogicalExtensionCodec) -> FFI_TableProvider,
}

impl RootModule for TableProviderModuleRef {
declare_root_module_statics! {TableProviderModuleRef}
const BASE_NAME: &'static str = "ffi_example_table_provider";
const NAME: &'static str = "ffi_example_table_provider";
const VERSION_STRINGS: VersionStrings = package_version_strings!();

fn initialization(self) -> Result<Self, LibraryError> {
Ok(self)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ publish = false
workspace = true

[dependencies]
abi_stable = "0.11.3"
datafusion = { workspace = true }
datafusion-ffi = { workspace = true }
ffi_module_interface = { path = "../ffi_module_interface" }
libloading = "0.8"
tokio = { workspace = true, features = ["rt-multi-thread", "parking_lot"] }
56 changes: 38 additions & 18 deletions datafusion-examples/examples/ffi/ffi_module_loader/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,28 +18,53 @@
use std::sync::Arc;

use datafusion::{
datasource::TableProvider,
error::{DataFusionError, Result},
execution::TaskContextProvider,
prelude::SessionContext,
};

use abi_stable::library::{RootModule, development_utils::compute_library_path};
use datafusion::datasource::TableProvider;
use datafusion::execution::TaskContextProvider;
use datafusion_ffi::proto::logical_extension_codec::FFI_LogicalExtensionCodec;
use ffi_module_interface::TableProviderModuleRef;
use ffi_module_interface::TableProviderModule;

#[tokio::main]
async fn main() -> Result<()> {
// Find the location of the library. This is specific to the build environment,
// so you will need to change the approach here based on your use case.
let target: &std::path::Path = "../../../../target/".as_ref();
let library_path = compute_library_path::<TableProviderModuleRef>(target)
.map_err(|e| DataFusionError::External(Box::new(e)))?;
let lib_prefix = if cfg!(target_os = "windows") {
""
} else {
"lib"
};
let lib_ext = if cfg!(target_os = "macos") {
"dylib"
} else if cfg!(target_os = "windows") {
"dll"
} else {
"so"
};

let build_type = if cfg!(debug_assertions) {
"debug"
} else {
"release"
};

let library_path = format!(
"../../../../target/{build_type}/{lib_prefix}ffi_example_table_provider.{lib_ext}"
);
Comment thread
coderfender marked this conversation as resolved.

// Load the library using libloading
let lib = unsafe {
libloading::Library::new(&library_path)
.map_err(|e| DataFusionError::External(Box::new(e)))?
};

let get_module: libloading::Symbol<extern "C" fn() -> TableProviderModule> = unsafe {
lib.get(b"ffi_example_get_module")
.map_err(|e| DataFusionError::External(Box::new(e)))?
};

// Load the module
let table_provider_module =
TableProviderModuleRef::load_from_directory(&library_path)
.map_err(|e| DataFusionError::External(Box::new(e)))?;
let table_provider_module = get_module();

let ctx = Arc::new(SessionContext::new());
let codec = FFI_LogicalExtensionCodec::new_default(
Expand All @@ -48,12 +73,7 @@ async fn main() -> Result<()> {

// By calling the code below, the table provided will be created within
// the module's code.
let ffi_table_provider =
table_provider_module
.create_table()
.ok_or(DataFusionError::NotImplemented(
"External table provider failed to implement create_table".to_string(),
))?(codec);
let ffi_table_provider = (table_provider_module.create_table)(codec);

// In order to access the table provider within this executable, we need to
// turn it into a `TableProvider`.
Expand Down
5 changes: 3 additions & 2 deletions datafusion/ffi/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,9 @@ crate-type = ["cdylib", "rlib"]
# It increases build times and library binary size for users.

[dependencies]
abi_stable = "0.11.3"
arrow = { workspace = true, features = ["ffi"] }
arrow-schema = { workspace = true }
async-ffi = { version = "0.5.0", features = ["abi_stable"] }
async-ffi = { version = "0.5.0" }
async-trait = { workspace = true }
datafusion-catalog = { workspace = true }
datafusion-common = { workspace = true }
Expand All @@ -67,8 +66,10 @@ datafusion-proto = { workspace = true }
datafusion-proto-common = { workspace = true }
datafusion-session = { workspace = true }
futures = { workspace = true }
libloading = "0.8"
log = { workspace = true }
prost = { workspace = true }
stabby = "72.1.1"
semver = "1.0.28"
tokio = { workspace = true }

Expand Down
28 changes: 27 additions & 1 deletion datafusion/ffi/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,14 +65,39 @@ to work across Rust libraries. In general, you can use Rust's [FFI] to
operate across different programming languages, but that is not the design
intent of this crate. Instead, we are using external crates that provide
stable interfaces that closely mirror the Rust native approach. To learn more
about this approach see the [abi_stable] and [async-ffi] crates.
about this approach see the [stabby] and [async-ffi] crates.

If you have a library in another language that you wish to interface to
DataFusion the recommendation is to create a Rust wrapper crate to interface
Copy link
Copy Markdown
Member

@timsaucer timsaucer Apr 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For users who are interested in working on this code, they will probably ask themselves the same thing I asked. Why is it we are not using the core features of stabby? You and I have both come to the same conclusion that especially with the work on getting the record batch stream converted over to using stabby it turns into a much different project, especially when it comes to build times with the heavy macro use.

I think we just need some documentation as mentioned in a prior comment.

with your library and then to connect it to DataFusion using this crate.
Alternatively, you could use [bindgen] to interface directly to the [FFI] provided
by this crate, but that is currently not supported.

## Stabby Usage

This crate uses [stabby] for ABI-stable types like `stabby::string::String` and
`stabby::vec::Vec`. We chose stabby because [abi_stable] is no longer actively
maintained.

We intentionally use `#[repr(C)]` for our struct definitions instead of stabby's
`#[stabby::stabby]` macro. The reason is that stabby's `IStable` trait (required
by the macro) demands that all inner types also implement `IStable`. This creates
challenges for our use case:

1. **Arrow types**: Arrow's FFI types like `FFI_ArrowSchema` do not implement
`IStable`, and adding such implementations would be laborious and error-prone.

2. **Self-referential function pointers**: Many of our FFI structs contain
function pointers that reference `&Self`, which complicates `IStable`
implementations.

3. **FFI_Option and FFIResult**: For similar reasons, we provide our own
`FFI_Option<T>` and `FFIResult<T>` types using `#[repr(C, u8)]` instead
of stabby's `Option` and `Result`, which require inner types to be `IStable`.

This hybrid approach gives us stabby's maintained, ABI-stable collection types
while retaining flexibility for our complex FFI struct layouts.

## FFI Boundary

We expect this crate to be used by both sides of the FFI Boundary. This should
Expand Down Expand Up @@ -197,6 +222,7 @@ and it is easy to implement on any struct that implements `Session`.
[api docs]: http://docs.rs/datafusion-ffi/latest
[rust abi]: https://doc.rust-lang.org/reference/abi.html
[ffi]: https://doc.rust-lang.org/nomicon/ffi.html
[stabby]: https://crates.io/crates/stabby
[abi_stable]: https://crates.io/crates/abi_stable
[async-ffi]: https://crates.io/crates/async-ffi
[bindgen]: https://crates.io/crates/bindgen
Expand Down
13 changes: 5 additions & 8 deletions datafusion/ffi/src/arrow_wrappers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@

use std::sync::Arc;

use abi_stable::StableAbi;
use arrow::array::{ArrayRef, make_array};
use arrow::datatypes::{Schema, SchemaRef};
use arrow::error::ArrowError;
Expand All @@ -26,10 +25,10 @@ use datafusion_common::{DataFusionError, ScalarValue};
use log::error;

/// This is a wrapper struct around FFI_ArrowSchema simply to indicate
/// to the StableAbi macros that the underlying struct is FFI safe.
/// that the underlying struct is FFI safe.
#[repr(C)]
#[derive(Debug, StableAbi)]
pub struct WrappedSchema(#[sabi(unsafe_opaque_field)] pub FFI_ArrowSchema);
#[derive(Debug)]
pub struct WrappedSchema(pub FFI_ArrowSchema);

impl From<SchemaRef> for WrappedSchema {
fn from(value: SchemaRef) -> Self {
Expand Down Expand Up @@ -66,15 +65,13 @@ impl From<WrappedSchema> for SchemaRef {
}
}

/// This is a wrapper struct for FFI_ArrowArray to indicate to StableAbi
/// This is a wrapper struct for FFI_ArrowArray to indicate
/// that the struct is FFI Safe. For convenience, we also include the
/// schema needed to create a record batch from the array.
#[repr(C)]
#[derive(Debug, StableAbi)]
#[derive(Debug)]
pub struct WrappedArray {
#[sabi(unsafe_opaque_field)]
pub array: FFI_ArrowArray,

pub schema: WrappedSchema,
}

Expand Down
Loading
Loading