Skip to content

Commit d8195fc

Browse files
committed
Add in FFI integration test for config extensions
1 parent c51b888 commit d8195fc

File tree

4 files changed

+181
-1
lines changed

4 files changed

+181
-1
lines changed

datafusion/ffi/src/config/mod.rs

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ pub mod extension_options;
1919

2020
use abi_stable::StableAbi;
2121
use abi_stable::std_types::{RHashMap, RString};
22-
use datafusion_common::config::{ConfigOptions, ExtensionOptions};
22+
use datafusion_common::config::{ConfigOptions, ExtensionOptions, TableOptions};
2323
use datafusion_common::{DataFusionError, Result};
2424

2525
use crate::config::extension_options::FFI_ExtensionOptions;
@@ -73,3 +73,53 @@ impl TryFrom<FFI_ConfigOptions> for ConfigOptions {
7373
Ok(options)
7474
}
7575
}
76+
77+
// TODO(tsaucer) add text about how extension options will require user to convert to concrete type.
78+
#[repr(C)]
79+
#[derive(Debug, Clone, StableAbi)]
80+
pub struct FFI_TableOptions {
81+
base_options: RHashMap<RString, RString>,
82+
83+
extensions: FFI_ExtensionOptions,
84+
}
85+
86+
impl From<&TableOptions> for FFI_TableOptions {
87+
fn from(options: &TableOptions) -> Self {
88+
let base_options: RHashMap<RString, RString> = options
89+
.entries()
90+
.into_iter()
91+
.filter_map(|entry| entry.value.map(|value| (entry.key, value)))
92+
.map(|(key, value)| (key.into(), value.into()))
93+
.collect();
94+
95+
let mut extensions = FFI_ExtensionOptions::default();
96+
for (extension_name, extension) in options.extensions.iter() {
97+
for entry in extension.entries().iter() {
98+
if let Some(value) = entry.value.as_ref() {
99+
extensions
100+
.set(format!("{extension_name}.{}", entry.key).as_str(), value)
101+
.expect("FFI_ExtensionOptions set should always return Ok");
102+
}
103+
}
104+
}
105+
106+
Self {
107+
base_options,
108+
extensions,
109+
}
110+
}
111+
}
112+
113+
impl TryFrom<FFI_TableOptions> for TableOptions {
114+
type Error = DataFusionError;
115+
fn try_from(ffi_options: FFI_TableOptions) -> Result<Self, Self::Error> {
116+
let mut options = TableOptions::default();
117+
options.extensions.insert(ffi_options.extensions);
118+
119+
for kv_tuple in ffi_options.base_options.iter() {
120+
options.set(kv_tuple.0.as_str(), kv_tuple.1.as_str())?;
121+
}
122+
123+
Ok(options)
124+
}
125+
}

datafusion/ffi/src/tests/config.rs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
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 datafusion_common::config::ConfigExtension;
19+
use datafusion_common::extensions_options;
20+
21+
use crate::config::extension_options::FFI_ExtensionOptions;
22+
23+
extensions_options! {
24+
pub struct ExternalConfig {
25+
/// Should "foo" be replaced by "bar"?
26+
pub is_enabled: bool, default = true
27+
28+
/// Some value to be extracted
29+
pub base_number: usize, default = 1000
30+
}
31+
}
32+
33+
impl PartialEq for ExternalConfig {
34+
fn eq(&self, other: &Self) -> bool {
35+
self.base_number == other.base_number && self.is_enabled == other.is_enabled
36+
}
37+
}
38+
impl Eq for ExternalConfig {}
39+
40+
impl ConfigExtension for ExternalConfig {
41+
const PREFIX: &'static str = "external_config";
42+
}
43+
44+
pub(crate) extern "C" fn create_extension_options() -> FFI_ExtensionOptions {
45+
let mut extensions = FFI_ExtensionOptions::default();
46+
extensions
47+
.add_config(&ExternalConfig::default())
48+
.expect("add_config should be infallible for ExternalConfig");
49+
50+
extensions
51+
}

