Skip to content

Commit 408e1a4

Browse files
authored
feat: add base config implementation (#92)
introduce a new config class to manage configurable options following design ideas from Velox
1 parent b839b3b commit 408e1a4

File tree

3 files changed

+230
-1
lines changed

3 files changed

+230
-1
lines changed

src/iceberg/util/config.h

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
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 <string>
24+
#include <unordered_map>
25+
26+
#include "iceberg/exception.h"
27+
28+
namespace iceberg {
29+
namespace internal {
30+
// Default conversion functions
31+
template <typename U>
32+
std::string DefaultToString(const U& val) {
33+
if constexpr ((std::is_signed_v<U> && std::is_integral_v<U>) ||
34+
std::is_floating_point_v<U>) {
35+
return std::to_string(val);
36+
} else if constexpr (std::is_same_v<U, bool>) {
37+
return val ? "true" : "false";
38+
} else if constexpr (std::is_same_v<U, std::string> ||
39+
std::is_same_v<U, std::string_view>) {
40+
return val;
41+
} else {
42+
throw IcebergError(
43+
std::format("Explicit to_str() is required for {}", typeid(U).name()));
44+
}
45+
}
46+
47+
template <typename U>
48+
U DefaultFromString(const std::string& val) {
49+
if constexpr (std::is_same_v<U, std::string>) {
50+
return val;
51+
} else if constexpr (std::is_same_v<U, bool>) {
52+
return val == "true";
53+
} else if constexpr (std::is_signed_v<U> && std::is_integral_v<U>) {
54+
return static_cast<U>(std::stoll(val));
55+
} else if constexpr (std::is_floating_point_v<U>) {
56+
return static_cast<U>(std::stod(val));
57+
} else {
58+
throw IcebergError(
59+
std::format("Explicit from_str() is required for {}", typeid(U).name()));
60+
}
61+
}
62+
} // namespace internal
63+
64+
template <class ConcreteConfig>
65+
class ConfigBase {
66+
public:
67+
template <typename T>
68+
class Entry {
69+
public:
70+
Entry(std::string key, const T& val,
71+
std::function<std::string(const T&)> to_str = internal::DefaultToString<T>,
72+
std::function<T(const std::string&)> from_str = internal::DefaultFromString<T>)
73+
: key_{std::move(key)}, default_{val}, to_str_{to_str}, from_str_{from_str} {}
74+
75+
private:
76+
const std::string key_;
77+
const T default_;
78+
const std::function<std::string(const T&)> to_str_;
79+
const std::function<T(const std::string&)> from_str_;
80+
81+
friend ConfigBase;
82+
friend ConcreteConfig;
83+
84+
public:
85+
const std::string& key() const { return key_; }
86+
87+
const T& value() const { return default_; }
88+
};
89+
90+
template <typename T>
91+
ConfigBase& Set(const Entry<T>& entry, const T& val) {
92+
configs_.emplace(entry.key_, entry.to_str_(val));
93+
return *this;
94+
}
95+
96+
template <typename T>
97+
ConfigBase& Unset(const Entry<T>& entry) {
98+
configs_.erase(entry.key_);
99+
return *this;
100+
}
101+
102+
ConfigBase& Reset() {
103+
configs_.clear();
104+
return *this;
105+
}
106+
107+
template <typename T>
108+
T Get(const Entry<T>& entry) const {
109+
auto iter = configs_.find(entry.key_);
110+
return iter != configs_.cend() ? entry.from_str_(iter->second) : entry.default_;
111+
}
112+
113+
const std::unordered_map<std::string, std::string>& configs() const { return configs_; }
114+
115+
protected:
116+
std::unordered_map<std::string, std::string> configs_;
117+
};
118+
119+
} // 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> kStringConfig{"string_config", "default_value"};
61+
inline static const Entry<int> kIntConfig{"int_config", 25};
62+
inline static const Entry<bool> kBoolConfig{"bool_config", false};
63+
inline static const Entry<TestEnum> kEnumConfig{"enum_config", TestEnum::VALUE1,
64+
EnumToString, StringToEnum};
65+
inline static const Entry<double> kDoubleConfig{"double_config", 3.14};
66+
};
67+
68+
TEST(ConfigTest, BasicOperations) {
69+
TestConfig config;
70+
71+
// Test default values
72+
ASSERT_EQ(config.Get(TestConfig::kStringConfig), std::string("default_value"));
73+
ASSERT_EQ(config.Get(TestConfig::kIntConfig), 25);
74+
ASSERT_EQ(config.Get(TestConfig::kBoolConfig), false);
75+
ASSERT_EQ(config.Get(TestConfig::kEnumConfig), TestEnum::VALUE1);
76+
ASSERT_EQ(config.Get(TestConfig::kDoubleConfig), 3.14);
77+
78+
// Test setting values
79+
config.Set(TestConfig::kStringConfig, std::string("new_value"));
80+
config.Set(TestConfig::kIntConfig, 100);
81+
config.Set(TestConfig::kBoolConfig, true);
82+
config.Set(TestConfig::kEnumConfig, TestEnum::VALUE2);
83+
config.Set(TestConfig::kDoubleConfig, 2.99);
84+
85+
ASSERT_EQ(config.Get(TestConfig::kStringConfig), "new_value");
86+
ASSERT_EQ(config.Get(TestConfig::kIntConfig), 100);
87+
ASSERT_EQ(config.Get(TestConfig::kBoolConfig), true);
88+
ASSERT_EQ(config.Get(TestConfig::kEnumConfig), TestEnum::VALUE2);
89+
ASSERT_EQ(config.Get(TestConfig::kDoubleConfig), 2.99);
90+
91+
// Test unsetting a value
92+
config.Unset(TestConfig::kIntConfig);
93+
config.Unset(TestConfig::kEnumConfig);
94+
config.Unset(TestConfig::kDoubleConfig);
95+
ASSERT_EQ(config.Get(TestConfig::kIntConfig), 25);
96+
ASSERT_EQ(config.Get(TestConfig::kStringConfig), "new_value");
97+
ASSERT_EQ(config.Get(TestConfig::kBoolConfig), true);
98+
ASSERT_EQ(config.Get(TestConfig::kEnumConfig), TestEnum::VALUE1);
99+
ASSERT_EQ(config.Get(TestConfig::kDoubleConfig), 3.14);
100+
101+
// Test resetting all values
102+
config.Reset();
103+
ASSERT_EQ(config.Get(TestConfig::kStringConfig), "default_value");
104+
ASSERT_EQ(config.Get(TestConfig::kIntConfig), 25);
105+
ASSERT_EQ(config.Get(TestConfig::kBoolConfig), false);
106+
ASSERT_EQ(config.Get(TestConfig::kEnumConfig), TestEnum::VALUE1);
107+
ASSERT_EQ(config.Get(TestConfig::kDoubleConfig), 3.14);
108+
}
109+
110+
} // namespace iceberg

0 commit comments

Comments
 (0)