Skip to content

Commit 1b61005

Browse files
committed
feat: introduce a new config class to manage configurable options following design ideas from Velox.
1 parent b839b3b commit 1b61005

File tree

3 files changed

+225
-1
lines changed

3 files changed

+225
-1
lines changed

src/iceberg/util/config.h

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
#pragma once
20+
21+
#include <format>
22+
#include <functional>
23+
#include <map>
24+
#include <string>
25+
26+
namespace iceberg {
27+
28+
template <class ConcreteConfig>
29+
class ConfigBase {
30+
public:
31+
template <typename T>
32+
class Entry {
33+
Entry(
34+
std::string key, const T& val,
35+
std::function<std::string(const T&)> toStr =
36+
[](const T& val) {
37+
if constexpr ((std::is_signed_v<T> && std::is_integral_v<T>) ||
38+
std::is_floating_point_v<T>) {
39+
return std::to_string(val);
40+
} else if constexpr (std::is_same_v<T, bool>) {
41+
return val ? "true" : "false";
42+
} else if constexpr (std::is_same_v<T, std::string> ||
43+
std::is_same_v<T, std::string_view>) {
44+
return val;
45+
} else {
46+
throw std::runtime_error(
47+
std::format("Explicit toStr() is required for {}", typeid(T).name()));
48+
}
49+
},
50+
std::function<T(const std::string&)> toT = [](const std::string& val) -> T {
51+
if constexpr (std::is_same_v<T, std::string>) {
52+
return val;
53+
} else if constexpr (std::is_same_v<T, bool>) {
54+
return val == "true";
55+
} else if constexpr (std::is_signed_v<T> && std::is_integral_v<T>) {
56+
return static_cast<T>(std::stoll(val));
57+
} else if constexpr (std::is_floating_point_v<T>) {
58+
return static_cast<T>(std::stod(val));
59+
} else {
60+
throw std::runtime_error(
61+
std::format("Explicit toT() is required for {}", typeid(T).name()));
62+
}
63+
})
64+
: key_{std::move(key)}, default_{val}, toStr_{toStr}, toT_{toT} {}
65+
66+
const std::string key_;
67+
const T default_;
68+
const std::function<std::string(const T&)> toStr_;
69+
const std::function<T(const std::string&)> toT_;
70+
71+
friend ConfigBase;
72+
friend ConcreteConfig;
73+
74+
public:
75+
const std::string& key() const { return key_; }
76+
77+
T value() const { return default_; }
78+
};
79+
80+
template <typename T>
81+
ConfigBase& set(const Entry<T>& entry, const T& val) {
82+
configs_[entry.key_] = entry.toStr_(val);
83+
return *this;
84+
}
85+
86+
template <typename T>
87+
ConfigBase& unset(const Entry<T>& entry) {
88+
configs_.erase(entry.key_);
89+
return *this;
90+
}
91+
92+
ConfigBase& reset() {
93+
configs_.clear();
94+
return *this;
95+
}
96+
97+
template <typename T>
98+
T get(const Entry<T>& entry) const {
99+
try {
100+
auto iter = configs_.find(entry.key_);
101+
return iter != configs_.cend() ? entry.toT_(iter->second) : entry.default_;
102+
} catch (std::exception& e) {
103+
throw std::runtime_error(
104+
std::format("Failed to get config {} with error: {}", entry.key_, e.what()));
105+
}
106+
}
107+
108+
std::map<std::string, std::string> const& configs() const { return configs_; }
109+
110+
protected:
111+
std::map<std::string, std::string> configs_;
112+
};
113+
114+
} // namespace iceberg

