datafusion_ffi/config/
extension_options.rs1use std::any::Any;
19use std::collections::HashMap;
20use std::ffi::c_void;
21
22use abi_stable::StableAbi;
23use abi_stable::std_types::{RResult, RStr, RString, RVec, Tuple2};
24use datafusion_common::config::{ConfigEntry, ConfigExtension, ExtensionOptions};
25use datafusion_common::{Result, exec_err};
26
27use crate::df_result;
28
29#[repr(C)]
41#[derive(Debug, StableAbi)]
42pub struct FFI_ExtensionOptions {
43 pub cloned: unsafe extern "C" fn(&Self) -> FFI_ExtensionOptions,
45
46 pub set:
48 unsafe extern "C" fn(&mut Self, key: RStr, value: RStr) -> RResult<(), RString>,
49
50 pub entries: unsafe extern "C" fn(&Self) -> RVec<Tuple2<RString, RString>>,
52
53 pub release: unsafe extern "C" fn(&mut Self),
55
56 pub private_data: *mut c_void,
58}
59
60unsafe impl Send for FFI_ExtensionOptions {}
61unsafe impl Sync for FFI_ExtensionOptions {}
62
63pub struct ExtensionOptionsPrivateData {
64 pub options: HashMap<String, String>,
65}
66
67impl FFI_ExtensionOptions {
68 #[inline]
69 fn inner_mut(&mut self) -> &mut HashMap<String, String> {
70 let private_data = self.private_data as *mut ExtensionOptionsPrivateData;
71 unsafe { &mut (*private_data).options }
72 }
73
74 #[inline]
75 fn inner(&self) -> &HashMap<String, String> {
76 let private_data = self.private_data as *const ExtensionOptionsPrivateData;
77 unsafe { &(*private_data).options }
78 }
79}
80
81unsafe extern "C" fn cloned_fn_wrapper(
82 options: &FFI_ExtensionOptions,
83) -> FFI_ExtensionOptions {
84 options
85 .inner()
86 .iter()
87 .map(|(k, v)| (k.to_owned(), v.to_owned()))
88 .collect::<HashMap<String, String>>()
89 .into()
90}
91
92unsafe extern "C" fn set_fn_wrapper(
93 options: &mut FFI_ExtensionOptions,
94 key: RStr,
95 value: RStr,
96) -> RResult<(), RString> {
97 let _ = options.inner_mut().insert(key.into(), value.into());
98 RResult::ROk(())
99}
100
101unsafe extern "C" fn entries_fn_wrapper(
102 options: &FFI_ExtensionOptions,
103) -> RVec<Tuple2<RString, RString>> {
104 options
105 .inner()
106 .iter()
107 .map(|(key, value)| (key.to_owned().into(), value.to_owned().into()).into())
108 .collect()
109}
110
111unsafe extern "C" fn release_fn_wrapper(options: &mut FFI_ExtensionOptions) {
112 unsafe {
113 debug_assert!(!options.private_data.is_null());
114 let private_data =
115 Box::from_raw(options.private_data as *mut ExtensionOptionsPrivateData);
116 drop(private_data);
117 options.private_data = std::ptr::null_mut();
118 }
119}
120
121impl Default for FFI_ExtensionOptions {
122 fn default() -> Self {
123 HashMap::new().into()
124 }
125}
126
127impl From<HashMap<String, String>> for FFI_ExtensionOptions {
128 fn from(options: HashMap<String, String>) -> Self {
129 let private_data = ExtensionOptionsPrivateData { options };
130
131 Self {
132 cloned: cloned_fn_wrapper,
133 set: set_fn_wrapper,
134 entries: entries_fn_wrapper,
135 release: release_fn_wrapper,
136 private_data: Box::into_raw(Box::new(private_data)) as *mut c_void,
137 }
138 }
139}
140
141impl Drop for FFI_ExtensionOptions {
142 fn drop(&mut self) {
143 unsafe { (self.release)(self) }
144 }
145}
146
147impl Clone for FFI_ExtensionOptions {
148 fn clone(&self) -> Self {
149 unsafe { (self.cloned)(self) }
150 }
151}
152
153impl ConfigExtension for FFI_ExtensionOptions {
154 const PREFIX: &'static str =
155 datafusion_common::config::DATAFUSION_FFI_CONFIG_NAMESPACE;
156}
157
158impl ExtensionOptions for FFI_ExtensionOptions {
159 fn as_any(&self) -> &dyn Any {
160 self
161 }
162
163 fn as_any_mut(&mut self) -> &mut dyn Any {
164 self
165 }
166
167 fn cloned(&self) -> Box<dyn ExtensionOptions> {
168 let ffi_options = unsafe { (self.cloned)(self) };
169 Box::new(ffi_options)
170 }
171
172 fn set(&mut self, key: &str, value: &str) -> Result<()> {
173 if key.split_once('.').is_none() {
174 return exec_err!("Unable to set FFI config value without namespace set");
175 };
176
177 df_result!(unsafe { (self.set)(self, key.into(), value.into()) })
178 }
179
180 fn entries(&self) -> Vec<ConfigEntry> {
181 unsafe {
182 (self.entries)(self)
183 .into_iter()
184 .map(|entry_tuple| ConfigEntry {
185 key: entry_tuple.0.into(),
186 value: Some(entry_tuple.1.into()),
187 description: "ffi_config_options",
188 })
189 .collect()
190 }
191 }
192}
193
194impl FFI_ExtensionOptions {
195 pub fn add_config<C: ConfigExtension>(&mut self, config: &C) -> Result<()> {
199 for entry in config.entries() {
200 if let Some(value) = entry.value {
201 let key = format!("{}.{}", C::PREFIX, entry.key);
202 self.set(key.as_str(), value.as_str())?;
203 }
204 }
205
206 Ok(())
207 }
208
209 pub fn merge(&mut self, other: &FFI_ExtensionOptions) -> Result<()> {
212 for entry in other.entries() {
213 if let Some(value) = entry.value {
214 self.set(entry.key.as_str(), value.as_str())?;
215 }
216 }
217 Ok(())
218 }
219
220 pub fn to_extension<C: ConfigExtension + Default>(&self) -> Result<C> {
223 let mut result = C::default();
224
225 unsafe {
226 for entry in (self.entries)(self) {
227 let key = entry.0.as_str();
228 let value = entry.1.as_str();
229
230 if let Some((prefix, inner_key)) = key.split_once('.')
231 && prefix == C::PREFIX
232 {
233 result.set(inner_key, value)?;
234 }
235 }
236 }
237
238 Ok(result)
239 }
240}
241
242#[cfg(test)]
243mod tests {
244 use datafusion_common::config::{ConfigExtension, ConfigOptions};
245 use datafusion_common::extensions_options;
246
247 use crate::config::extension_options::FFI_ExtensionOptions;
248
249 extensions_options! {
251 pub struct MyConfig {
253 pub foo_to_bar: bool, default = true
255
256 pub baz_count: usize, default = 1337
258 }
259 }
260
261 impl ConfigExtension for MyConfig {
262 const PREFIX: &'static str = "my_config";
263 }
264
265 #[test]
266 fn round_trip_ffi_extension_options() {
267 let mut config = ConfigOptions::default();
269 let mut ffi_options = FFI_ExtensionOptions::default();
270 ffi_options.add_config(&MyConfig::default()).unwrap();
271
272 config.extensions.insert(ffi_options);
273
274 config.set("my_config.baz_count", "42").unwrap();
276
277 let returned_ffi_config =
279 config.extensions.get::<FFI_ExtensionOptions>().unwrap();
280 let my_config: MyConfig = returned_ffi_config.to_extension().unwrap();
281
282 assert!(my_config.foo_to_bar);
284
285 assert_eq!(my_config.baz_count, 42);
287 }
288}