This is an automated email from the ASF dual-hosted git repository.
fokko pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/iceberg-cpp.git
The following commit(s) were added to refs/heads/main by this push:
new 408e1a4 feat: add base config implementation (#92)
408e1a4 is described below
commit 408e1a4792e701d73f7bd1c73185d0bbd31a198a
Author: Ying Cai <[email protected]>
AuthorDate: Mon May 12 15:50:03 2025 +0800
feat: add base config implementation (#92)
introduce a new config class to manage configurable options following
design ideas from Velox
---
src/iceberg/util/config.h | 119 ++++++++++++++++++++++++++++++++++++++++++++++
test/CMakeLists.txt | 2 +-
test/config_test.cc | 110 ++++++++++++++++++++++++++++++++++++++++++
3 files changed, 230 insertions(+), 1 deletion(-)
diff --git a/src/iceberg/util/config.h b/src/iceberg/util/config.h
new file mode 100644
index 0000000..7a3a28b
--- /dev/null
+++ b/src/iceberg/util/config.h
@@ -0,0 +1,119 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+#pragma once
+
+#include <format>
+#include <functional>
+#include <string>
+#include <unordered_map>
+
+#include "iceberg/exception.h"
+
+namespace iceberg {
+namespace internal {
+// Default conversion functions
+template <typename U>
+std::string DefaultToString(const U& val) {
+ if constexpr ((std::is_signed_v<U> && std::is_integral_v<U>) ||
+ std::is_floating_point_v<U>) {
+ return std::to_string(val);
+ } else if constexpr (std::is_same_v<U, bool>) {
+ return val ? "true" : "false";
+ } else if constexpr (std::is_same_v<U, std::string> ||
+ std::is_same_v<U, std::string_view>) {
+ return val;
+ } else {
+ throw IcebergError(
+ std::format("Explicit to_str() is required for {}", typeid(U).name()));
+ }
+}
+
+template <typename U>
+U DefaultFromString(const std::string& val) {
+ if constexpr (std::is_same_v<U, std::string>) {
+ return val;
+ } else if constexpr (std::is_same_v<U, bool>) {
+ return val == "true";
+ } else if constexpr (std::is_signed_v<U> && std::is_integral_v<U>) {
+ return static_cast<U>(std::stoll(val));
+ } else if constexpr (std::is_floating_point_v<U>) {
+ return static_cast<U>(std::stod(val));
+ } else {
+ throw IcebergError(
+ std::format("Explicit from_str() is required for {}",
typeid(U).name()));
+ }
+}
+} // namespace internal
+
+template <class ConcreteConfig>
+class ConfigBase {
+ public:
+ template <typename T>
+ class Entry {
+ public:
+ Entry(std::string key, const T& val,
+ std::function<std::string(const T&)> to_str =
internal::DefaultToString<T>,
+ std::function<T(const std::string&)> from_str =
internal::DefaultFromString<T>)
+ : key_{std::move(key)}, default_{val}, to_str_{to_str},
from_str_{from_str} {}
+
+ private:
+ const std::string key_;
+ const T default_;
+ const std::function<std::string(const T&)> to_str_;
+ const std::function<T(const std::string&)> from_str_;
+
+ friend ConfigBase;
+ friend ConcreteConfig;
+
+ public:
+ const std::string& key() const { return key_; }
+
+ const T& value() const { return default_; }
+ };
+
+ template <typename T>
+ ConfigBase& Set(const Entry<T>& entry, const T& val) {
+ configs_.emplace(entry.key_, entry.to_str_(val));
+ return *this;
+ }
+
+ template <typename T>
+ ConfigBase& Unset(const Entry<T>& entry) {
+ configs_.erase(entry.key_);
+ return *this;
+ }
+
+ ConfigBase& Reset() {
+ configs_.clear();
+ return *this;
+ }
+
+ template <typename T>
+ T Get(const Entry<T>& entry) const {
+ auto iter = configs_.find(entry.key_);
+ return iter != configs_.cend() ? entry.from_str_(iter->second) :
entry.default_;
+ }
+
+ const std::unordered_map<std::string, std::string>& configs() const { return
configs_; }
+
+ protected:
+ std::unordered_map<std::string, std::string> configs_;
+};
+
+} // namespace iceberg
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
index bf8caa0..3fc3152 100644
--- a/test/CMakeLists.txt
+++ b/test/CMakeLists.txt
@@ -57,7 +57,7 @@ target_link_libraries(json_serde_test PRIVATE iceberg_static
GTest::gtest_main
add_test(NAME json_serde_test COMMAND json_serde_test)
add_executable(util_test)
-target_sources(util_test PRIVATE expected_test.cc formatter_test.cc)
+target_sources(util_test PRIVATE expected_test.cc formatter_test.cc
config_test.cc)
target_link_libraries(util_test PRIVATE iceberg_static GTest::gtest_main
GTest::gmock)
add_test(NAME util_test COMMAND util_test)
diff --git a/test/config_test.cc b/test/config_test.cc
new file mode 100644
index 0000000..1ee0422
--- /dev/null
+++ b/test/config_test.cc
@@ -0,0 +1,110 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#include <string>
+
+#include <gtest/gtest.h>
+#include <iceberg/util/config.h>
+
+namespace iceberg {
+
+enum class TestEnum { VALUE1, VALUE2, VALUE3 };
+
+std::string EnumToString(const TestEnum& val) {
+ switch (val) {
+ case TestEnum::VALUE1:
+ return "VALUE1";
+ case TestEnum::VALUE2:
+ return "VALUE2";
+ case TestEnum::VALUE3:
+ return "VALUE3";
+ default:
+ throw std::runtime_error("Invalid enum value");
+ }
+}
+
+TestEnum StringToEnum(const std::string& val) {
+ if (val == "VALUE1") {
+ return TestEnum::VALUE1;
+ } else if (val == "VALUE2") {
+ return TestEnum::VALUE2;
+ } else if (val == "VALUE3") {
+ return TestEnum::VALUE3;
+ } else {
+ throw std::runtime_error("Invalid enum string");
+ }
+}
+
+// Define a concrete config class for testing
+class TestConfig : public ConfigBase<TestConfig> {
+ public:
+ template <typename T>
+ using Entry = const ConfigBase<TestConfig>::Entry<T>;
+
+ inline static const Entry<std::string> kStringConfig{"string_config",
"default_value"};
+ inline static const Entry<int> kIntConfig{"int_config", 25};
+ inline static const Entry<bool> kBoolConfig{"bool_config", false};
+ inline static const Entry<TestEnum> kEnumConfig{"enum_config",
TestEnum::VALUE1,
+ EnumToString, StringToEnum};
+ inline static const Entry<double> kDoubleConfig{"double_config", 3.14};
+};
+
+TEST(ConfigTest, BasicOperations) {
+ TestConfig config;
+
+ // Test default values
+ ASSERT_EQ(config.Get(TestConfig::kStringConfig),
std::string("default_value"));
+ ASSERT_EQ(config.Get(TestConfig::kIntConfig), 25);
+ ASSERT_EQ(config.Get(TestConfig::kBoolConfig), false);
+ ASSERT_EQ(config.Get(TestConfig::kEnumConfig), TestEnum::VALUE1);
+ ASSERT_EQ(config.Get(TestConfig::kDoubleConfig), 3.14);
+
+ // Test setting values
+ config.Set(TestConfig::kStringConfig, std::string("new_value"));
+ config.Set(TestConfig::kIntConfig, 100);
+ config.Set(TestConfig::kBoolConfig, true);
+ config.Set(TestConfig::kEnumConfig, TestEnum::VALUE2);
+ config.Set(TestConfig::kDoubleConfig, 2.99);
+
+ ASSERT_EQ(config.Get(TestConfig::kStringConfig), "new_value");
+ ASSERT_EQ(config.Get(TestConfig::kIntConfig), 100);
+ ASSERT_EQ(config.Get(TestConfig::kBoolConfig), true);
+ ASSERT_EQ(config.Get(TestConfig::kEnumConfig), TestEnum::VALUE2);
+ ASSERT_EQ(config.Get(TestConfig::kDoubleConfig), 2.99);
+
+ // Test unsetting a value
+ config.Unset(TestConfig::kIntConfig);
+ config.Unset(TestConfig::kEnumConfig);
+ config.Unset(TestConfig::kDoubleConfig);
+ ASSERT_EQ(config.Get(TestConfig::kIntConfig), 25);
+ ASSERT_EQ(config.Get(TestConfig::kStringConfig), "new_value");
+ ASSERT_EQ(config.Get(TestConfig::kBoolConfig), true);
+ ASSERT_EQ(config.Get(TestConfig::kEnumConfig), TestEnum::VALUE1);
+ ASSERT_EQ(config.Get(TestConfig::kDoubleConfig), 3.14);
+
+ // Test resetting all values
+ config.Reset();
+ ASSERT_EQ(config.Get(TestConfig::kStringConfig), "default_value");
+ ASSERT_EQ(config.Get(TestConfig::kIntConfig), 25);
+ ASSERT_EQ(config.Get(TestConfig::kBoolConfig), false);
+ ASSERT_EQ(config.Get(TestConfig::kEnumConfig), TestEnum::VALUE1);
+ ASSERT_EQ(config.Get(TestConfig::kDoubleConfig), 3.14);
+}
+
+} // namespace iceberg