test/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ target_link_libraries(json_serde_test PRIVATE iceberg_static GTest::gtest_main
5757
add_test(NAME json_serde_test COMMAND json_serde_test)
5858

5959
add_executable(util_test)
60-
target_sources(util_test PRIVATE expected_test.cc formatter_test.cc)
60+
target_sources(util_test PRIVATE expected_test.cc formatter_test.cc config_test.cc)
6161
target_link_libraries(util_test PRIVATE iceberg_static GTest::gtest_main GTest::gmock)
6262
add_test(NAME util_test COMMAND util_test)
6363

test/config_test.cc

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
#include <string>
21+
22+
#include <gtest/gtest.h>
23+
#include <iceberg/util/config.h>
24+
25+
namespace iceberg {
26+
27+
enum class TestEnum { VALUE1, VALUE2, VALUE3 };
28+
29+
std::string enumToString(const TestEnum& val) {
30+
switch (val) {
31+
case TestEnum::VALUE1:
32+
return "VALUE1";
33+
case TestEnum::VALUE2:
34+
return "VALUE2";
35+
case TestEnum::VALUE3:
36+
return "VALUE3";
37+
default:
38+
throw std::runtime_error("Invalid enum value");
39+
}
40+
}
41+
42+
TestEnum stringToEnum(const std::string& val) {
43+
if (val == "VALUE1") {
44+
return TestEnum::VALUE1;
45+
} else if (val == "VALUE2") {
46+
return TestEnum::VALUE2;
47+
} else if (val == "VALUE3") {
48+
return TestEnum::VALUE3;
49+
} else {
50+
throw std::runtime_error("Invalid enum string");
51+
}
52+
}
53+
54+
// Define a concrete config class for testing
55+
class TestConfig : public ConfigBase<TestConfig> {
56+
public:
57+
template <typename T>
58+
using Entry = const ConfigBase<TestConfig>::Entry<T>;
59+
60+
inline static const Entry<std::string> STRING_CONFIG{"string_config", "default_value"};
61+
inline static const Entry<int> INT_CONFIG{"int_config", 25};
62+
inline static const Entry<bool> BOOL_CONFIG{"bool_config", false};
63+
inline static const Entry<TestEnum> ENUM_CONFIG{"enum_config", TestEnum::VALUE1,
64+
enumToString, stringToEnum};
65+
inline static const Entry<double> DOUBLE_CONFIG{"double_config", 3.14};
66+
};
67+
68+
TEST(ConfigTest, BasicOperations) {
69+
TestConfig config;
70+
71+
// Test default values
72+
ASSERT_EQ(config.get(TestConfig::STRING_CONFIG), std::string("default_value"));
73+
ASSERT_EQ(config.get(TestConfig::INT_CONFIG), 25);
74+
ASSERT_EQ(config.get(TestConfig::BOOL_CONFIG), false);
75+
ASSERT_EQ(config.get(TestConfig::ENUM_CONFIG), TestEnum::VALUE1);
76+
ASSERT_EQ(config.get(TestConfig::DOUBLE_CONFIG), 3.14);
77+
78+
// Test setting values
79+
config.set(TestConfig::STRING_CONFIG, std::string("new_value"));
80+
config.set(TestConfig::INT_CONFIG, 100);
81+
config.set(TestConfig::BOOL_CONFIG, true);
82+
config.set(TestConfig::ENUM_CONFIG, TestEnum::VALUE2);
83+
config.set(TestConfig::DOUBLE_CONFIG, 2.71828);
84+
85+
ASSERT_EQ(config.get(TestConfig::STRING_CONFIG), "new_value");
86+
ASSERT_EQ(config.get(TestConfig::INT_CONFIG), 100);
87+
ASSERT_EQ(config.get(TestConfig::BOOL_CONFIG), true);
88+
ASSERT_EQ(config.get(TestConfig::ENUM_CONFIG), TestEnum::VALUE2);
89+
ASSERT_EQ(config.get(TestConfig::DOUBLE_CONFIG), 2.71828);
90+
91+
// Test unsetting a value
92+
config.unset(TestConfig::INT_CONFIG);
93+
config.unset(TestConfig::ENUM_CONFIG);
94+
config.unset(TestConfig::DOUBLE_CONFIG);
95+
ASSERT_EQ(config.get(TestConfig::INT_CONFIG), 25);
96+
ASSERT_EQ(config.get(TestConfig::STRING_CONFIG), "new_value");
97+
ASSERT_EQ(config.get(TestConfig::BOOL_CONFIG), true);
98+
ASSERT_EQ(config.get(TestConfig::ENUM_CONFIG), TestEnum::VALUE1);
99+
ASSERT_EQ(config.get(TestConfig::DOUBLE_CONFIG), 3.14);
100+
101+
// Test resetting all values
102+
config.reset();
103+
ASSERT_EQ(config.get(TestConfig::STRING_CONFIG), "default_value");
104+
ASSERT_EQ(config.get(TestConfig::INT_CONFIG), 25);
105+
ASSERT_EQ(config.get(TestConfig::BOOL_CONFIG), false);
106+
ASSERT_EQ(config.get(TestConfig::ENUM_CONFIG), TestEnum::VALUE1);
107+
ASSERT_EQ(config.get(TestConfig::DOUBLE_CONFIG), 3.14);
108+
}
109+
110+
} // namespace iceberg

0 commit comments

Comments
 (0)