use std::collections::HashMap;
use std::ffi::c_void;
use abi_stable::StableAbi;
use abi_stable::std_types::{RHashMap, RString};
use datafusion_common::error::{DataFusionError, Result};
use datafusion_execution::config::SessionConfig;
#[repr(C)]
#[derive(Debug, StableAbi)]
pub struct FFI_SessionConfig {
pub config_options: unsafe extern "C" fn(config: &Self) -> RHashMap<RString, RString>,
pub clone: unsafe extern "C" fn(plan: &Self) -> Self,
pub release: unsafe extern "C" fn(arg: &mut Self),
pub private_data: *mut c_void,
pub library_marker_id: extern "C" fn() -> usize,
}
unsafe impl Send for FFI_SessionConfig {}
unsafe impl Sync for FFI_SessionConfig {}
impl FFI_SessionConfig {
fn inner(&self) -> &SessionConfig {
let private_data = self.private_data as *mut SessionConfigPrivateData;
unsafe { &(*private_data).config }
}
}
unsafe extern "C" fn config_options_fn_wrapper(
config: &FFI_SessionConfig,
) -> RHashMap<RString, RString> {
let config_options = config.inner().options();
let mut options = RHashMap::default();
for config_entry in config_options.entries() {
if let Some(value) = config_entry.value {
options.insert(config_entry.key.into(), value.into());
}
}
options
}
unsafe extern "C" fn release_fn_wrapper(config: &mut FFI_SessionConfig) {
unsafe {
debug_assert!(!config.private_data.is_null());
let private_data =
Box::from_raw(config.private_data as *mut SessionConfigPrivateData);
drop(private_data);
config.private_data = std::ptr::null_mut();
}
}
unsafe extern "C" fn clone_fn_wrapper(config: &FFI_SessionConfig) -> FFI_SessionConfig {
unsafe {
let old_private_data = config.private_data as *mut SessionConfigPrivateData;
let old_config = (*old_private_data).config.clone();
let private_data = Box::new(SessionConfigPrivateData { config: old_config });
FFI_SessionConfig {
config_options: config_options_fn_wrapper,
private_data: Box::into_raw(private_data) as *mut c_void,
clone: clone_fn_wrapper,
release: release_fn_wrapper,
library_marker_id: crate::get_library_marker_id,
}
}
}
struct SessionConfigPrivateData {
pub config: SessionConfig,
}
impl From<&SessionConfig> for FFI_SessionConfig {
fn from(session: &SessionConfig) -> Self {
let private_data = Box::new(SessionConfigPrivateData {
config: session.clone(),
});
Self {
config_options: config_options_fn_wrapper,
private_data: Box::into_raw(private_data) as *mut c_void,
clone: clone_fn_wrapper,
release: release_fn_wrapper,
library_marker_id: crate::get_library_marker_id,
}
}
}
impl Clone for FFI_SessionConfig {
fn clone(&self) -> Self {
unsafe { (self.clone)(self) }
}
}
impl Drop for FFI_SessionConfig {
fn drop(&mut self) {
unsafe { (self.release)(self) };
}
}
impl TryFrom<&FFI_SessionConfig> for SessionConfig {
type Error = DataFusionError;
fn try_from(config: &FFI_SessionConfig) -> Result<Self, Self::Error> {
if (config.library_marker_id)() == crate::get_library_marker_id() {
return Ok(config.inner().clone());
}
let config_options = unsafe { (config.config_options)(config) };
let mut options_map = HashMap::new();
config_options.iter().for_each(|kv_pair| {
options_map.insert(kv_pair.0.to_string(), kv_pair.1.to_string());
});
SessionConfig::from_string_hash_map(&options_map)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_round_trip_ffi_session_config() -> Result<()> {
let session_config = SessionConfig::new();
let original_options = session_config.options().entries();
let mut ffi_config: FFI_SessionConfig = (&session_config).into();
let _ = ffi_config.clone();
ffi_config.library_marker_id = crate::mock_foreign_marker_id;
let foreign_config: SessionConfig = (&ffi_config).try_into()?;
let returned_options = foreign_config.options().entries();
assert_eq!(original_options.len(), returned_options.len());
Ok(())
}
}