This is an automated email from the ASF dual-hosted git repository.

SteNicholas pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/paimon-cpp.git


The following commit(s) were added to refs/heads/main by this push:
     new 4027601e refactor(utils): consolidate string and option handling (#249)
4027601e is described below

commit 4027601e67f4271d5efd8ebe278efd66f2e1bee0
Author: lxy <[email protected]>
AuthorDate: Thu Aug 27 14:55:03 2026 +0800

    refactor(utils): consolidate string and option handling (#249)
---
 .../common/data/variant/variant_access_utils.cpp   |   3 +-
 src/paimon/common/types/data_type_json_parser.cpp  |   6 +-
 src/paimon/common/utils/options_utils.h            |  20 +-
 src/paimon/common/utils/options_utils_test.cpp     |  12 +-
 src/paimon/common/utils/string_utils.cpp           |  57 ++++-
 src/paimon/common/utils/string_utils.h             |  31 +--
 src/paimon/common/utils/string_utils_test.cpp      |  32 +++
 src/paimon/core/core_options.cpp                   | 281 +++++++++------------
 .../compact/lookup_merge_tree_compact_rewriter.cpp |  11 +-
 .../compact/merge_tree_compact_rewriter.cpp        |  11 +-
 src/paimon/core/mergetree/lookup_levels.cpp        |  11 +-
 .../operation/append_only_file_store_write.cpp     |   8 +-
 .../commit/sequence_snapshot_properties.cpp        |  18 +-
 .../commit/sequence_snapshot_properties_test.cpp   |   2 +-
 .../core/postpone/postpone_bucket_writer.cpp       |  18 +-
 src/paimon/core/schema/schema_validation.cpp       |   2 +-
 src/paimon/core/schema/schema_validation_test.cpp  |   6 +
 .../core/table/system/global_system_tables.cpp     |   7 +-
 src/paimon/format/orc/orc_format_writer.cpp        |  24 +-
 src/paimon/format/parquet/parquet_format_defs.h    |   2 +-
 src/paimon/fs/local/local_file.cpp                 |   2 +-
 src/paimon/fs/local/local_file_test.cpp            |   5 +
 src/paimon/fs/s3/s3_file_system.cpp                |  12 +-
 src/paimon/global_index/lucene/jieba_analyzer.cpp  |   6 +-
 src/paimon/rest/dlf_auth.cpp                       |  25 +-
 src/paimon/rest/rest_api.cpp                       |   7 +-
 src/paimon/rest/rest_auth.cpp                      |  15 +-
 src/paimon/rest/rest_catalog.cpp                   |   2 +-
 src/paimon/rest/rest_http_client.cpp               |   3 +-
 src/paimon/rest/rest_util.cpp                      |   9 +-
 30 files changed, 313 insertions(+), 335 deletions(-)

diff --git a/src/paimon/common/data/variant/variant_access_utils.cpp 
b/src/paimon/common/data/variant/variant_access_utils.cpp
index 78180d1c..0238c086 100644
--- a/src/paimon/common/data/variant/variant_access_utils.cpp
+++ b/src/paimon/common/data/variant/variant_access_utils.cpp
@@ -26,6 +26,7 @@
 #include "fmt/format.h"
 #include "paimon/common/data/variant/variant_defs.h"
 #include "paimon/common/types/data_field.h"
+#include "paimon/common/utils/string_utils.h"
 
 namespace paimon {
 
@@ -61,7 +62,7 @@ std::vector<std::string> SplitDescription(const std::string& 
description) {
 }
 
 bool HasAccessDescription(const std::shared_ptr<arrow::Field>& field) {
-    return GetDescription(field).rfind(VariantAccessUtils::kMetadataKey, 0) == 
0;
+    return StringUtils::StartsWith(GetDescription(field), 
VariantAccessUtils::kMetadataKey);
 }
 
 }  // namespace
diff --git a/src/paimon/common/types/data_type_json_parser.cpp 
b/src/paimon/common/types/data_type_json_parser.cpp
index e95582a1..d04e5ade 100644
--- a/src/paimon/common/types/data_type_json_parser.cpp
+++ b/src/paimon/common/types/data_type_json_parser.cpp
@@ -19,7 +19,6 @@
 
 #include "paimon/common/types/data_type_json_parser.h"
 
-#include <algorithm>
 #include <cctype>
 #include <cstddef>
 #include <cstdint>
@@ -331,10 +330,7 @@ std::vector<Token> Tokenize(const std::string& chars) {
                 builder.clear();
                 cursor = ConsumeIdentifier(chars, cursor, builder);
                 auto token = builder.str();
-                auto normalized_token = token;
-                std::transform(normalized_token.begin(), 
normalized_token.end(),
-                               normalized_token.begin(),
-                               [](unsigned char c) { return std::toupper(c); 
});
+                std::string normalized_token = StringUtils::ToUpperCase(token);
                 if (Keywords().find(normalized_token) != Keywords().end()) {
                     tokens.emplace_back(TokenType::KEYWORD, cursor, 
normalized_token);
                 } else {
diff --git a/src/paimon/common/utils/options_utils.h 
b/src/paimon/common/utils/options_utils.h
index c2014007..08b09ad2 100644
--- a/src/paimon/common/utils/options_utils.h
+++ b/src/paimon/common/utils/options_utils.h
@@ -89,13 +89,29 @@ class OptionsUtils {
         return value.status();
     }
 
+    static Result<std::string> GetNonEmptyValueFromMap(
+        const std::map<std::string, std::string>& key_value_map, const 
std::string& key) {
+        Result<std::string> value = 
GetValueFromMap<std::string>(key_value_map, key);
+        if (!value.ok()) {
+            return value.status();
+        }
+        if (value.value().empty()) {
+            return Status::Invalid(fmt::format("value for key {} must not be 
empty", key));
+        }
+        return value.value();
+    }
+
     /// Fetch options with specific prefix and remove prefix for key.
+    /// @param prefix Prefix used to select options and removed from the 
returned keys.
+    /// @param options Options to select from.
+    /// @return Options whose keys start with and are longer than `prefix`, 
with the prefix removed
+    /// from each key.
     static std::map<std::string, std::string> FetchOptionsWithPrefix(
         const std::string& prefix, const std::map<std::string, std::string>& 
options) {
         std::map<std::string, std::string> options_with_prefix;
-        int64_t prefix_len = prefix.size();
+        const std::string::size_type prefix_len = prefix.size();
         for (const auto& [key, value] : options) {
-            if (StringUtils::StartsWith(key, prefix)) {
+            if (key.size() > prefix_len && StringUtils::StartsWith(key, 
prefix)) {
                 options_with_prefix[key.substr(prefix_len)] = value;
             }
         }
diff --git a/src/paimon/common/utils/options_utils_test.cpp 
b/src/paimon/common/utils/options_utils_test.cpp
index d4641184..61e52087 100644
--- a/src/paimon/common/utils/options_utils_test.cpp
+++ b/src/paimon/common/utils/options_utils_test.cpp
@@ -84,9 +84,19 @@ TEST(OptionsUtilsTest, TestGetOptionalValueFromMap) {
 }
 
 TEST(OptionsUtilsTest, TestFetchOptionsWithPrefix) {
-    std::map<std::string, std::string> options = {{"key1", "value1"}, 
{"test.key2", "value2"}};
+    std::map<std::string, std::string> options = {
+        {"key1", "value1"}, {"test.", "empty-key"}, {"test.key2", "value2"}};
     auto new_options = OptionsUtils::FetchOptionsWithPrefix("test.", options);
     std::map<std::string, std::string> expected = {{"key2", "value2"}};
     ASSERT_EQ(expected, new_options);
 }
+
+TEST(OptionsUtilsTest, TestGetNonEmptyValueFromMap) {
+    std::map<std::string, std::string> options = {{"present", "value"}, 
{"empty", ""}};
+    ASSERT_OK_AND_ASSIGN(std::string value,
+                         OptionsUtils::GetNonEmptyValueFromMap(options, 
"present"));
+    ASSERT_EQ("value", value);
+    ASSERT_TRUE(OptionsUtils::GetNonEmptyValueFromMap(options, 
"missing").status().IsNotExist());
+    ASSERT_TRUE(OptionsUtils::GetNonEmptyValueFromMap(options, 
"empty").status().IsInvalid());
+}
 }  // namespace paimon::test
diff --git a/src/paimon/common/utils/string_utils.cpp 
b/src/paimon/common/utils/string_utils.cpp
index 5b405895..869e184d 100644
--- a/src/paimon/common/utils/string_utils.cpp
+++ b/src/paimon/common/utils/string_utils.cpp
@@ -30,8 +30,28 @@
 #include "paimon/status.h"
 
 namespace paimon {
+namespace {
+
+bool IsTrimCharacter(unsigned char c) {
+    // Match the characters removed by Java String::trim for the ASCII strings 
handled here.
+    return c <= 0x20;
+}
+
+char ToAsciiLower(unsigned char c) {
+    return c >= 'A' && c <= 'Z' ? static_cast<char>(c + ('a' - 'A')) : 
static_cast<char>(c);
+}
+
+char ToAsciiUpper(unsigned char c) {
+    return c >= 'a' && c <= 'z' ? static_cast<char>(c - ('a' - 'A')) : 
static_cast<char>(c);
+}
+
+}  // namespace
+
 std::string StringUtils::Replace(const std::string& text, const std::string& 
search_string,
                                  const std::string& replacement, int32_t max) {
+    if (text.empty() || search_string.empty() || max == 0) {
+        return text;
+    }
     std::string str = text;
     size_t pos = str.find(search_string);
     int32_t count = 0;
@@ -45,6 +65,9 @@ std::string StringUtils::Replace(const std::string& text, 
const std::string& sea
 
 std::string StringUtils::ReplaceLast(const std::string& text, const 
std::string& old_str,
                                      const std::string& new_str) {
+    if (text.empty() || old_str.empty()) {
+        return text;
+    }
     std::string str = text;
     size_t pos = str.rfind(old_str);
     if (pos != std::string::npos) {
@@ -54,7 +77,8 @@ std::string StringUtils::ReplaceLast(const std::string& text, 
const std::string&
 }
 
 bool StringUtils::StartsWith(const std::string& str, const std::string& 
prefix, size_t start_pos) {
-    return (str.size() >= prefix.size()) && (str.compare(start_pos, 
prefix.size(), prefix) == 0);
+    return start_pos <= str.size() && prefix.size() <= str.size() - start_pos 
&&
+           str.compare(start_pos, prefix.size(), prefix) == 0;
 }
 bool StringUtils::EndsWith(const std::string& str, const std::string& suffix) {
     size_t s1 = str.size();
@@ -74,26 +98,45 @@ bool StringUtils::IsNullOrWhitespaceOnly(const std::string& 
str) {
 }
 
 void StringUtils::Trim(std::string* str) {
-    str->erase(str->find_last_not_of(' ') + 1);
-    str->erase(0, str->find_first_not_of(' '));
+    auto first = std::find_if_not(str->begin(), str->end(),
+                                  [](unsigned char c) { return 
IsTrimCharacter(c); });
+    auto last = std::find_if_not(str->rbegin(), str->rend(), [](unsigned char 
c) {
+                    return IsTrimCharacter(c);
+                }).base();
+    if (first >= last) {
+        str->clear();
+        return;
+    }
+    *str = std::string(first, last);
 }
 
 std::string StringUtils::ToLowerCase(const std::string& str) {
     std::string result;
     result.reserve(str.length());
-    std::transform(str.begin(), str.end(), std::back_inserter(result),
-                   [](unsigned char c) { return std::tolower(c); });
+    std::transform(str.begin(), str.end(), std::back_inserter(result), 
ToAsciiLower);
     return result;
 }
 
 std::string StringUtils::ToUpperCase(const std::string& str) {
     std::string result;
     result.reserve(str.length());
-    std::transform(str.begin(), str.end(), std::back_inserter(result),
-                   [](unsigned char c) { return std::toupper(c); });
+    std::transform(str.begin(), str.end(), std::back_inserter(result), 
ToAsciiUpper);
     return result;
 }
 
+bool StringUtils::EqualsIgnoreCase(const std::string& left, const std::string& 
right) {
+    if (left.size() != right.size()) {
+        return false;
+    }
+    for (size_t i = 0; i < left.size(); ++i) {
+        if (ToAsciiLower(static_cast<unsigned char>(left[i])) !=
+            ToAsciiLower(static_cast<unsigned char>(right[i]))) {
+            return false;
+        }
+    }
+    return true;
+}
+
 std::vector<std::string> StringUtils::Split(const std::string& text, const 
std::string& sep_str,
                                             bool ignore_empty) {
     std::vector<std::string> vec;
diff --git a/src/paimon/common/utils/string_utils.h 
b/src/paimon/common/utils/string_utils.h
index 3c0906e2..7681a8d9 100644
--- a/src/paimon/common/utils/string_utils.h
+++ b/src/paimon/common/utils/string_utils.h
@@ -50,24 +50,18 @@ class PAIMON_EXPORT StringUtils {
  public:
     /// Replaces all occurrences of a string within another string.
     ///
-    /// A `null` reference passed to this method is a no-op.
-    ///
     /// <pre>
-    /// StringUtils::Replace(null, *, *)        = null
     /// StringUtils::Replace("", *, *)          = ""
-    /// StringUtils::Replace("any", null, *)    = "any"
-    /// StringUtils::Replace("any", *, null)    = "any"
     /// StringUtils::Replace("any", "", *)      = "any"
-    /// StringUtils::Replace("aba", "a", null)  = "aba"
     /// StringUtils::Replace("aba", "a", "")    = "b"
     /// StringUtils::Replace("aba", "a", "z")   = "zbz"
     /// </pre>
     ///
     /// @see #replace(string text, string search_string, string replacement, 
int max)
-    /// @param text text to search and replace in, may be null
-    /// @param search_string the String to search for, may be null
-    /// @param replacement the String to replace it with, may be null
-    /// @return the text with any replacements processed, `null` if null 
string input
+    /// @param text text to search and replace in
+    /// @param search_string the String to search for
+    /// @param replacement the String to replace it with
+    /// @return the text with any replacements processed
     static std::string Replace(const std::string& text, const std::string& 
search_string,
                                const std::string& replacement) {
         return Replace(text, search_string, replacement, -1);
@@ -76,16 +70,10 @@ class PAIMON_EXPORT StringUtils {
     /// Replaces a String with another String inside a larger String, for the 
first `max` values of
     /// the search String.
     ///
-    /// A `null` reference passed to this method is a no-op.
-    ///
     /// <pre>
-    /// StringUtils::Replace(null, *, *, *)         = null
     /// StringUtils::Replace("", *, *, *)           = ""
-    /// StringUtils::Replace("any", null, *, *)     = "any"
-    /// StringUtils::Replace("any", *, null, *)     = "any"
     /// StringUtils::Replace("any", "", *, *)       = "any"
     /// StringUtils::Replace("any", *, *, 0)        = "any"
-    /// StringUtils::Replace("abaa", "a", null, -1) = "abaa"
     /// StringUtils::Replace("abaa", "a", "", -1)   = "b"
     /// StringUtils::Replace("abaa", "a", "z", 0)   = "abaa"
     /// StringUtils::Replace("abaa", "a", "z", 1)   = "zbaa"
@@ -93,11 +81,11 @@ class PAIMON_EXPORT StringUtils {
     /// StringUtils::Replace("abaa", "a", "z", -1)  = "zbzz"
     /// </pre>
     ///
-    /// @param text text to search and replace in, may be null
-    /// @param search_string the String to search for, may be null
-    /// @param replacement the String to replace it with, may be null
+    /// @param text text to search and replace in
+    /// @param search_string the String to search for
+    /// @param replacement the String to replace it with
     /// @param max maximum number of values to replace, or `-1` if no maximum
-    /// @return the text with any replacements processed, `null` if null 
string input
+    /// @return the text with any replacements processed
     static std::string Replace(const std::string& text, const std::string& 
search_string,
                                const std::string& replacement, int32_t max);
 
@@ -115,6 +103,9 @@ class PAIMON_EXPORT StringUtils {
     static std::string ToLowerCase(const std::string& str);
     static std::string ToUpperCase(const std::string& str);
 
+    /// Compares two strings using ASCII case folding.
+    static bool EqualsIgnoreCase(const std::string& left, const std::string& 
right);
+
     template <typename T>
     static std::string VectorToString(const std::vector<T>& vec) {
         std::vector<std::string> strs;
diff --git a/src/paimon/common/utils/string_utils_test.cpp 
b/src/paimon/common/utils/string_utils_test.cpp
index 11c3e000..a4f23078 100644
--- a/src/paimon/common/utils/string_utils_test.cpp
+++ b/src/paimon/common/utils/string_utils_test.cpp
@@ -73,6 +73,8 @@ void StringUtilsTest::CheckOverFlowAndUnderFlow(const 
std::string& over_flow,
 }
 
 TEST_F(StringUtilsTest, TestReplaceAll) {
+    ASSERT_EQ("abc", StringUtils::Replace("abc", "", "x"));
+    ASSERT_EQ("", StringUtils::Replace("", "a", "b"));
     {
         std::string origin = "how is is you";
         std::string expect = "how are are you";
@@ -118,6 +120,8 @@ TEST_F(StringUtilsTest, TestReplaceAll) {
 }
 
 TEST_F(StringUtilsTest, TestReplaceLast) {
+    ASSERT_EQ("abc", StringUtils::ReplaceLast("abc", "", "x"));
+    ASSERT_EQ("", StringUtils::ReplaceLast("", "a", "b"));
     {
         std::string origin = "a/b/c//";
         std::string expect = "a/b/c/_";
@@ -140,6 +144,7 @@ TEST_F(StringUtilsTest, TestReplaceLast) {
 }
 
 TEST_F(StringUtilsTest, TestReplaceWithMaxCount) {
+    ASSERT_EQ("abc", StringUtils::Replace("abc", "a", "b", 0));
     {
         std::string origin = "how is is you";
         std::string expect = "how are is you";
@@ -236,6 +241,13 @@ TEST_F(StringUtilsTest, TestToUpperCase) {
     }
 }
 
+TEST_F(StringUtilsTest, TestEqualsIgnoreCase) {
+    ASSERT_TRUE(StringUtils::EqualsIgnoreCase("", ""));
+    ASSERT_TRUE(StringUtils::EqualsIgnoreCase("AbC-123", "aBc-123"));
+    ASSERT_FALSE(StringUtils::EqualsIgnoreCase("abc", "abcd"));
+    ASSERT_FALSE(StringUtils::EqualsIgnoreCase("abc", "abx"));
+}
+
 TEST_F(StringUtilsTest, TestStartsWith) {
     {
         std::string str = "abcde";
@@ -261,6 +273,26 @@ TEST_F(StringUtilsTest, TestStartsWith) {
         std::string str = "";
         ASSERT_TRUE(StringUtils::StartsWith(str, ""));
     }
+    {
+        std::string str = "abc";
+        ASSERT_TRUE(StringUtils::StartsWith(str, "", /*start_pos=*/3));
+        ASSERT_FALSE(StringUtils::StartsWith(str, "", /*start_pos=*/4));
+        ASSERT_FALSE(StringUtils::StartsWith(str, "a", /*start_pos=*/4));
+    }
+}
+
+TEST_F(StringUtilsTest, TestTrim) {
+    std::string value = " \tabc\r\n";
+    StringUtils::Trim(&value);
+    ASSERT_EQ("abc", value);
+
+    value = "\t\r\n";
+    StringUtils::Trim(&value);
+    ASSERT_TRUE(value.empty());
+
+    value.clear();
+    StringUtils::Trim(&value);
+    ASSERT_TRUE(value.empty());
 }
 TEST_F(StringUtilsTest, TestEndsWith) {
     {
diff --git a/src/paimon/core/core_options.cpp b/src/paimon/core/core_options.cpp
index 6320ec57..a5b32b09 100644
--- a/src/paimon/core/core_options.cpp
+++ b/src/paimon/core/core_options.cpp
@@ -19,6 +19,7 @@
 #include "paimon/core/core_options.h"
 
 #include <cstring>
+#include <initializer_list>
 #include <limits>
 #include <memory>
 #include <optional>
@@ -51,14 +52,9 @@ class ConfigParser {
     // Parse basic type configurations
     template <typename T>
     Status Parse(const std::string& key, T* value) const {
-        auto iter = config_map_.find(key);
-        if (iter != config_map_.end()) {
-            auto result = StringUtils::StringToValue<T>(iter->second);
-            if (result) {
-                *value = result.value();
-                return Status::OK();
-            }
-            return Status::Invalid(fmt::format("Invalid Config [{}: {}]", key, 
iter->second));
+        PAIMON_ASSIGN_OR_RAISE(std::optional<T> parsed_value, 
GetOptionalValue<T>(key));
+        if (parsed_value) {
+            *value = parsed_value.value();
         }
         return Status::OK();  // Return success even if the configuration does 
not exist
     }
@@ -66,14 +62,9 @@ class ConfigParser {
     // Parse optional basic type configurations
     template <typename T>
     Status Parse(const std::string& key, std::optional<T>* value) const {
-        auto iter = config_map_.find(key);
-        if (iter != config_map_.end()) {
-            auto result = StringUtils::StringToValue<T>(iter->second);
-            if (result) {
-                *value = result.value();
-                return Status::OK();
-            }
-            return Status::Invalid(fmt::format("Invalid Config [{}: {}]", key, 
iter->second));
+        PAIMON_ASSIGN_OR_RAISE(std::optional<T> parsed_value, 
GetOptionalValue<T>(key));
+        if (parsed_value) {
+            *value = parsed_value.value();
         }
         return Status::OK();  // Return success even if the configuration does 
not exist
     }
@@ -82,23 +73,26 @@ class ConfigParser {
     template <typename T>
     Status ParseList(const std::string& key, const std::string& delimiter, 
std::vector<T>* list,
                      bool need_trim = false) const {
-        auto iter = config_map_.find(key);
-        if (iter != config_map_.end()) {
-            auto value_str_vec = StringUtils::Split(iter->second, delimiter, 
/*ignore_empty=*/true);
-            for (auto& value_str : value_str_vec) {
-                if (need_trim) {
-                    StringUtils::Trim(&value_str);
-                }
-                if constexpr (std::is_same_v<T, std::string>) {
-                    list->emplace_back(value_str);
-                } else {
-                    auto value = StringUtils::StringToValue<T>(value_str);
-                    if (!value) {
-                        return Status::Invalid(
-                            fmt::format("Invalid Config [{}: {}]", key, 
iter->second));
-                    }
-                    list->emplace_back(value.value());
+        PAIMON_ASSIGN_OR_RAISE(std::optional<std::string> config_value,
+                               GetOptionalValue<std::string>(key));
+        if (!config_value) {
+            return Status::OK();
+        }
+        auto value_str_vec =
+            StringUtils::Split(config_value.value(), delimiter, 
/*ignore_empty=*/true);
+        for (auto& value_str : value_str_vec) {
+            if (need_trim) {
+                StringUtils::Trim(&value_str);
+            }
+            if constexpr (std::is_same_v<T, std::string>) {
+                list->emplace_back(value_str);
+            } else {
+                auto value = StringUtils::StringToValue<T>(value_str);
+                if (!value) {
+                    return Status::Invalid(
+                        fmt::format("Invalid Config [{}: {}]", key, 
config_value.value()));
                 }
+                list->emplace_back(value.value());
             }
         }
         return Status::OK();  // Return success even if the configuration does 
not exist
@@ -109,9 +103,10 @@ class ConfigParser {
     Status ParseMemorySize(const std::string& key, T* value) const {
         static_assert(std::is_same_v<T, int64_t> || std::is_same_v<T, 
std::optional<int64_t>>,
                       "ParseMemorySize only supports int64_t and 
std::optional<int64_t>");
-        auto iter = config_map_.find(key);
-        if (iter != config_map_.end()) {
-            PAIMON_ASSIGN_OR_RAISE(*value, 
MemorySize::ParseBytes(iter->second));
+        PAIMON_ASSIGN_OR_RAISE(std::optional<std::string> config_value,
+                               GetOptionalValue<std::string>(key));
+        if (config_value) {
+            PAIMON_ASSIGN_OR_RAISE(*value, 
MemorySize::ParseBytes(config_value.value()));
         }
         return Status::OK();
     }
@@ -121,9 +116,10 @@ class ConfigParser {
     Status ParseTimeDuration(const std::string& key, T* value) const {
         static_assert(std::is_same_v<T, int64_t> || std::is_same_v<T, 
std::optional<int64_t>>,
                       "ParseTimeDuration only supports int64_t and 
std::optional<int64_t>");
-        auto iter = config_map_.find(key);
-        if (iter != config_map_.end()) {
-            PAIMON_ASSIGN_OR_RAISE(*value, TimeDuration::Parse(iter->second));
+        PAIMON_ASSIGN_OR_RAISE(std::optional<std::string> config_value,
+                               GetOptionalValue<std::string>(key));
+        if (config_value) {
+            PAIMON_ASSIGN_OR_RAISE(*value, 
TimeDuration::Parse(config_value.value()));
         }
         return Status::OK();
     }
@@ -132,14 +128,10 @@ class ConfigParser {
     template <typename Factory, typename ObjectType>
     Status ParseObject(const std::string& key, const std::string& 
default_identifier,
                        std::shared_ptr<ObjectType>* value) const {
-        auto iter = config_map_.find(key);
-        if (iter != config_map_.end()) {
-            std::string normalized_value = 
StringUtils::ToLowerCase(iter->second);
-            PAIMON_ASSIGN_OR_RAISE(*value, Factory::Get(normalized_value, 
config_map_));
-        } else {
-            PAIMON_ASSIGN_OR_RAISE(
-                *value, 
Factory::Get(StringUtils::ToLowerCase(default_identifier), config_map_));
-        }
+        PAIMON_ASSIGN_OR_RAISE(std::string identifier, 
OptionsUtils::GetValueFromMap<std::string>(
+                                                           config_map_, key, 
default_identifier));
+        PAIMON_ASSIGN_OR_RAISE(*value,
+                               
Factory::Get(StringUtils::ToLowerCase(identifier), config_map_));
         return Status::OK();
     }
 
@@ -152,11 +144,10 @@ class ConfigParser {
             *value = specified_file_system;
             return Status::OK();
         }
-        std::string default_fs_identifier = "local";
-        auto iter = config_map_.find(Options::FILE_SYSTEM);
-        if (iter != config_map_.end()) {
-            default_fs_identifier = StringUtils::ToLowerCase(iter->second);
-        }
+        PAIMON_ASSIGN_OR_RAISE(
+            std::string default_fs_identifier,
+            OptionsUtils::GetValueFromMap<std::string>(config_map_, 
Options::FILE_SYSTEM, "local"));
+        default_fs_identifier = 
StringUtils::ToLowerCase(default_fs_identifier);
         *value = 
std::make_shared<ResolvingFileSystem>(fs_scheme_to_identifier_map,
                                                        default_fs_identifier, 
config_map_);
         return Status::OK();
@@ -164,151 +155,81 @@ class ConfigParser {
 
     // Parse SortOrder
     Status ParseSortOrder(SortOrder* sort_order) const {
-        auto iter = config_map_.find(Options::SEQUENCE_FIELD_SORT_ORDER);
-        if (iter != config_map_.end()) {
-            std::string str = StringUtils::ToLowerCase(iter->second);
-            if (str == "ascending") {
-                *sort_order = SortOrder::ASCENDING;
-            } else if (str == "descending") {
-                *sort_order = SortOrder::DESCENDING;
-            } else {
-                return Status::Invalid(fmt::format("invalid sort order: {}", 
str));
-            }
-        }
-        return Status::OK();
+        return ParseEnum(
+            Options::SEQUENCE_FIELD_SORT_ORDER,
+            {{"ascending", SortOrder::ASCENDING}, {"descending", 
SortOrder::DESCENDING}},
+            "sort order", sort_order);
     }
 
     // Parse LookupCompactMode
     Status ParseLookupCompactMode(LookupCompactMode* mode) const {
-        auto iter = config_map_.find(Options::LOOKUP_COMPACT);
-        if (iter != config_map_.end()) {
-            std::string str = StringUtils::ToLowerCase(iter->second);
-            if (str == "radical") {
-                *mode = LookupCompactMode::RADICAL;
-            } else if (str == "gentle") {
-                *mode = LookupCompactMode::GENTLE;
-            } else {
-                return Status::Invalid(fmt::format("invalid lookup mode: {}", 
str));
-            }
-        }
-        return Status::OK();
+        return ParseEnum(
+            Options::LOOKUP_COMPACT,
+            {{"radical", LookupCompactMode::RADICAL}, {"gentle", 
LookupCompactMode::GENTLE}},
+            "lookup mode", mode);
     }
 
     // Parse SortEngine
     Status ParseSortEngine(SortEngine* sort_engine) const {
-        auto iter = config_map_.find(Options::SORT_ENGINE);
-        if (iter != config_map_.end()) {
-            std::string str = StringUtils::ToLowerCase(iter->second);
-            if (str == "min-heap") {
-                *sort_engine = SortEngine::MIN_HEAP;
-            } else if (str == "loser-tree") {
-                *sort_engine = SortEngine::LOSER_TREE;
-            } else {
-                return Status::Invalid(fmt::format("invalid sort engine: {}", 
str));
-            }
-        }
-        return Status::OK();
+        return ParseEnum(
+            Options::SORT_ENGINE,
+            {{"min-heap", SortEngine::MIN_HEAP}, {"loser-tree", 
SortEngine::LOSER_TREE}},
+            "sort engine", sort_engine);
     }
 
     // Parse MergeEngine
     Status ParseMergeEngine(MergeEngine* merge_engine) const {
-        auto iter = config_map_.find(Options::MERGE_ENGINE);
-        if (iter != config_map_.end()) {
-            std::string str = StringUtils::ToLowerCase(iter->second);
-            if (str == "deduplicate") {
-                *merge_engine = MergeEngine::DEDUPLICATE;
-            } else if (str == "partial-update") {
-                *merge_engine = MergeEngine::PARTIAL_UPDATE;
-            } else if (str == "aggregation") {
-                *merge_engine = MergeEngine::AGGREGATE;
-            } else if (str == "first-row") {
-                *merge_engine = MergeEngine::FIRST_ROW;
-            } else {
-                return Status::Invalid(fmt::format("invalid merge engine: {}", 
str));
-            }
-        }
-        return Status::OK();
+        return ParseEnum(Options::MERGE_ENGINE,
+                         {{"deduplicate", MergeEngine::DEDUPLICATE},
+                          {"partial-update", MergeEngine::PARTIAL_UPDATE},
+                          {"aggregation", MergeEngine::AGGREGATE},
+                          {"first-row", MergeEngine::FIRST_ROW}},
+                         "merge engine", merge_engine);
     }
 
     // Parse VariantShreddingInferenceMode
     Status ParseVariantShreddingInferenceMode(VariantShreddingInferenceMode* 
inference_mode) const {
-        auto iter = 
config_map_.find(Options::VARIANT_SHREDDING_INFERENCE_MODE);
-        if (iter != config_map_.end()) {
-            std::string str = StringUtils::ToLowerCase(iter->second);
-            if (str == "per-file") {
-                *inference_mode = VariantShreddingInferenceMode::PER_FILE;
-            } else if (str == "adaptive") {
-                *inference_mode = VariantShreddingInferenceMode::ADAPTIVE;
-            } else {
-                return Status::Invalid(
-                    fmt::format("invalid variant shredding inference mode: 
{}", str));
-            }
-        }
-        return Status::OK();
+        return ParseEnum(Options::VARIANT_SHREDDING_INFERENCE_MODE,
+                         {{"per-file", 
VariantShreddingInferenceMode::PER_FILE},
+                          {"adaptive", 
VariantShreddingInferenceMode::ADAPTIVE}},
+                         "variant shredding inference mode", inference_mode);
     }
 
     // Parse ChangelogProducer
     Status ParseChangelogProducer(ChangelogProducer* changelog_producer) const 
{
-        auto iter = config_map_.find(Options::CHANGELOG_PRODUCER);
-        if (iter != config_map_.end()) {
-            std::string str = StringUtils::ToLowerCase(iter->second);
-            if (str == "none") {
-                *changelog_producer = ChangelogProducer::NONE;
-            } else if (str == "input") {
-                *changelog_producer = ChangelogProducer::INPUT;
-            } else if (str == "full-compaction") {
-                *changelog_producer = ChangelogProducer::FULL_COMPACTION;
-            } else if (str == "lookup") {
-                *changelog_producer = ChangelogProducer::LOOKUP;
-            } else {
-                return Status::Invalid(fmt::format("invalid changelog 
producer: {}", str));
-            }
-        }
-        return Status::OK();
+        return ParseEnum(Options::CHANGELOG_PRODUCER,
+                         {{"none", ChangelogProducer::NONE},
+                          {"input", ChangelogProducer::INPUT},
+                          {"full-compaction", 
ChangelogProducer::FULL_COMPACTION},
+                          {"lookup", ChangelogProducer::LOOKUP}},
+                         "changelog producer", changelog_producer);
     }
 
     // Parse ExternalPathStrategy
     Status ParseExternalPathStrategy(ExternalPathStrategy* 
external_path_strategy) const {
-        auto iter = 
config_map_.find(Options::DATA_FILE_EXTERNAL_PATHS_STRATEGY);
-        if (iter != config_map_.end()) {
-            std::string str = StringUtils::ToLowerCase(iter->second);
-            if (str == "none") {
-                *external_path_strategy = ExternalPathStrategy::NONE;
-            } else if (str == "specific-fs") {
-                *external_path_strategy = ExternalPathStrategy::SPECIFIC_FS;
-            } else if (str == "round-robin") {
-                *external_path_strategy = ExternalPathStrategy::ROUND_ROBIN;
-            } else {
-                return Status::Invalid(fmt::format("invalid external path 
strategy: {}", str));
-            }
-        }
-        return Status::OK();
+        return ParseEnum(Options::DATA_FILE_EXTERNAL_PATHS_STRATEGY,
+                         {{"none", ExternalPathStrategy::NONE},
+                          {"specific-fs", ExternalPathStrategy::SPECIFIC_FS},
+                          {"round-robin", ExternalPathStrategy::ROUND_ROBIN}},
+                         "external path strategy", external_path_strategy);
     }
 
     // Parse BucketFunctionType
     Status ParseBucketFunctionType(BucketFunctionType* bucket_function_type) 
const {
-        auto iter = config_map_.find(Options::BUCKET_FUNCTION_TYPE);
-        if (iter != config_map_.end()) {
-            std::string str = StringUtils::ToLowerCase(iter->second);
-            if (str == "default") {
-                *bucket_function_type = BucketFunctionType::DEFAULT;
-            } else if (str == "mod") {
-                *bucket_function_type = BucketFunctionType::MOD;
-            } else if (str == "hive") {
-                *bucket_function_type = BucketFunctionType::HIVE;
-            } else {
-                return Status::Invalid(fmt::format("invalid bucket function 
type: {}", str));
-            }
-        }
-        return Status::OK();
+        return ParseEnum(Options::BUCKET_FUNCTION_TYPE,
+                         {{"default", BucketFunctionType::DEFAULT},
+                          {"mod", BucketFunctionType::MOD},
+                          {"hive", BucketFunctionType::HIVE}},
+                         "bucket function type", bucket_function_type);
     }
 
     // Parse StartupMode
     Status ParseStartupMode(StartupMode* startup_mode) const {
-        auto iter = config_map_.find(Options::SCAN_MODE);
-        if (iter != config_map_.end()) {
-            std::string str = StringUtils::ToLowerCase(iter->second);
-            PAIMON_ASSIGN_OR_RAISE(*startup_mode, 
StartupMode::FromString(str));
+        PAIMON_ASSIGN_OR_RAISE(std::optional<std::string> value,
+                               
GetOptionalValue<std::string>(Options::SCAN_MODE));
+        if (value) {
+            PAIMON_ASSIGN_OR_RAISE(
+                *startup_mode, 
StartupMode::FromString(StringUtils::ToLowerCase(value.value())));
         }
         return Status::OK();
     }
@@ -372,7 +293,37 @@ class ConfigParser {
     }
 
  private:
-    const std::map<std::string, std::string> config_map_;
+    template <typename T>
+    Status ParseEnum(const std::string& key,
+                     std::initializer_list<std::pair<const char*, T>> 
candidates,
+                     const std::string& error_name, T* value) const {
+        PAIMON_ASSIGN_OR_RAISE(std::optional<std::string> config_value,
+                               GetOptionalValue<std::string>(key));
+        if (!config_value) {
+            return Status::OK();
+        }
+        std::string normalized_value = 
StringUtils::ToLowerCase(config_value.value());
+        for (const auto& [candidate, candidate_value] : candidates) {
+            if (normalized_value == candidate) {
+                *value = candidate_value;
+                return Status::OK();
+            }
+        }
+        return Status::Invalid(fmt::format("invalid {}: {}", error_name, 
normalized_value));
+    }
+
+    template <typename T>
+    Result<std::optional<T>> GetOptionalValue(const std::string& key) const {
+        Result<std::optional<T>> result =
+            OptionsUtils::GetOptionalValueFromMap<T>(config_map_, key);
+        if (!result.ok()) {
+            return Status::Invalid(
+                fmt::format("Invalid Config [{}: {}]", key, 
config_map_.at(key)));
+        }
+        return result.value();
+    }
+
+    const std::map<std::string, std::string>& config_map_;
 };
 
 // Impl is a private implementation of CoreOptions,
diff --git 
a/src/paimon/core/mergetree/compact/lookup_merge_tree_compact_rewriter.cpp 
b/src/paimon/core/mergetree/compact/lookup_merge_tree_compact_rewriter.cpp
index 994b0e3f..071456e4 100644
--- a/src/paimon/core/mergetree/compact/lookup_merge_tree_compact_rewriter.cpp
+++ b/src/paimon/core/mergetree/compact/lookup_merge_tree_compact_rewriter.cpp
@@ -78,14 +78,9 @@ LookupMergeTreeCompactRewriter<T>::Create(
         .WithMemoryPool(pool);
     PAIMON_ASSIGN_OR_RAISE(std::shared_ptr<ReadContext> read_context,
                            read_context_builder.Finish());
-    // TODO(xinyu.lxy): temporarily disabled pre-buffer for parquet, which may 
cause high memory
-    // usage during compaction. Will fix via parquet format refactor.
-    auto new_options = options.ToMap();
-    if (new_options.find("parquet.read.enable-pre-buffer") == 
new_options.end()) {
-        new_options["parquet.read.enable-pre-buffer"] = "false";
-    }
-    PAIMON_ASSIGN_OR_RAISE(std::shared_ptr<InternalReadContext> 
internal_context,
-                           InternalReadContext::Create(read_context, 
table_schema, new_options));
+    PAIMON_ASSIGN_OR_RAISE(
+        std::shared_ptr<InternalReadContext> internal_context,
+        InternalReadContext::Create(read_context, table_schema, 
options.ToMap()));
     PAIMON_ASSIGN_OR_RAISE(
         std::shared_ptr<FileStorePathFactory> path_factory,
         
path_factory_cache->GetOrCreatePathFactory(options.GetFileFormat()->Identifier()));
diff --git a/src/paimon/core/mergetree/compact/merge_tree_compact_rewriter.cpp 
b/src/paimon/core/mergetree/compact/merge_tree_compact_rewriter.cpp
index 1b64be2d..fd7c7cbd 100644
--- a/src/paimon/core/mergetree/compact/merge_tree_compact_rewriter.cpp
+++ b/src/paimon/core/mergetree/compact/merge_tree_compact_rewriter.cpp
@@ -84,14 +84,9 @@ Result<std::unique_ptr<MergeTreeCompactRewriter>> 
MergeTreeCompactRewriter::Crea
         .WithMemoryPool(pool);
     PAIMON_ASSIGN_OR_RAISE(std::shared_ptr<ReadContext> read_context,
                            read_context_builder.Finish());
-    // TODO(xinyu.lxy): temporarily disabled pre-buffer for parquet, which may 
cause high memory
-    // usage during compaction. Will fix via parquet format refactor.
-    auto new_options = options.ToMap();
-    if (new_options.find("parquet.read.enable-pre-buffer") == 
new_options.end()) {
-        new_options["parquet.read.enable-pre-buffer"] = "false";
-    }
-    PAIMON_ASSIGN_OR_RAISE(std::shared_ptr<InternalReadContext> 
internal_context,
-                           InternalReadContext::Create(read_context, 
table_schema, new_options));
+    PAIMON_ASSIGN_OR_RAISE(
+        std::shared_ptr<InternalReadContext> internal_context,
+        InternalReadContext::Create(read_context, table_schema, 
options.ToMap()));
     PAIMON_ASSIGN_OR_RAISE(
         std::shared_ptr<FileStorePathFactory> path_factory,
         
path_factory_cache->GetOrCreatePathFactory(options.GetFileFormat()->Identifier()));
diff --git a/src/paimon/core/mergetree/lookup_levels.cpp 
b/src/paimon/core/mergetree/lookup_levels.cpp
index a931fea1..512f2872 100644
--- a/src/paimon/core/mergetree/lookup_levels.cpp
+++ b/src/paimon/core/mergetree/lookup_levels.cpp
@@ -66,14 +66,9 @@ Result<std::unique_ptr<LookupLevels<T>>> 
LookupLevels<T>::Create(
         .WithMemoryPool(pool);
     PAIMON_ASSIGN_OR_RAISE(std::shared_ptr<ReadContext> read_context,
                            read_context_builder.Finish());
-    // TODO(xinyu.lxy): temporarily disabled pre-buffer for parquet, which may 
cause high memory
-    // usage during compaction. Will fix via parquet format refactor.
-    auto new_options = options.ToMap();
-    if (new_options.find("parquet.read.enable-pre-buffer") == 
new_options.end()) {
-        new_options["parquet.read.enable-pre-buffer"] = "false";
-    }
-    PAIMON_ASSIGN_OR_RAISE(std::shared_ptr<InternalReadContext> 
internal_read_context,
-                           InternalReadContext::Create(read_context, 
table_schema, new_options));
+    PAIMON_ASSIGN_OR_RAISE(
+        std::shared_ptr<InternalReadContext> internal_read_context,
+        InternalReadContext::Create(read_context, table_schema, 
options.ToMap()));
     auto split_read = std::make_unique<RawFileSplitRead>(path_factory, 
internal_read_context, pool,
                                                          
CreateDefaultExecutor());
 
diff --git a/src/paimon/core/operation/append_only_file_store_write.cpp 
b/src/paimon/core/operation/append_only_file_store_write.cpp
index 5d6c2c93..f660093a 100644
--- a/src/paimon/core/operation/append_only_file_store_write.cpp
+++ b/src/paimon/core/operation/append_only_file_store_write.cpp
@@ -290,14 +290,8 @@ Result<std::unique_ptr<BatchReader>> 
AppendOnlyFileStoreWrite::CreateFilesReader
         .WithMemoryPool(pool_);
     PAIMON_ASSIGN_OR_RAISE(std::shared_ptr<ReadContext> read_context, 
context_builder.Finish());
     std::map<std::string, std::string> options = options_.ToMap();
-    // TODO(xinyu.lxy): temporarily disabled pre-buffer for parquet, which may 
cause high
-    // memory usage during compaction. Will fix via parquet format refactor.
-    auto new_options = options;
-    if (new_options.find("parquet.read.enable-pre-buffer") == 
new_options.end()) {
-        new_options["parquet.read.enable-pre-buffer"] = "false";
-    }
     PAIMON_ASSIGN_OR_RAISE(std::shared_ptr<InternalReadContext> 
internal_read_context,
-                           InternalReadContext::Create(read_context, 
table_schema_, new_options));
+                           InternalReadContext::Create(read_context, 
table_schema_, options));
     auto read = std::make_unique<RawFileSplitRead>(file_store_path_factory_, 
internal_read_context,
                                                    pool_, compact_executor_);
 
diff --git a/src/paimon/core/operation/commit/sequence_snapshot_properties.cpp 
b/src/paimon/core/operation/commit/sequence_snapshot_properties.cpp
index 51729b99..512869f0 100644
--- a/src/paimon/core/operation/commit/sequence_snapshot_properties.cpp
+++ b/src/paimon/core/operation/commit/sequence_snapshot_properties.cpp
@@ -24,6 +24,7 @@
 #include <algorithm>
 #include <limits>
 
+#include "paimon/common/utils/string_utils.h"
 #include "paimon/core/manifest/file_kind.h"
 
 namespace paimon {
@@ -40,19 +41,12 @@ Result<std::optional<int64_t>> 
SequenceSnapshotProperties::MaxSequenceNumber(
         return std::optional<int64_t>();
     }
 
-    try {
-        size_t parsed = 0;
-        int64_t value = std::stoll(iter->second, &parsed);
-        if (parsed != iter->second.size()) {
-            return Status::Invalid(
-                fmt::format("Invalid {} value '{}': trailing characters are 
not allowed",
-                            kMaxSequenceNumberKey, iter->second));
-        }
-        return std::optional<int64_t>(value);
-    } catch (const std::exception& e) {
-        return Status::Invalid(fmt::format("Invalid {} value '{}': {}", 
kMaxSequenceNumberKey,
-                                           iter->second, e.what()));
+    std::optional<int64_t> value = 
StringUtils::StringToValue<int64_t>(iter->second);
+    if (!value) {
+        return Status::Invalid(
+            fmt::format("Invalid {} value '{}'", kMaxSequenceNumberKey, 
iter->second));
     }
+    return value;
 }
 
 std::optional<int64_t> SequenceSnapshotProperties::MaxSequenceNumberFromFiles(
diff --git 
a/src/paimon/core/operation/commit/sequence_snapshot_properties_test.cpp 
b/src/paimon/core/operation/commit/sequence_snapshot_properties_test.cpp
index 5975eb1e..ca962e0e 100644
--- a/src/paimon/core/operation/commit/sequence_snapshot_properties_test.cpp
+++ b/src/paimon/core/operation/commit/sequence_snapshot_properties_test.cpp
@@ -115,7 +115,7 @@ TEST_F(SequenceSnapshotPropertiesTest, 
MaxSequenceNumberTrailingCharacters) {
     std::map<std::string, std::string> properties{
         {SequenceSnapshotProperties::kMaxSequenceNumberKey, "123abc"}};
     
ASSERT_NOK_WITH_MSG(SequenceSnapshotProperties::MaxSequenceNumber(MakeSnapshot(properties)),
-                        "trailing characters are not allowed");
+                        "Invalid sequence.generation.max-sequence-number value 
'123abc'");
 }
 
 TEST_F(SequenceSnapshotPropertiesTest, MaxSequenceNumberNotANumber) {
diff --git a/src/paimon/core/postpone/postpone_bucket_writer.cpp 
b/src/paimon/core/postpone/postpone_bucket_writer.cpp
index 47fd3bcb..45bcfd36 100644
--- a/src/paimon/core/postpone/postpone_bucket_writer.cpp
+++ b/src/paimon/core/postpone/postpone_bucket_writer.cpp
@@ -34,7 +34,6 @@
 #include "paimon/common/data/shredding/shredding_write_plan_factories.h"
 #include "paimon/common/metrics/metrics_impl.h"
 #include "paimon/common/table/special_fields.h"
-#include "paimon/common/types/data_field.h"
 #include "paimon/common/types/row_kind.h"
 #include "paimon/common/utils/arrow/mem_utils.h"
 #include "paimon/common/utils/arrow/status_utils.h"
@@ -55,27 +54,12 @@ namespace paimon {
 class InternalRow;
 class MemoryPool;
 
-namespace {
-
-std::shared_ptr<arrow::Schema> BuildPostponeBucketWriteSchema(
-    const std::shared_ptr<arrow::Schema>& value_schema) {
-    arrow::FieldVector target_fields;
-    target_fields.push_back(
-        
DataField::ConvertDataFieldToArrowField(SpecialFields::SequenceNumber()));
-    
target_fields.push_back(DataField::ConvertDataFieldToArrowField(SpecialFields::ValueKind()));
-    target_fields.insert(target_fields.end(), value_schema->fields().begin(),
-                         value_schema->fields().end());
-    return arrow::schema(target_fields);
-}
-
-}  // namespace
-
 Result<std::unique_ptr<PostponeBucketWriter>> PostponeBucketWriter::Create(
     const std::vector<std::string>& trimmed_primary_keys,
     const std::shared_ptr<DataFilePathFactory>& path_factory, int64_t 
schema_id,
     const std::shared_ptr<arrow::Schema>& value_schema, const CoreOptions& 
options,
     const std::shared_ptr<MemoryPool>& pool) {
-    auto write_schema = BuildPostponeBucketWriteSchema(value_schema);
+    auto write_schema = 
SpecialFields::CompleteSequenceAndValueKindField(value_schema);
     return std::unique_ptr<PostponeBucketWriter>(new PostponeBucketWriter(
         trimmed_primary_keys, path_factory, schema_id, value_schema, 
write_schema, options, pool));
 }
diff --git a/src/paimon/core/schema/schema_validation.cpp 
b/src/paimon/core/schema/schema_validation.cpp
index 7342826d..4c7dd2ce 100644
--- a/src/paimon/core/schema/schema_validation.cpp
+++ b/src/paimon/core/schema/schema_validation.cpp
@@ -100,7 +100,7 @@ Status ValidateSharedShreddingFileFormat(const std::string& 
option_key,
 }
 
 Status ValidateVectorFileFormat(const std::string& option_key, const 
std::string& file_format) {
-    if (StringUtils::ToLowerCase(file_format) != "parquet") {
+    if (!StringUtils::EqualsIgnoreCase(file_format, "parquet")) {
         return Status::Invalid(
             fmt::format("VECTOR currently only supports parquet data files, 
but {} is {}.",
                         option_key, file_format));
diff --git a/src/paimon/core/schema/schema_validation_test.cpp 
b/src/paimon/core/schema/schema_validation_test.cpp
index 47603497..57885626 100644
--- a/src/paimon/core/schema/schema_validation_test.cpp
+++ b/src/paimon/core/schema/schema_validation_test.cpp
@@ -56,6 +56,12 @@ TEST(SchemaValidationTest, TestVectorType) {
                                              /*primary_keys=*/{}, 
parquet_options));
     ASSERT_OK(SchemaValidation::ValidateTableSchema(*table_schema));
 
+    parquet_options[Options::FILE_FORMAT] = "PARQUET";
+    ASSERT_OK_AND_ASSIGN(table_schema,
+                         TableSchema::Create(/*schema_id=*/0, schema, 
/*partition_keys=*/{},
+                                             /*primary_keys=*/{}, 
parquet_options));
+    ASSERT_OK(SchemaValidation::ValidateTableSchema(*table_schema));
+
     std::map<std::string, std::string> orc_options = {{Options::BUCKET, "-1"},
                                                       {Options::FILE_FORMAT, 
"orc"}};
     ASSERT_OK_AND_ASSIGN(table_schema,
diff --git a/src/paimon/core/table/system/global_system_tables.cpp 
b/src/paimon/core/table/system/global_system_tables.cpp
index dd1b4e60..6523588e 100644
--- a/src/paimon/core/table/system/global_system_tables.cpp
+++ b/src/paimon/core/table/system/global_system_tables.cpp
@@ -108,11 +108,12 @@ VariantType OptionalStringValue(const 
std::map<std::string, std::string>& option
 
 Result<VariantType> OptionalLongValue(const std::map<std::string, 
std::string>& options,
                                       const std::string& key) {
-    if (options.find(key) == options.end()) {
+    PAIMON_ASSIGN_OR_RAISE(std::optional<int64_t> value,
+                           
OptionsUtils::GetOptionalValueFromMap<int64_t>(options, key));
+    if (!value) {
         return VariantType(NullType());
     }
-    PAIMON_ASSIGN_OR_RAISE(int64_t value, 
OptionsUtils::GetValueFromMap<int64_t>(options, key));
-    return VariantType(value);
+    return VariantType(value.value());
 }
 
 Result<bool> IsEnabled(const GlobalSystemTableRegistryEntry& entry,
diff --git a/src/paimon/format/orc/orc_format_writer.cpp 
b/src/paimon/format/orc/orc_format_writer.cpp
index 1a394ca9..fc2316b2 100644
--- a/src/paimon/format/orc/orc_format_writer.cpp
+++ b/src/paimon/format/orc/orc_format_writer.cpp
@@ -40,7 +40,6 @@
 #include "orc/Writer.hh"
 #include "paimon/common/data/variant/variant_type_utils.h"
 #include "paimon/common/metrics/metrics_impl.h"
-#include "paimon/common/options/memory_size.h"
 #include "paimon/common/utils/arrow/status_utils.h"
 #include "paimon/common/utils/options_utils.h"
 #include "paimon/common/utils/string_utils.h"
@@ -236,18 +235,6 @@ Status OrcFormatWriter::AddMetadata(const 
std::map<std::string, std::string>& me
     return Status::OK();
 }
 
-namespace {
-
-Result<uint64_t> GetMemorySizeOption(const std::map<std::string, std::string>& 
options,
-                                     const std::string& key, uint64_t 
default_value) {
-    PAIMON_ASSIGN_OR_RAISE(std::string value, 
OptionsUtils::GetValueFromMap<std::string>(
-                                                  options, key, 
std::to_string(default_value)));
-    PAIMON_ASSIGN_OR_RAISE(int64_t bytes, MemorySize::ParseBytes(value));
-    return static_cast<uint64_t>(bytes);
-}
-
-}  // namespace
-
 Result<::orc::WriterOptions> OrcFormatWriter::PrepareWriterOptions(
     const std::map<std::string, std::string>& options, const std::string& 
file_compression,
     const std::shared_ptr<arrow::DataType>& data_type) {
@@ -261,15 +248,16 @@ Result<::orc::WriterOptions> 
OrcFormatWriter::PrepareWriterOptions(
         }
     }
     ::orc::WriterOptions writer_options;
-    PAIMON_ASSIGN_OR_RAISE(uint64_t stripe_size,
-                           GetMemorySizeOption(options, ORC_STRIPE_SIZE, 
DEFAULT_STRIPE_SIZE));
+    PAIMON_ASSIGN_OR_RAISE(
+        uint64_t stripe_size,
+        OptionsUtils::GetValueFromMap<uint64_t>(options, ORC_STRIPE_SIZE, 
DEFAULT_STRIPE_SIZE));
     writer_options.setStripeSize(stripe_size);
     PAIMON_ASSIGN_OR_RAISE(::orc::CompressionKind compression,
                            
ToOrcCompressionKind(StringUtils::ToLowerCase(file_compression)));
     writer_options.setCompression(compression);
-    PAIMON_ASSIGN_OR_RAISE(
-        uint64_t compression_block_size,
-        GetMemorySizeOption(options, ORC_COMPRESSION_BLOCK_SIZE, 
DEFAULT_COMPRESSION_BLOCK_SIZE));
+    PAIMON_ASSIGN_OR_RAISE(uint64_t compression_block_size, 
OptionsUtils::GetValueFromMap<uint64_t>(
+                                                                options, 
ORC_COMPRESSION_BLOCK_SIZE,
+                                                                
DEFAULT_COMPRESSION_BLOCK_SIZE));
     writer_options.setCompressionBlockSize(compression_block_size);
     PAIMON_ASSIGN_OR_RAISE(
         double dictionary_key_threshold,
diff --git a/src/paimon/format/parquet/parquet_format_defs.h 
b/src/paimon/format/parquet/parquet_format_defs.h
index 433103e5..8b205a09 100644
--- a/src/paimon/format/parquet/parquet_format_defs.h
+++ b/src/paimon/format/parquet/parquet_format_defs.h
@@ -99,7 +99,7 @@ static inline const char 
PARQUET_READ_PREDICATE_NODE_COUNT_LIMIT[] =
 static inline const char PARQUET_READ_ENABLE_PAGE_INDEX_FILTER[] =
     "parquet.read.enable-page-index-filter";
 
-// Default is true. Compaction will set to false to reduce memory consumption.
+// Default is true.
 static inline const char PARQUET_READ_ENABLE_PRE_BUFFER[] = 
"parquet.read.enable-pre-buffer";
 
 static constexpr uint32_t DEFAULT_PARQUET_READ_CACHE_OPTION_PREFETCH_LIMIT = 0;
diff --git a/src/paimon/fs/local/local_file.cpp 
b/src/paimon/fs/local/local_file.cpp
index 64530667..a3f960af 100644
--- a/src/paimon/fs/local/local_file.cpp
+++ b/src/paimon/fs/local/local_file.cpp
@@ -49,7 +49,7 @@ Result<std::unique_ptr<LocalFile>> LocalFile::Create(const 
std::string& path_str
     // local file system does not support path_string with scheme, e.g., 
"file:/tmp" will be
     // rewritten to "/tmp"
     PAIMON_ASSIGN_OR_RAISE(Path path, PathUtil::ToPath(path_string));
-    if (!path.scheme.empty() && StringUtils::ToLowerCase(path.scheme) != 
"file") {
+    if (!path.scheme.empty() && !StringUtils::EqualsIgnoreCase(path.scheme, 
"file")) {
         return Status::Invalid(fmt::format("invalid scheme {} for local file 
system", path.scheme));
     }
     if (path.path.empty() || path.path[0] != '/') {
diff --git a/src/paimon/fs/local/local_file_test.cpp 
b/src/paimon/fs/local/local_file_test.cpp
index f22095c9..25b9f6db 100644
--- a/src/paimon/fs/local/local_file_test.cpp
+++ b/src/paimon/fs/local/local_file_test.cpp
@@ -27,6 +27,11 @@
 
 namespace paimon::test {
 
+TEST(LocalFileTest, TestSchemeCaseInsensitive) {
+    ASSERT_OK(LocalFile::Create("FILE:/tmp"));
+    ASSERT_NOK(LocalFile::Create("s3:/tmp"));
+}
+
 TEST(LocalFileTest, TestReadWriteEmptyContent) {
     auto test_root_dir = UniqueTestDirectory::Create();
     ASSERT_TRUE(test_root_dir);
diff --git a/src/paimon/fs/s3/s3_file_system.cpp 
b/src/paimon/fs/s3/s3_file_system.cpp
index 49668b70..e31cc9ca 100644
--- a/src/paimon/fs/s3/s3_file_system.cpp
+++ b/src/paimon/fs/s3/s3_file_system.cpp
@@ -580,22 +580,22 @@ bool IsIpAddressAuthority(const std::string& authority) {
 }
 
 const char* AwsDnsSuffixForRegion(const std::string& region) {
-    if (region.rfind("cn-", 0) == 0) {
+    if (StringUtils::StartsWith(region, "cn-")) {
         return "amazonaws.com.cn";
     }
-    if (region.rfind("eusc-de-", 0) == 0) {
+    if (StringUtils::StartsWith(region, "eusc-de-")) {
         return "amazonaws.eu";
     }
-    if (region.rfind("us-iso-", 0) == 0) {
+    if (StringUtils::StartsWith(region, "us-iso-")) {
         return "c2s.ic.gov";
     }
-    if (region.rfind("us-isob-", 0) == 0) {
+    if (StringUtils::StartsWith(region, "us-isob-")) {
         return "sc2s.sgov.gov";
     }
-    if (region.rfind("eu-isoe-", 0) == 0) {
+    if (StringUtils::StartsWith(region, "eu-isoe-")) {
         return "cloud.adc-e.uk";
     }
-    if (region.rfind("us-isof-", 0) == 0) {
+    if (StringUtils::StartsWith(region, "us-isof-")) {
         return "csp.hci.ic.gov";
     }
     return "amazonaws.com";
diff --git a/src/paimon/global_index/lucene/jieba_analyzer.cpp 
b/src/paimon/global_index/lucene/jieba_analyzer.cpp
index 39cecec2..e0a71a81 100644
--- a/src/paimon/global_index/lucene/jieba_analyzer.cpp
+++ b/src/paimon/global_index/lucene/jieba_analyzer.cpp
@@ -17,6 +17,8 @@
  */
 #include "paimon/global_index/lucene/jieba_analyzer.h"
 
+#include <cctype>
+
 #include "paimon/common/utils/string_utils.h"
 #include "paimon/global_index/lucene/lucene_utils.h"
 
@@ -94,9 +96,7 @@ void JiebaTokenizer::NormalizeCase(std::string* term) {
         }
     }
     if (is_alphanumeric && !term->empty()) {
-        std::transform(term->begin(), term->end(), term->begin(), [](char ch) {
-            return static_cast<char>(std::tolower(static_cast<unsigned 
char>(ch)));
-        });
+        *term = StringUtils::ToLowerCase(*term);
     }
 }
 
diff --git a/src/paimon/rest/dlf_auth.cpp b/src/paimon/rest/dlf_auth.cpp
index 4c592f31..6a490627 100644
--- a/src/paimon/rest/dlf_auth.cpp
+++ b/src/paimon/rest/dlf_auth.cpp
@@ -22,7 +22,6 @@
 #include <openssl/evp.h>
 
 #include <array>
-#include <cctype>
 #include <climits>
 #include <ctime>
 #include <fstream>
@@ -74,28 +73,10 @@ constexpr const char kAcsSignatureVersionHeader[] = 
"x-acs-signature-version";
 constexpr const char kAcsVersionHeader[] = "x-acs-version";
 constexpr const char kAcsSecurityTokenHeader[] = "x-acs-security-token";
 
-void TrimWhitespace(std::string* value) {
-    size_t begin = 0;
-    while (begin < value->size() && std::isspace(static_cast<unsigned 
char>((*value)[begin]))) {
-        ++begin;
-    }
-    size_t end = value->size();
-    while (end > begin && std::isspace(static_cast<unsigned char>((*value)[end 
- 1]))) {
-        --end;
-    }
-    *value = value->substr(begin, end - begin);
-}
-
 Result<std::string> RequiredNonEmptyOption(const std::map<std::string, 
std::string>& options,
                                            const std::string& key) {
-    Result<std::string> value = 
OptionsUtils::GetValueFromMap<std::string>(options, key);
+    Result<std::string> value = OptionsUtils::GetNonEmptyValueFromMap(options, 
key);
     if (!value.ok()) {
-        if (!value.status().IsNotExist()) {
-            return value.status();
-        }
-        return Status::Invalid(fmt::format("option '{}' must be configured for 
DLF auth", key));
-    }
-    if (value.value().empty()) {
         return Status::Invalid(fmt::format("option '{}' must be configured for 
DLF auth", key));
     }
     return value.value();
@@ -267,7 +248,7 @@ Result<std::string> Md5Base64(const std::string& value) {
 
 std::string Trimmed(const std::string& value) {
     std::string trimmed = value;
-    TrimWhitespace(&trimmed);
+    StringUtils::Trim(&trimmed);
     return trimmed;
 }
 
@@ -510,7 +491,7 @@ Result<DlfToken> DlfEcsTokenLoader::LoadToken() {
     }
     if (!role_name_) {
         PAIMON_ASSIGN_OR_RAISE(std::string role, Get(metadata_url_));
-        TrimWhitespace(&role);
+        StringUtils::Trim(&role);
         if (role.empty()) {
             return Status::Invalid("DLF ECS metadata service returned an empty 
role name");
         }
diff --git a/src/paimon/rest/rest_api.cpp b/src/paimon/rest/rest_api.cpp
index c249ede0..a28e1991 100644
--- a/src/paimon/rest/rest_api.cpp
+++ b/src/paimon/rest/rest_api.cpp
@@ -24,6 +24,7 @@
 
 #include "fmt/format.h"
 #include "paimon/catalog_options.h"
+#include "paimon/common/utils/options_utils.h"
 #include "paimon/common/utils/rapidjson_util.h"
 #include "paimon/common/utils/sensitive_config_utils.h"
 #include "paimon/logging.h"
@@ -61,13 +62,13 @@ RestApi::RestApi(std::unique_ptr<RestHttpClient> client,
 Result<std::unique_ptr<RestApi>> RestApi::Create(const std::map<std::string, 
std::string>& options,
                                                  const std::string& warehouse, 
bool config_required,
                                                  const RestHttpClient::Config& 
http_config) {
-    auto uri_iter = options.find(CatalogOptions::URI);
-    if (uri_iter == options.end() || uri_iter->second.empty()) {
+    Result<std::string> uri = OptionsUtils::GetNonEmptyValueFromMap(options, 
CatalogOptions::URI);
+    if (!uri.ok()) {
         return Status::Invalid(fmt::format("option '{}' must be configured for 
the rest catalog",
                                            CatalogOptions::URI));
     }
     PAIMON_ASSIGN_OR_RAISE(std::unique_ptr<RestHttpClient> client,
-                           RestHttpClient::Create(uri_iter->second, 
http_config));
+                           RestHttpClient::Create(uri.value(), http_config));
     PAIMON_ASSIGN_OR_RAISE(std::unique_ptr<AuthProvider> auth_provider,
                            AuthProvider::Create(options));
 
diff --git a/src/paimon/rest/rest_auth.cpp b/src/paimon/rest/rest_auth.cpp
index 1af3b06a..43513b70 100644
--- a/src/paimon/rest/rest_auth.cpp
+++ b/src/paimon/rest/rest_auth.cpp
@@ -20,6 +20,7 @@
 
 #include "fmt/format.h"
 #include "paimon/catalog_options.h"
+#include "paimon/common/utils/options_utils.h"
 #include "paimon/common/utils/string_utils.h"
 #include "paimon/common/utils/url_utils.h"
 #include "paimon/rest/dlf_auth.h"
@@ -50,22 +51,24 @@ Result<std::map<std::string, std::string>> 
BearTokenAuthProvider::MergeAuthHeade
 
 Result<std::unique_ptr<AuthProvider>> AuthProvider::Create(
     const std::map<std::string, std::string>& options) {
-    auto provider_iter = options.find(CatalogOptions::TOKEN_PROVIDER);
-    if (provider_iter == options.end() || provider_iter->second.empty()) {
+    Result<std::string> provider_value =
+        OptionsUtils::GetNonEmptyValueFromMap(options, 
CatalogOptions::TOKEN_PROVIDER);
+    if (!provider_value.ok()) {
         return Status::Invalid(fmt::format("option '{}' must be configured for 
the rest catalog",
                                            CatalogOptions::TOKEN_PROVIDER));
     }
     // Matched leniently in lower case; other clients may match provider names
     // case-sensitively, so the exact "bear" and "dlf" spellings are portable.
-    std::string provider = StringUtils::ToLowerCase(provider_iter->second);
+    std::string provider = StringUtils::ToLowerCase(provider_value.value());
     if (provider == "bear") {
-        auto token_iter = options.find(CatalogOptions::TOKEN);
-        if (token_iter == options.end() || token_iter->second.empty()) {
+        Result<std::string> token =
+            OptionsUtils::GetNonEmptyValueFromMap(options, 
CatalogOptions::TOKEN);
+        if (!token.ok()) {
             return Status::Invalid(
                 fmt::format("option '{}' must be configured for the bear token 
provider",
                             CatalogOptions::TOKEN));
         }
-        return std::make_unique<BearTokenAuthProvider>(token_iter->second);
+        return std::make_unique<BearTokenAuthProvider>(token.value());
     }
     if (provider == "dlf") {
         return DlfAuthProvider::Create(options);
diff --git a/src/paimon/rest/rest_catalog.cpp b/src/paimon/rest/rest_catalog.cpp
index c928c6a2..eb86035c 100644
--- a/src/paimon/rest/rest_catalog.cpp
+++ b/src/paimon/rest/rest_catalog.cpp
@@ -52,7 +52,7 @@ constexpr const char kPathOption[] = "path";
 // `BranchManager::IsMainBranch`, which names the branch directory of a table, 
stays
 // case-sensitive: this normalization only decides how a table is addressed on 
the server.
 std::optional<std::string> NormalizeBranch(std::optional<std::string> branch) {
-    if (branch && StringUtils::ToLowerCase(branch.value()) == 
Identifier::kDefaultMainBranch) {
+    if (branch && StringUtils::EqualsIgnoreCase(branch.value(), 
Identifier::kDefaultMainBranch)) {
         return std::nullopt;
     }
     return branch;
diff --git a/src/paimon/rest/rest_http_client.cpp 
b/src/paimon/rest/rest_http_client.cpp
index 99b233f1..f4964bf5 100644
--- a/src/paimon/rest/rest_http_client.cpp
+++ b/src/paimon/rest/rest_http_client.cpp
@@ -228,7 +228,8 @@ std::string RestHttpClient::NormalizeUri(const std::string& 
uri) {
     while (!normalized.empty() && normalized.back() == '/') {
         normalized.pop_back();
     }
-    if (normalized.rfind("http://";, 0) != 0 && normalized.rfind("https://";, 0) 
!= 0) {
+    if (!StringUtils::StartsWith(normalized, "http://";) &&
+        !StringUtils::StartsWith(normalized, "https://";)) {
         normalized = "http://"; + normalized;
     }
     return normalized;
diff --git a/src/paimon/rest/rest_util.cpp b/src/paimon/rest/rest_util.cpp
index fc8e6f69..1a746987 100644
--- a/src/paimon/rest/rest_util.cpp
+++ b/src/paimon/rest/rest_util.cpp
@@ -21,6 +21,7 @@
 #include <stdexcept>
 
 #include "fmt/format.h"
+#include "paimon/common/utils/options_utils.h"
 #include "rapidjson/error/en.h"
 #include "rapidjson/stringbuffer.h"
 #include "rapidjson/writer.h"
@@ -29,13 +30,7 @@ namespace paimon {
 
 std::map<std::string, std::string> RestUtil::ExtractPrefixMap(
     const std::map<std::string, std::string>& options, const std::string& 
prefix) {
-    std::map<std::string, std::string> result;
-    for (const auto& [key, value] : options) {
-        if (key.size() > prefix.size() && key.compare(0, prefix.size(), 
prefix) == 0) {
-            result[key.substr(prefix.size())] = value;
-        }
-    }
-    return result;
+    return OptionsUtils::FetchOptionsWithPrefix(prefix, options);
 }
 
 std::string RestUtil::ExtractRequestId(const std::map<std::string, 
std::string>& headers) {

Reply via email to