datafusion/ffi/src/tests/mod.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ use super::table_provider::FFI_TableProvider;
3838
use super::udf::FFI_ScalarUDF;
3939
use crate::catalog_provider::FFI_CatalogProvider;
4040
use crate::catalog_provider_list::FFI_CatalogProviderList;
41+
use crate::config::extension_options::FFI_ExtensionOptions;
4142
use crate::proto::logical_extension_codec::FFI_LogicalExtensionCodec;
4243
use crate::tests::catalog::create_catalog_provider_list;
4344
use crate::udaf::FFI_AggregateUDF;
@@ -46,6 +47,7 @@ use crate::udwf::FFI_WindowUDF;
4647

4748
mod async_provider;
4849
pub mod catalog;
50+
pub mod config;
4951
mod sync_provider;
5052
mod udf_udaf_udwf;
5153
pub mod utils;
@@ -87,6 +89,9 @@ pub struct ForeignLibraryModule {
8789

8890
pub create_rank_udwf: extern "C" fn() -> FFI_WindowUDF,
8991

92+
/// Create extension options, for either ConfigOptions or TableOptions
93+
pub create_extension_options: extern "C" fn() -> FFI_ExtensionOptions,
94+
9095
pub version: extern "C" fn() -> u64,
9196
}
9297

@@ -141,6 +146,7 @@ pub fn get_foreign_library_module() -> ForeignLibraryModuleRef {
141146
create_sum_udaf: create_ffi_sum_func,
142147
create_stddev_udaf: create_ffi_stddev_func,
143148
create_rank_udwf: create_ffi_rank_func,
149+
create_extension_options: config::create_extension_options,
144150
version: super::version,
145151
}
146152
.leak_into_prefix()

datafusion/ffi/tests/ffi_config.rs

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
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+
/// Add an additional module here for convenience to scope this to only
19+
/// when the feature integration-tests is built
20+
#[cfg(feature = "integration-tests")]
21+
mod tests {
22+
use datafusion::error::{DataFusionError, Result};
23+
use datafusion_common::config::{ConfigExtension, ConfigOptions};
24+
use datafusion_ffi::config::extension_options::FFI_ExtensionOptions;
25+
use datafusion_ffi::tests::config::ExternalConfig;
26+
use datafusion_ffi::tests::utils::get_module;
27+
28+
#[test]
29+
fn test_ffi_extension_options() -> Result<()> {
30+
let module = get_module()?;
31+
32+
let extension_options =
33+
module
34+
.create_extension_options()
35+
.ok_or(DataFusionError::NotImplemented(
36+
"External test library failed to implement create_extension_options"
37+
.to_string(),
38+
))?();
39+
40+
println!("{:?} {}", extension_options, FFI_ExtensionOptions::PREFIX);
41+
42+
let mut config = ConfigOptions::new();
43+
config.extensions.insert(extension_options);
44+
45+
fn extract_config(config: &ConfigOptions) -> ExternalConfig {
46+
// For our use case of this test, we do not need to check
47+
// using .get::<ExternalConfig>() but it is left here as an
48+
// example to users of this crate.
49+
config
50+
.extensions
51+
.get::<ExternalConfig>()
52+
.map(|v| v.to_owned())
53+
.or_else(|| {
54+
config
55+
.extensions
56+
.get::<FFI_ExtensionOptions>()
57+
.and_then(|ext| ext.to_extension().ok())
58+
})
59+
.expect("Should be able to get ExternalConfig")
60+
}
61+
62+
// Verify default values are as expected
63+
let returned_config = extract_config(&config);
64+
65+
assert_eq!(returned_config, ExternalConfig::default());
66+
67+
config.set("external_config.is_enabled", "false")?;
68+
let returned_config = extract_config(&config);
69+
assert!(!returned_config.is_enabled);
70+
71+
Ok(())
72+
}
73+
}

0 commit comments

Comments
 (0)