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

Reply via email to