datafusion_ffi/config/
extension_options.rs1use 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#[repr(C)]
44#[derive(Debug)]
45pub struct FFI_ExtensionOptions {
46 pub cloned: unsafe extern "C" fn(&Self) -> FFI_ExtensionOptions,
48
49 pub set: unsafe extern "C" fn(&mut Self, key: SStr, value: SStr) -> FFI_Result<()>,
51
52 pub entries: unsafe extern "C" fn(&Self) -> SVec<(SString, SString)>,
54
55 pub release: unsafe extern "C" fn(&mut Self),
57
58 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 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 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 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 extensions_options! {
255 pub struct MyConfig {
257 pub foo_to_bar: bool, default = true
259
260 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 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 config.set("my_config.baz_count", "42").unwrap();
280
281 let returned_ffi_config =
283 config.extensions.get::<FFI_ExtensionOptions>().unwrap();
284 let my_config: MyConfig = returned_ffi_config.to_extension().unwrap();
285
286 assert!(my_config.foo_to_bar);
288
289 assert_eq!(my_config.baz_count, 42);
291 }
292}