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

twice pushed a commit to branch unstable
in repository https://gitbox.apache.org/repos/asf/kvrocks.git


The following commit(s) were added to refs/heads/unstable by this push:
     new bb607291c chore(common): refactor parse utils via std::from_chars 
(#3052)
bb607291c is described below

commit bb607291c80e72420a76e3bd190891f428d7859f
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) {

Reply via email to