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