datafusion_ffi/session/
config.rs

1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements.  See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership.  The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License.  You may obtain a copy of the License at
8//
9//   http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied.  See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18use std::collections::HashMap;
19use std::ffi::c_void;
20
21use abi_stable::StableAbi;
22use abi_stable::std_types::{RHashMap, RString};
23use datafusion_common::error::{DataFusionError, Result};
24use datafusion_execution::config::SessionConfig;
25
26/// A stable struct for sharing [`SessionConfig`] across FFI boundaries.
27/// Instead of attempting to expose the entire SessionConfig interface, we
28/// convert the config options into a map from a string to string and pass
29/// those values across the FFI boundary. On the receiver side, we
30/// reconstruct a SessionConfig from those values.
31///
32/// It is possible that using different versions of DataFusion across the
33/// FFI boundary could have differing expectations of the config options.
34/// This is a limitation of this approach, but exposing the entire
35/// SessionConfig via a FFI interface would be extensive and provide limited
36/// value over this version.
37#[repr(C)]
38#[derive(Debug, StableAbi)]
39pub struct FFI_SessionConfig {
40    /// Return a hash map from key to value of the config options represented
41    /// by string values.
42    pub config_options: unsafe extern "C" fn(config: &Self) -> RHashMap<RString, RString>,
43
44    /// Used to create a clone on the provider of the execution plan. This should
45    /// only need to be called by the receiver of the plan.
46    pub clone: unsafe extern "C" fn(plan: &Self) -> Self,
47
48    /// Release the memory of the private data when it is no longer being used.
49    pub release: unsafe extern "C" fn(arg: &mut Self),
50
51    /// Internal data. This is only to be accessed by the provider of the plan.
52    pub private_data: *mut c_void,
53
54    /// Utility to identify when FFI objects are accessed locally through
55    /// the foreign interface. See [`crate::get_library_marker_id`] and
56    /// the crate's `README.md` for more information.
57    pub library_marker_id: extern "C" fn() -> usize,
58}
59
60unsafe impl Send for FFI_SessionConfig {}
61unsafe impl Sync for FFI_SessionConfig {}
62
63impl FFI_SessionConfig {
64    fn inner(&self) -> &SessionConfig {
65        let private_data = self.private_data as *mut SessionConfigPrivateData;
66        unsafe { &(*private_data).config }
67    }
68}
69
70unsafe extern "C" fn config_options_fn_wrapper(
71    config: &FFI_SessionConfig,
72) -> RHashMap<RString, RString> {
73    let config_options = config.inner().options();
74
75    let mut options = RHashMap::default();
76    for config_entry in config_options.entries() {
77        if let Some(value) = config_entry.value {
78            options.insert(config_entry.key.into(), value.into());
79        }
80    }
81
82    options
83}
84
85unsafe extern "C" fn release_fn_wrapper(config: &mut FFI_SessionConfig) {
86    unsafe {
87        debug_assert!(!config.private_data.is_null());
88        let private_data =
89            Box::from_raw(config.private_data as *mut SessionConfigPrivateData);
90        drop(private_data);
91        config.private_data = std::ptr::null_mut();
92    }
93}
94
95unsafe extern "C" fn clone_fn_wrapper(config: &FFI_SessionConfig) -> FFI_SessionConfig {
96    unsafe {
97        let old_private_data = config.private_data as *mut SessionConfigPrivateData;
98        let old_config = (*old_private_data).config.clone();
99
100        let private_data = Box::new(SessionConfigPrivateData { config: old_config });
101
102        FFI_SessionConfig {
103            config_options: config_options_fn_wrapper,
104            private_data: Box::into_raw(private_data) as *mut c_void,
105            clone: clone_fn_wrapper,
106            release: release_fn_wrapper,
107            library_marker_id: crate::get_library_marker_id,
108        }
109    }
110}
111
112struct SessionConfigPrivateData {
113    pub config: SessionConfig,
114}
115
116impl From<&SessionConfig> for FFI_SessionConfig {
117    fn from(session: &SessionConfig) -> Self {
118        let private_data = Box::new(SessionConfigPrivateData {
119            config: session.clone(),
120        });
121
122        Self {
123            config_options: config_options_fn_wrapper,
124            private_data: Box::into_raw(private_data) as *mut c_void,
125            clone: clone_fn_wrapper,
126            release: release_fn_wrapper,
127            library_marker_id: crate::get_library_marker_id,
128        }
129    }
130}
131
132impl Clone for FFI_SessionConfig {
133    fn clone(&self) -> Self {
134        unsafe { (self.clone)(self) }
135    }
136}
137
138impl Drop for FFI_SessionConfig {
139    fn drop(&mut self) {
140        unsafe { (self.release)(self) };
141    }
142}
143
144impl TryFrom<&FFI_SessionConfig> for SessionConfig {
145    type Error = DataFusionError;
146
147    fn try_from(config: &FFI_SessionConfig) -> Result<Self, Self::Error> {
148        if (config.library_marker_id)() == crate::get_library_marker_id() {
149            return Ok(config.inner().clone());
150        }
151
152        let config_options = unsafe { (config.config_options)(config) };
153
154        let mut options_map = HashMap::new();
155        config_options.iter().for_each(|kv_pair| {
156            options_map.insert(kv_pair.0.to_string(), kv_pair.1.to_string());
157        });
158
159        SessionConfig::from_string_hash_map(&options_map)
160    }
161}
162
163#[cfg(test)]
164mod tests {
165    use super::*;
166
167    #[test]
168    fn test_round_trip_ffi_session_config() -> Result<()> {
169        let session_config = SessionConfig::new();
170        let original_options = session_config.options().entries();
171
172        let mut ffi_config: FFI_SessionConfig = (&session_config).into();
173        let _ = ffi_config.clone();
174        ffi_config.library_marker_id = crate::mock_foreign_marker_id;
175
176        let foreign_config: SessionConfig = (&ffi_config).try_into()?;
177
178        let returned_options = foreign_config.options().entries();
179
180        assert_eq!(original_options.len(), returned_options.len());
181
182        Ok(())
183    }
184}