This is an automated email from the ASF dual-hosted git repository. edwardxu pushed a commit to branch 2.13 in repository https://gitbox.apache.org/repos/asf/kvrocks.git
commit 4ffbed4e912561ef1955b490ec0ad134cf253714 Author: Twice <[email protected]> AuthorDate: Tue Jul 15 14:49:50 2025 +0800 chore(common): refactor parse utils via std::from_chars (#3052) Co-authored-by: hulk <[email protected]> --- src/common/parse_util.cc | 9 +-- src/common/parse_util.h | 100 +++++-------------------- tests/cppunit/parse_util.cc | 4 +- tests/gocase/unit/type/hash/hash_test.go | 2 +- tests/gocase/unit/type/strings/strings_test.go | 2 +- 5 files changed, 26 insertions(+), 91 deletions(-) diff --git a/src/common/parse_util.cc b/src/common/parse_util.cc index a7359c78a..9cb9790b3 100644 --- a/src/common/parse_util.cc +++ b/src/common/parse_util.cc @@ -20,14 +20,13 @@ #include "parse_util.h" -#include <limits> - #include "bit_util.h" +#include "string_util.h" -StatusOr<std::uint64_t> ParseSizeAndUnit(const std::string &v) { - auto [num, rest] = GET_OR_RET(TryParseInt<std::uint64_t>(v.c_str(), 10)); +StatusOr<std::uint64_t> ParseSizeAndUnit(std::string_view v) { + auto [num, rest] = GET_OR_RET(TryParseInt<std::uint64_t>(v, 10)); - if (*rest == 0) { + if (rest == v.data() + v.size()) { return num; } else if (util::EqualICase(rest, "k")) { return util::CheckedShiftLeft(num, 10); diff --git a/src/common/parse_util.h b/src/common/parse_util.h index 3e3cf116c..201f9415a 100644 --- a/src/common/parse_util.h +++ b/src/common/parse_util.h @@ -20,70 +20,11 @@ #pragma once -#include <cstdlib> -#include <limits> -#include <string> +#include <charconv> #include <tuple> +#include <type_traits> #include "status.h" -#include "string_util.h" - -namespace details { - -template <typename> -struct ParseIntFunc; - -template <> -struct ParseIntFunc<char> { // NOLINT - constexpr static const auto value = std::strtol; -}; - -template <> -struct ParseIntFunc<short> { // NOLINT - constexpr static const auto value = std::strtol; -}; - -template <> -struct ParseIntFunc<int> { // NOLINT - constexpr static const auto value = std::strtol; -}; - -template <> -struct ParseIntFunc<long> { // NOLINT - constexpr static const auto value = std::strtol; -}; - -template <> -struct ParseIntFunc<long long> { // NOLINT - constexpr static const auto value = std::strtoll; -}; - -template <> -struct ParseIntFunc<unsigned char> { // NOLINT - constexpr static const auto value = std::strtoul; -}; - -template <> -struct ParseIntFunc<unsigned short> { // NOLINT - constexpr static const auto value = std::strtoul; -}; - -template <> -struct ParseIntFunc<unsigned> { // NOLINT - constexpr static const auto value = std::strtoul; -}; - -template <> -struct ParseIntFunc<unsigned long> { // NOLINT - constexpr static const auto value = std::strtoul; -}; - -template <> -struct ParseIntFunc<unsigned long long> { // NOLINT - constexpr static const auto value = std::strtoull; -}; - -} // namespace details template <typename T> using ParseResultAndPos = std::tuple<T, const char *>; @@ -94,24 +35,20 @@ using ParseResultAndPos = std::tuple<T, const char *>; // e.g. TryParseInt("100MB") -> {100, "MB"} // if no integer can be parsed or out of type range, an error will be returned // base can be in {0, 2, ..., 36}, refer to strto* in standard c for more details -template <typename T = long long> // NOLINT -StatusOr<ParseResultAndPos<T>> TryParseInt(const char *v, int base = 0) { - char *end = nullptr; - - errno = 0; - auto res = details::ParseIntFunc<T>::value(v, &end, base); +template <typename T = long long, std::enable_if_t<std::is_integral_v<T>, int> = 0> // NOLINT +StatusOr<ParseResultAndPos<T>> TryParseInt(std::string_view v, int base = 10) { + T res = 0; + auto [end, ec] = std::from_chars(v.data(), v.data() + v.size(), res, base); - if (v == end) { + if (v.data() == end) { return {Status::NotOK, "not started as an integer"}; } - if (errno) { - return Status::FromErrno(); - } - - if (!std::is_same<T, decltype(res)>::value && - (res < std::numeric_limits<T>::min() || res > std::numeric_limits<T>::max())) { - return {Status::NotOK, "out of range of integer type"}; + if (ec != std::errc()) { + if (ec == std::errc::result_out_of_range) { + return {Status::NotOK, "out of range of integer type"}; + } + return {Status::NotOK, std::make_error_code(ec).message()}; } return ParseResultAndPos<T>{res, end}; @@ -121,13 +58,12 @@ StatusOr<ParseResultAndPos<T>> TryParseInt(const char *v, int base = 0) { // not like TryParseInt, the whole string need to be parsed as an integer, // e.g. ParseInt("100MB") -> error status template <typename T = long long> // NOLINT -StatusOr<T> ParseInt(const std::string &v, int base = 0) { - const char *begin = v.c_str(); - auto res = TryParseInt<T>(begin, base); +StatusOr<T> ParseInt(std::string_view v, int base = 10) { + auto res = TryParseInt<T>(v, base); if (!res) return res; - if (std::get<1>(*res) != begin + v.size()) { + if (std::get<1>(*res) != v.data() + v.size()) { return {Status::NotOK, "encounter non-integer characters"}; } @@ -140,7 +76,7 @@ using NumericRange = std::tuple<T, T>; // this overload accepts a range {min, max}, // integer out of the range will trigger an error status template <typename T = long long> // NOLINT -StatusOr<T> ParseInt(const std::string &v, NumericRange<T> range, int base = 0) { +StatusOr<T> ParseInt(std::string_view v, NumericRange<T> range, int base = 10) { auto res = ParseInt<T>(v, base); if (!res) return res; @@ -153,8 +89,10 @@ StatusOr<T> ParseInt(const std::string &v, NumericRange<T> range, int base = 0) } // available units: K, M, G, T, P -StatusOr<std::uint64_t> ParseSizeAndUnit(const std::string &v); +StatusOr<std::uint64_t> ParseSizeAndUnit(std::string_view v); +// we cannot use std::from_chars for floating-point numbers, +// since it is available since gcc/libstdc++ 11 and libc++ 20. template <typename> struct ParseFloatFunc; diff --git a/tests/cppunit/parse_util.cc b/tests/cppunit/parse_util.cc index d0364b7b9..3dd00de64 100644 --- a/tests/cppunit/parse_util.cc +++ b/tests/cppunit/parse_util.cc @@ -31,13 +31,10 @@ TEST(ParseUtil, TryParseInt) { ASSERT_EQ(TryParseInt("hello").Msg(), "not started as an integer"); ASSERT_FALSE(TryParseInt("9999999999999999999999999999999999")); - ASSERT_FALSE(TryParseInt("1", 100)); } TEST(ParseUtil, ParseInt) { ASSERT_EQ(*ParseInt("-2333"), -2333); - ASSERT_EQ(*ParseInt("0x1a"), 26); - ASSERT_EQ(*ParseInt("011"), 9); ASSERT_EQ(*ParseInt("111", 2), 7); ASSERT_EQ(*ParseInt("11", 010), 9); ASSERT_EQ(*ParseInt("11", 10), 11); @@ -50,6 +47,7 @@ TEST(ParseUtil, ParseInt) { ASSERT_EQ(*ParseInt<short>("30000"), 30000); ASSERT_EQ(*ParseInt<int>("99999"), 99999); ASSERT_EQ(ParseInt<int>("3000000000").Msg(), "out of range of integer type"); + ASSERT_EQ(ParseInt<unsigned>("-1").Msg(), "not started as an integer"); ASSERT_EQ(*ParseInt("123", {0, 123}), 123); ASSERT_EQ(ParseInt("124", {0, 123}).Msg(), "out of numeric range"); diff --git a/tests/gocase/unit/type/hash/hash_test.go b/tests/gocase/unit/type/hash/hash_test.go index 29db4747d..8d1536796 100644 --- a/tests/gocase/unit/type/hash/hash_test.go +++ b/tests/gocase/unit/type/hash/hash_test.go @@ -631,7 +631,7 @@ var testHash = func(t *testing.T, configs util.KvrocksServerConfigs) { t.Run("HINCRBY fails against hash value with spaces (left)", func(t *testing.T) { rdb.HSet(ctx, "samllhash", "str", " 11") rdb.HSet(ctx, "bighash", "str", " 11") - pattern := "ERR.*not an integer.*" + pattern := "ERR.*not.*an integer.*" util.ErrorRegexp(t, rdb.HIncrBy(ctx, "samllhash", "str", 1).Err(), pattern) util.ErrorRegexp(t, rdb.HIncrBy(ctx, "bighash", "str", 1).Err(), pattern) }) diff --git a/tests/gocase/unit/type/strings/strings_test.go b/tests/gocase/unit/type/strings/strings_test.go index 305ab3975..56b67cb00 100644 --- a/tests/gocase/unit/type/strings/strings_test.go +++ b/tests/gocase/unit/type/strings/strings_test.go @@ -378,7 +378,7 @@ func testString(t *testing.T, configs util.KvrocksServerConfigs) { t.Run("SETBIT with out of range bit offset", func(t *testing.T) { require.NoError(t, rdb.Del(ctx, "mykey").Err()) require.ErrorContains(t, rdb.SetBit(ctx, "mykey", 4*1024*1024*1024+2, 1).Err(), "out of range") - require.ErrorContains(t, rdb.SetBit(ctx, "mykey", -1, 1).Err(), "out of range") + require.ErrorContains(t, rdb.SetBit(ctx, "mykey", -1, 1).Err(), "an integer") }) t.Run("SETBIT with non-bit argument", func(t *testing.T) {
