Skip to main content

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::ffi::c_void;
19
20use crate::config::FFI_ConfigOptions;
21use abi_stable::StableAbi;
22use datafusion_common::config::ConfigOptions;
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    /// FFI stable configuration options.
41    pub config_options: FFI_ConfigOptions,
42
43    /// Used to create a clone on the provider of the execution plan. This should
44    /// only need to be called by the receiver of the plan.
45    pub clone: unsafe extern "C" fn(plan: &Self) -> Self,
46
47    /// Release the memory of the private data when it is no longer being used.
48    pub release: unsafe extern "C" fn(arg: &mut Self),
49
50    /// Internal data. This is only to be accessed by the provider of the plan.
51    pub private_data: *mut c_void,
52
53    /// Utility to identify when FFI objects are accessed locally through
54    /// the foreign interface. See [`crate::get_library_marker_id`] and
55    /// the crate's `README.md` for more information.
56    pub library_marker_id: extern "C" fn() -> usize,
57}
58
59unsafe impl Send for FFI_SessionConfig {}
60unsafe impl Sync for FFI_SessionConfig {}
61
62impl FFI_SessionConfig {
63    fn inner(&self) -> &SessionConfig {
64        let private_data = self.private_data as *mut SessionConfigPrivateData;
65        unsafe { &(*private_data).config }
66    }
67}
68
69unsafe extern "C" fn release_fn_wrapper(config: &mut FFI_SessionConfig) {
70    unsafe {
71        debug_assert!(!config.private_data.is_null());
72        let private_data =
73            Box::from_raw(config.private_data as *mut SessionConfigPrivateData);
74        drop(private_data);
75        config.private_data = std::ptr::null_mut();
76    }
77}
78
79unsafe extern "C" fn clone_fn_wrapper(config: &FFI_SessionConfig) -> FFI_SessionConfig {
80    unsafe {
81        let old_private_data = config.private_data as *mut SessionConfigPrivateData;
82        let old_config = (*old_private_data).config.clone();
83
84        let private_data = Box::new(SessionConfigPrivateData { config: old_config });
85
86        FFI_SessionConfig {
87            config_options: config.config_options.clone(),
88            private_data: Box::into_raw(private_data) as *mut c_void,
89            clone: clone_fn_wrapper,
90            release: release_fn_wrapper,
91            library_marker_id: crate::get_library_marker_id,
92        }
93    }
94}
95
96struct SessionConfigPrivateData {
97    pub config: SessionConfig,
98}
99
100impl From<&SessionConfig> for FFI_SessionConfig {
101    fn from(session: &SessionConfig) -> Self {
102        let private_data = Box::new(SessionConfigPrivateData {
103            config: session.clone(),
104        });
105
106        let config_options = FFI_ConfigOptions::from(session.options().as_ref());
107
108        Self {
109            config_options,
110            private_data: Box::into_raw(private_data) as *mut c_void,
111            clone: clone_fn_wrapper,
112            release: release_fn_wrapper,
113            library_marker_id: crate::get_library_marker_id,
114        }
115    }
116}
117
118impl Clone for FFI_SessionConfig {
119    fn clone(&self) -> Self {
120        unsafe { (self.clone)(self) }
121    }
122}
123
124impl Drop for FFI_SessionConfig {
125    fn drop(&mut self) {
126        unsafe { (self.release)(self) };
127    }
128}
129
130impl TryFrom<&FFI_SessionConfig> for SessionConfig {
131    type Error = DataFusionError;
132
133    fn try_from(config: &FFI_SessionConfig) -> Result<Self, Self::Error> {
134        if (config.library_marker_id)() == crate::get_library_marker_id() {
135            return Ok(config.inner().clone());
136        }
137
138        let config_options = ConfigOptions::try_from(config.config_options.clone())?;
139
140        Ok(SessionConfig::from(config_options))
141    }
142}
143
144#[cfg(test)]
145mod tests {
146    use super::*;
147
148    #[test]
149    fn test_round_trip_ffi_session_config() -> Result<()> {
150        let session_config = SessionConfig::new();
151        let original_options = session_config.options().entries();
152
153        let mut ffi_config: FFI_SessionConfig = (&session_config).into();
154        let _ = ffi_config.clone();
155        ffi_config.library_marker_id = crate::mock_foreign_marker_id;
156
157        let foreign_config: SessionConfig = (&ffi_config).try_into()?;
158
159        let returned_options = foreign_config.options().entries();
160
161        assert_eq!(original_options.len(), returned_options.len());
162
163        Ok(())
164    }
165}