leaves12138 commented on code in PR #23: URL: https://github.com/apache/paimon-cpp/pull/23#discussion_r3310580031
########## src/paimon/common/utils/string_utils.cpp: ########## @@ -0,0 +1,220 @@ +/* + * 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 "paimon/common/utils/string_utils.h" + +#include <algorithm> +#include <cctype> +#include <ctime> +#include <iomanip> +#include <iterator> +#include <utility> + +#include "fmt/format.h" +#include "paimon/status.h" + +namespace paimon { +std::string StringUtils::Replace(const std::string& text, const std::string& search_string, + const std::string& replacement, int32_t max) { + std::string str = text; + size_t pos = str.find(search_string); + int32_t count = 0; + while (pos != std::string::npos && (count < max || max == -1)) { + str.replace(pos, search_string.size(), replacement); + pos = str.find(search_string, pos + replacement.size()); + count++; + } + return str; +} + +std::string StringUtils::ReplaceLast(const std::string& text, const std::string& old_str, + const std::string& new_str) { + std::string str = text; + size_t pos = str.rfind(old_str); + if (pos != std::string::npos) { + str.replace(pos, old_str.size(), new_str); + } + return str; +} + +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); +} +bool StringUtils::EndsWith(const std::string& str, const std::string& suffix) { + size_t s1 = str.size(); + size_t s2 = suffix.size(); + return (s1 >= s2) && (str.compare(s1 - s2, s2, suffix) == 0); +} +bool StringUtils::IsNullOrWhitespaceOnly(const std::string& str) { + if (str.empty()) { + return true; + } + for (char c : str) { + if (!std::isspace(static_cast<unsigned char>(c))) { + return false; + } + } + return true; +} + +void StringUtils::Trim(std::string* str) { + str->erase(str->find_last_not_of(' ') + 1); + str->erase(0, str->find_first_not_of(' ')); +} + +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); }); + 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); }); + return result; +} + +std::vector<std::string> StringUtils::Split(const std::string& text, const std::string& sep_str, + bool ignore_empty) { + std::vector<std::string> vec; + if (sep_str.empty()) { + // invalid case, do not split. + vec.emplace_back(text); + return vec; + } + size_t n = 0, old = 0; + while (n != std::string::npos) { + n = text.find(sep_str, n); + if (n != std::string::npos) { + if (!ignore_empty || n != old) { + vec.emplace_back(text.substr(old, n - old)); + } + n += sep_str.length(); + old = n; + } + } + + if (!ignore_empty || old < text.length()) { + vec.emplace_back(text.substr(old, text.length() - old)); + } + return vec; +} + +std::vector<std::vector<std::string>> StringUtils::Split(const std::string& text, + const std::string& delim1, + const std::string& delim2) { + std::vector<std::vector<std::string>> result; + std::vector<std::string> split_parts = Split(text, delim1); + result.reserve(split_parts.size()); + for (auto& part : split_parts) { + result.emplace_back(Split(part, delim2)); + } + return result; +} + +Result<int32_t> StringUtils::StringToDate(const std::string& str) { + auto int_value = StringToValue<int32_t>(str); + if (int_value) { + return int_value.value(); + } + std::tm timeinfo = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, nullptr}; + std::istringstream ss(str); + ss >> std::get_time(&timeinfo, "%Y-%m-%d"); + if (ss.fail()) { + return Status::Invalid(fmt::format("failed to convert string '{}' to date", str)); + } + std::time_t time = timegm(&timeinfo); Review Comment: This accepts invalid calendar dates because `std::get_time` only fills `tm`, and `timegm`/`mktime` normalizes out-of-range fields instead of rejecting them. For example, `StringToDate("2019-02-29")` will become `2019-03-01`, and `StringToTimestampMillis("2023-02-30 00:00:00")` will become `2023-03-02 00:00:00`. Java Paimon's `DateTimeUtils.parseDate` explicitly validates month/day ranges and rejects invalid dates, so this can persist or compare wrong metadata values. Please validate the parsed year/month/day before converting, or compare the normalized `tm` back to the parsed fields, and add tests for non-leap Feb 29 / Feb 30. -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected]
