|
| 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 | + |
| 18 | +use std::any::Any; |
| 19 | +use std::collections::HashMap; |
| 20 | +use std::ffi::c_void; |
| 21 | + |
| 22 | +use abi_stable::StableAbi; |
| 23 | +use abi_stable::std_types::{RResult, RStr, RString, RVec, Tuple2}; |
| 24 | +use datafusion_common::config::{ConfigEntry, ConfigExtension, ExtensionOptions}; |
| 25 | +use datafusion_common::{Result, exec_err}; |
| 26 | + |
| 27 | +use crate::df_result; |
| 28 | + |
| 29 | +/// A stable struct for sharing [`ExtensionOptions`] across FFI boundaries. |
| 30 | +/// |
| 31 | +/// Unlike other FFI structs in this crate, we do not construct a foreign |
| 32 | +/// variant of this object. This is due to the typical method for interacting |
| 33 | +/// with extension options is by creating a local struct of your concrete type. |
| 34 | +/// To support this methodology use the `to_extension` method instead. |
| 35 | +/// |
| 36 | +/// When using [`FFI_ExtensionOptions`] with multiple extensions, all extension |
| 37 | +/// values are stored on a single [`FFI_ExtensionOptions`] object. The keys |
| 38 | +/// are stored with the full path prefix to avoid overwriting values when using |
| 39 | +/// multiple extensions. |
| 40 | +#[repr(C)] |
| 41 | +#[derive(Debug, StableAbi)] |
| 42 | +pub struct FFI_ExtensionOptions { |
| 43 | + /// Return a deep clone of this [`ExtensionOptions`] |
| 44 | + pub cloned: unsafe extern "C" fn(&Self) -> FFI_ExtensionOptions, |
| 45 | + |
| 46 | + /// Set the given `key`, `value` pair |
| 47 | + pub set: |
| 48 | + unsafe extern "C" fn(&mut Self, key: RStr, value: RStr) -> RResult<(), RString>, |
| 49 | + |
| 50 | + /// Returns the [`ConfigEntry`] stored in this [`ExtensionOptions`] |
| 51 | + pub entries: unsafe extern "C" fn(&Self) -> RVec<Tuple2<RString, RString>>, |
| 52 | + |
| 53 | + /// Release the memory of the private data when it is no longer being used. |
| 54 | + pub release: unsafe extern "C" fn(&mut Self), |
| 55 | + |
| 56 | + /// Internal data. This is only to be accessed by the provider of the options. |
| 57 | + pub private_data: *mut c_void, |
| 58 | +} |
| 59 | + |
| 60 | +unsafe impl Send for FFI_ExtensionOptions {} |
| 61 | +unsafe impl Sync for FFI_ExtensionOptions {} |
| 62 | + |
| 63 | +pub struct ExtensionOptionsPrivateData { |
| 64 | + pub options: HashMap<String, String>, |
| 65 | +} |
| 66 | + |
| 67 | +impl 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 | + |
| 81 | +unsafe 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 | + |
| 92 | +unsafe 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 | + |
| 101 | +unsafe 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 | + |
| 111 | +unsafe 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 | + |
| 121 | +impl Default for FFI_ExtensionOptions { |
| 122 | + fn default() -> Self { |
| 123 | + HashMap::new().into() |
| 124 | + } |
| 125 | +} |
| 126 | + |
| 127 | +impl 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 | + |
| 141 | +impl Drop for FFI_ExtensionOptions { |
| 142 | + fn drop(&mut self) { |
| 143 | + unsafe { (self.release)(self) } |
| 144 | + } |
| 145 | +} |
| 146 | + |
| 147 | +impl Clone for FFI_ExtensionOptions { |
| 148 | + fn clone(&self) -> Self { |
| 149 | + unsafe { (self.cloned)(self) } |
| 150 | + } |
| 151 | +} |
| 152 | + |
| 153 | +impl ConfigExtension for FFI_ExtensionOptions { |
| 154 | + const PREFIX: &'static str = |
| 155 | + datafusion_common::config::DATAFUSION_FFI_CONFIG_NAMESPACE; |
| 156 | +} |
| 157 | + |
| 158 | +impl 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 | + |
| 194 | +impl FFI_ExtensionOptions { |
| 195 | + /// Add all of the values in a concrete configuration extension to the |
| 196 | + /// FFI variant. This is safe to call on either side of the FFI |
| 197 | + /// boundary. |
| 198 | + 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 | + /// Merge another `FFI_ExtensionOptions` configurations into this one. |
| 210 | + /// This is safe to call on either side of the FFI boundary. |
| 211 | + 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 | + /// Create a concrete extension type from the FFI variant. |
| 221 | + /// This is safe to call on either side of the FFI boundary. |
| 222 | + 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)] |
| 243 | +mod 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 | + // Define a new configuration struct using the `extensions_options` macro |
| 250 | + extensions_options! { |
| 251 | + /// My own config options. |
| 252 | + pub struct MyConfig { |
| 253 | + /// Should "foo" be replaced by "bar"? |
| 254 | + pub foo_to_bar: bool, default = true |
| 255 | + |
| 256 | + /// How many "baz" should be created? |
| 257 | + 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 | + // set up config struct and register extension |
| 268 | + 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 | + // overwrite config default |
| 275 | + config.set("my_config.baz_count", "42").unwrap(); |
| 276 | + |
| 277 | + // check config state |
| 278 | + let returned_ffi_config = |
| 279 | + config.extensions.get::<FFI_ExtensionOptions>().unwrap(); |
| 280 | + let my_config: MyConfig = returned_ffi_config.to_extension().unwrap(); |
| 281 | + |
| 282 | + // check default value |
| 283 | + assert!(my_config.foo_to_bar); |
| 284 | + |
| 285 | + // check overwritten value |
| 286 | + assert_eq!(my_config.baz_count, 42); |
| 287 | + } |
| 288 | +} |
0 commit comments