Skip to main content

datafusion_ffi/config/
extension_options.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::any::Any;
19use std::collections::HashMap;
20use std::ffi::c_void;
21
22use datafusion_common::config::{ConfigEntry, ConfigExtension, ExtensionOptions};
23use datafusion_common::{Result, exec_err};
24
25use stabby::str::Str as SStr;
26use stabby::string::String as SString;
27use stabby::vec::Vec as SVec;
28
29use crate::df_result;
30use crate::util::FFI_Result;
31
32/// A stable struct for sharing [`ExtensionOptions`] across FFI boundaries.
33///
34/// Unlike other FFI structs in this crate, we do not construct a foreign
35/// variant of this object. This is due to the typical method for interacting
36/// with extension options is by creating a local struct of your concrete type.
37/// To support this methodology use the `to_extension` method instead.
38///
39/// When using [`FFI_ExtensionOptions`] with multiple extensions, all extension
40/// values are stored on a single [`FFI_ExtensionOptions`] object. The keys
41/// are stored with the full path prefix to avoid overwriting values when using
42/// multiple extensions.
43#[repr(C)]
44#[derive(Debug)]
45pub struct FFI_ExtensionOptions {
46    /// Return a deep clone of this [`ExtensionOptions`]
47    pub cloned: unsafe extern "C" fn(&Self) -> FFI_ExtensionOptions,
48
49    /// Set the given `key`, `value` pair
50    pub set: unsafe extern "C" fn(&mut Self, key: SStr, value: SStr) -> FFI_Result<()>,
51
52    /// Returns the [`ConfigEntry`] stored in this [`ExtensionOptions`]
53    pub entries: unsafe extern "C" fn(&Self) -> SVec<(SString, SString)>,
54
55    /// Release the memory of the private data when it is no longer being used.
56    pub release: unsafe extern "C" fn(&mut Self),
57
58    /// Internal data. This is only to be accessed by the provider of the options.
59    pub private_data: *mut c_void,
60}
61
62unsafe impl Send for FFI_ExtensionOptions {}
63unsafe impl Sync for FFI_ExtensionOptions {}
64
65pub struct ExtensionOptionsPrivateData {
66    pub options: HashMap<String, String>,
67}
68
69impl FFI_ExtensionOptions {
70    #[inline]
71    fn inner_mut(&mut self) -> &mut HashMap<String, String> {
72        let private_data = self.private_data as *mut ExtensionOptionsPrivateData;
73        unsafe { &mut (*private_data).options }
74    }
75
76    #[inline]
77    fn inner(&self) -> &HashMap<String, String> {
78        let private_data = self.private_data as *const ExtensionOptionsPrivateData;
79        unsafe { &(*private_data).options }
80    }
81}
82
83unsafe extern "C" fn cloned_fn_wrapper(
84    options: &FFI_ExtensionOptions,
85) -> FFI_ExtensionOptions {
86    options
87        .inner()
88        .iter()
89        .map(|(k, v)| (k.to_owned(), v.to_owned()))
90        .collect::<HashMap<String, String>>()
91        .into()
92}
93
94unsafe extern "C" fn set_fn_wrapper(
95    options: &mut FFI_ExtensionOptions,
96    key: SStr,
97    value: SStr,
98) -> FFI_Result<()> {
99    let _ = options
100        .inner_mut()
101        .insert(key.as_str().into(), value.as_str().into());
102    FFI_Result::Ok(())
103}
104
105unsafe extern "C" fn entries_fn_wrapper(
106    options: &FFI_ExtensionOptions,
107) -> SVec<(SString, SString)> {
108    options
109        .inner()
110        .iter()
111        .map(|(key, value)| (key.to_owned().into(), value.to_owned().into()))
112        .collect()
113}
114
115unsafe extern "C" fn release_fn_wrapper(options: &mut FFI_ExtensionOptions) {
116    unsafe {
117        debug_assert!(!options.private_data.is_null());
118        let private_data =
119            Box::from_raw(options.private_data as *mut ExtensionOptionsPrivateData);
120        drop(private_data);
121        options.private_data = std::ptr::null_mut();
122    }
123}
124
125impl Default for FFI_ExtensionOptions {
126    fn default() -> Self {
127        HashMap::new().into()
128    }
129}
130
131impl From<HashMap<String, String>> for FFI_ExtensionOptions {
132    fn from(options: HashMap<String, String>) -> Self {
133        let private_data = ExtensionOptionsPrivateData { options };
134
135        Self {
136            cloned: cloned_fn_wrapper,
137            set: set_fn_wrapper,
138            entries: entries_fn_wrapper,
139            release: release_fn_wrapper,
140            private_data: Box::into_raw(Box::new(private_data)) as *mut c_void,
141        }
142    }
143}
144
145impl Drop for FFI_ExtensionOptions {
146    fn drop(&mut self) {
147        unsafe { (self.release)(self) }
148    }
149}
150
151impl Clone for FFI_ExtensionOptions {
152    fn clone(&self) -> Self {
153        unsafe { (self.cloned)(self) }
154    }
155}
156
157impl ConfigExtension for FFI_ExtensionOptions {
158    const PREFIX: &'static str =
159        datafusion_common::config::DATAFUSION_FFI_CONFIG_NAMESPACE;
160}
161
162impl ExtensionOptions for FFI_ExtensionOptions {
163    fn as_any(&self) -> &dyn Any {
164        self
165    }
166
167    fn as_any_mut(&mut self) -> &mut dyn Any {
168        self
169    }
170
171    fn cloned(&self) -> Box<dyn ExtensionOptions> {
172        let ffi_options = unsafe { (self.cloned)(self) };
173        Box::new(ffi_options)
174    }
175
176    fn set(&mut self, key: &str, value: &str) -> Result<()> {
177        if key.split_once('.').is_none() {
178            return exec_err!("Unable to set FFI config value without namespace set");
179        };
180
181        df_result!(unsafe { (self.set)(self, key.into(), value.into()) })
182    }
183
184    fn entries(&self) -> Vec<ConfigEntry> {
185        unsafe {
186            (self.entries)(self)
187                .into_iter()
188                .map(|entry_tuple| ConfigEntry {
189                    key: entry_tuple.0.into(),
190                    value: Some(entry_tuple.1.into()),
191                    description: "ffi_config_options",
192                })
193                .collect()
194        }
195    }
196}
197
198impl FFI_ExtensionOptions {
199    /// Add all of the values in a concrete configuration extension to the
200    /// FFI variant. This is safe to call on either side of the FFI
201    /// boundary.
202    pub fn add_config<C: ConfigExtension>(&mut self, config: &C) -> Result<()> {
203        for entry in config.entries() {
204            if let Some(value) = entry.value {
205                let key = format!("{}.{}", C::PREFIX, entry.key);
206                self.set(key.as_str(), value.as_str())?;
207            }
208        }
209
210        Ok(())
211    }
212
213    /// Merge another `FFI_ExtensionOptions` configurations into this one.
214    /// This is safe to call on either side of the FFI boundary.
215    pub fn merge(&mut self, other: &FFI_ExtensionOptions) -> Result<()> {
216        for entry in other.entries() {
217            if let Some(value) = entry.value {
218                self.set(entry.key.as_str(), value.as_str())?;
219            }
220        }
221        Ok(())
222    }
223
224    /// Create a concrete extension type from the FFI variant.
225    /// This is safe to call on either side of the FFI boundary.
226    pub fn to_extension<C: ConfigExtension + Default>(&self) -> Result<C> {
227        let mut result = C::default();
228
229        unsafe {
230            for entry in (self.entries)(self) {
231                let key = entry.0.as_str();
232                let value = entry.1.as_str();
233
234                if let Some((prefix, inner_key)) = key.split_once('.')
235                    && prefix == C::PREFIX
236                {
237                    result.set(inner_key, value)?;
238                }
239            }
240        }
241
242        Ok(result)
243    }
244}
245
246#[cfg(test)]
247mod tests {
248    use datafusion_common::config::{ConfigExtension, ConfigOptions};
249    use datafusion_common::extensions_options;
250
251    use crate::config::extension_options::FFI_ExtensionOptions;
252
253    // Define a new configuration struct using the `extensions_options` macro
254    extensions_options! {
255       /// My own config options.
256       pub struct MyConfig {
257           /// Should "foo" be replaced by "bar"?
258           pub foo_to_bar: bool, default = true
259
260           /// How many "baz" should be created?
261           pub baz_count: usize, default = 1337
262       }
263    }
264
265    impl ConfigExtension for MyConfig {
266        const PREFIX: &'static str = "my_config";
267    }
268
269    #[test]
270    fn round_trip_ffi_extension_options() {
271        // set up config struct and register extension
272        let mut config = ConfigOptions::default();
273        let mut ffi_options = FFI_ExtensionOptions::default();
274        ffi_options.add_config(&MyConfig::default()).unwrap();
275
276        config.extensions.insert(ffi_options);
277
278        // overwrite config default
279        config.set("my_config.baz_count", "42").unwrap();
280
281        // check config state
282        let returned_ffi_config =
283            config.extensions.get::<FFI_ExtensionOptions>().unwrap();
284        let my_config: MyConfig = returned_ffi_config.to_extension().unwrap();
285
286        // check default value
287        assert!(my_config.foo_to_bar);
288
289        // check overwritten value
290        assert_eq!(my_config.baz_count, 42);
291    }
292}