Skip to content

Commit c2f731a

Browse files
authored
impl(spanner): do Uuid to/from string conversions using absl::uint128 (googleapis#15057)
1 parent e1c4291 commit c2f731a

File tree

3 files changed

+126
-216
lines changed

3 files changed

+126
-216
lines changed

google/cloud/spanner/uuid.cc

Lines changed: 42 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -14,60 +14,18 @@
1414

1515
#include "google/cloud/spanner/uuid.h"
1616
#include "google/cloud/internal/make_status.h"
17+
#include "absl/strings/match.h"
1718
#include "absl/strings/str_format.h"
1819
#include "absl/strings/strip.h"
19-
#include <unordered_map>
20+
#include <cctype>
21+
#include <cstring>
2022

2123
namespace google {
2224
namespace cloud {
2325
namespace spanner {
2426
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN
25-
namespace {
2627

27-
// Helper function to parse a single hexadecimal block of a UUID.
28-
// A hexadecimal block is a 16-digit hexadecimal number, which is represented
29-
// as 8 bytes.
30-
StatusOr<std::uint64_t> ParseHexBlock(absl::string_view& str,
31-
absl::string_view original_str) {
32-
constexpr int kUuidNumberOfHexDigits = 32;
33-
constexpr int kMaxUuidBlockLength = 16;
34-
static auto const* char_to_hex = new std::unordered_map<char, std::uint8_t>(
35-
{{'0', 0x00}, {'1', 0x01}, {'2', 0x02}, {'3', 0x03}, {'4', 0x04},
36-
{'5', 0x05}, {'6', 0x06}, {'7', 0x07}, {'8', 0x08}, {'9', 0x09},
37-
{'a', 0x0a}, {'b', 0x0b}, {'c', 0x0c}, {'d', 0x0d}, {'e', 0x0e},
38-
{'f', 0x0f}, {'A', 0x0a}, {'B', 0x0b}, {'C', 0x0c}, {'D', 0x0d},
39-
{'E', 0x0e}, {'F', 0x0f}});
40-
std::uint64_t block = 0;
41-
for (int j = 0; j < kMaxUuidBlockLength; ++j) {
42-
absl::ConsumePrefix(&str, "-");
43-
if (str.empty()) {
44-
return internal::InvalidArgumentError(
45-
absl::StrFormat("UUID must contain %d hexadecimal digits: %s",
46-
kUuidNumberOfHexDigits, original_str),
47-
GCP_ERROR_INFO());
48-
}
49-
auto it = char_to_hex->find(str[0]);
50-
if (it == char_to_hex->end()) {
51-
if (str[0] == '-') {
52-
return internal::InvalidArgumentError(
53-
absl::StrFormat("UUID cannot contain consecutive hyphens: %s",
54-
original_str),
55-
GCP_ERROR_INFO());
56-
}
57-
58-
return internal::InvalidArgumentError(
59-
absl::StrFormat("UUID contains invalid character (%c): %s", str[0],
60-
original_str),
61-
GCP_ERROR_INFO());
62-
}
63-
block = (block << 4) + it->second;
64-
str.remove_prefix(1);
65-
}
66-
return block;
67-
}
68-
} // namespace
69-
70-
Uuid::Uuid(absl::uint128 value) : uuid_(value) {}
28+
constexpr char kHexDigits[] = "0123456789abcdef";
7129

7230
Uuid::Uuid(std::uint64_t high_bits, std::uint64_t low_bits)
7331
: Uuid(absl::MakeUint128(high_bits, low_bits)) {}
@@ -76,72 +34,59 @@ std::pair<std::uint64_t, std::uint64_t> Uuid::As64BitPair() const {
7634
return std::make_pair(Uint128High64(uuid_), Uint128Low64(uuid_));
7735
}
7836

79-
// TODO(#15043): Refactor to handle all 128 bits at once instead of splitting
80-
// into a pair of unsigned 64-bit integers.
8137
Uuid::operator std::string() const {
82-
constexpr int kUuidStringLen = 36;
83-
constexpr int kChunkLength[] = {8, 4, 4, 4, 12};
84-
auto to_hex = [](std::uint64_t v, int start_index, int end_index, char* out) {
85-
static constexpr char kHexChar[] = {'0', '1', '2', '3', '4', '5', '6', '7',
86-
'8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
87-
for (int i = start_index; i >= end_index; --i) {
88-
*out++ = kHexChar[(v >> (i * 4)) & 0xf];
89-
}
90-
return start_index - end_index + 1;
91-
};
92-
93-
std::string output;
94-
output.resize(kUuidStringLen);
95-
char* target = const_cast<char*>(output.data());
96-
char* const last = &((output)[output.size()]);
97-
auto bits = Uint128High64(uuid_);
98-
int start = 16;
99-
for (auto length : kChunkLength) {
100-
int end = start - length;
101-
target += to_hex(bits, start - 1, end, target);
102-
// Only hyphens write to valid addresses.
103-
if (target < last) *(target++) = '-';
104-
if (end == 0) {
105-
start = 16;
106-
bits = Uint128Low64(uuid_);
38+
constexpr char kTemplate[] = "00000000-0000-0000-0000-000000000000";
39+
char buf[sizeof kTemplate];
40+
auto uuid = uuid_;
41+
for (auto j = sizeof buf; j-- != 0;) {
42+
if (kTemplate[j] != '0') {
43+
buf[j] = kTemplate[j];
10744
} else {
108-
start = end;
45+
buf[j] = kHexDigits[static_cast<int>(uuid & 0xf)];
46+
uuid >>= 4;
10947
}
11048
}
111-
return output;
49+
return buf;
11250
}
11351

11452
StatusOr<Uuid> MakeUuid(absl::string_view str) {
115-
absl::string_view original_str = str;
116-
// Check and remove optional braces
117-
if (absl::ConsumePrefix(&str, "{")) {
118-
if (!absl::ConsumeSuffix(&str, "}")) {
53+
absl::uint128 uuid = 0;
54+
auto const original_str = str;
55+
if (absl::StartsWith(str, "{") && absl::ConsumeSuffix(&str, "}")) {
56+
str.remove_prefix(1);
57+
}
58+
if (absl::StartsWithIgnoreCase(str, "0x")) {
59+
str.remove_prefix(2);
60+
}
61+
constexpr int kUuidNumberOfHexDigits = 32;
62+
for (int j = 0; j != kUuidNumberOfHexDigits; ++j) {
63+
if (j != 0) absl::ConsumePrefix(&str, "-");
64+
if (str.empty()) {
11965
return internal::InvalidArgumentError(
120-
absl::StrFormat("UUID missing closing '}': %s", original_str),
66+
absl::StrFormat("UUID must contain %d hexadecimal digits: %s",
67+
kUuidNumberOfHexDigits, original_str),
12168
GCP_ERROR_INFO());
12269
}
70+
auto const* dp = std::strchr(
71+
kHexDigits, std::tolower(static_cast<unsigned char>(str.front())));
72+
if (dp == nullptr) {
73+
return internal::InvalidArgumentError(
74+
absl::StrFormat(
75+
"UUID contains invalid character '%c' at position %d: %s",
76+
str.front(), str.data() - original_str.data(), original_str),
77+
GCP_ERROR_INFO());
78+
}
79+
uuid <<= 4;
80+
uuid += dp - kHexDigits;
81+
str.remove_prefix(1);
12382
}
124-
125-
// Check for leading hyphen after stripping any surrounding braces.
126-
if (absl::StartsWith(str, "-")) {
127-
return internal::InvalidArgumentError(
128-
absl::StrFormat("UUID cannot begin with '-': %s", original_str),
129-
GCP_ERROR_INFO());
130-
}
131-
132-
// TODO(#15043): Refactor to parse all the bits at once.
133-
auto high_bits = ParseHexBlock(str, original_str);
134-
if (!high_bits.ok()) return std::move(high_bits).status();
135-
auto low_bits = ParseHexBlock(str, original_str);
136-
if (!low_bits.ok()) return std::move(low_bits).status();
137-
13883
if (!str.empty()) {
13984
return internal::InvalidArgumentError(
140-
absl::StrFormat("Extra characters found after parsing UUID: %s", str),
85+
absl::StrFormat("Extra characters \"%s\" found after parsing UUID: %s",
86+
str, original_str),
14187
GCP_ERROR_INFO());
14288
}
143-
144-
return Uuid(absl::MakeUint128(*high_bits, *low_bits));
89+
return Uuid{uuid};
14590
}
14691

14792
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END

google/cloud/spanner/uuid.h

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
#include "absl/numeric/int128.h"
2121
#include "absl/strings/string_view.h"
2222
#include <cstdint>
23-
#include <iosfwd>
23+
#include <ostream>
2424
#include <string>
2525
#include <utility>
2626

@@ -30,7 +30,7 @@ namespace spanner {
3030
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN
3131

3232
/**
33-
* A representation of the Spanner UUID type: A fixed size 16 byte value
33+
* A representation of the Spanner UUID type: A 16-byte value
3434
* that can be represented as a 32-digit hexadecimal string.
3535
*
3636
* @see https://cloud.google.com/spanner/docs/data-types#uuid_type
@@ -41,9 +41,9 @@ class Uuid {
4141
Uuid() = default;
4242

4343
/// Construct a UUID from one unsigned 128-bit integer.
44-
explicit Uuid(absl::uint128 value);
44+
explicit Uuid(absl::uint128 uuid) : uuid_(uuid) {}
4545

46-
/// Construct a UUID from two unsigned 64-bit pieces.
46+
/// Construct a UUID from two unsigned 64-bit integers.
4747
Uuid(std::uint64_t high_bits, std::uint64_t low_bits);
4848

4949
/// @name Regular value type, supporting copy, assign, move.
@@ -75,15 +75,13 @@ class Uuid {
7575
friend bool operator>(Uuid const& lhs, Uuid const& rhs) { return rhs < lhs; }
7676
///@}
7777

78-
/// @name Returns a pair of unsigned 64-bit integers representing the UUID.
79-
std::pair<std::uint64_t, std::uint64_t> As64BitPair() const;
80-
81-
/// @name Conversion to unsigned 128-bit integer representation.
78+
/// @name Conversion to one 128-bit unsigned integer.
8279
explicit operator absl::uint128() const { return uuid_; }
8380

84-
/// @name Conversion to a lower case string formatted as:
85-
/// [8 hex-digits]-[4 hex-digits]-[4 hex-digits]-[4 hex-digits]-[12
86-
/// hex-digits]
81+
/// @name Conversion to two unsigned 64-bit integers.
82+
std::pair<std::uint64_t, std::uint64_t> As64BitPair() const;
83+
84+
/// @name Conversion to an 8-4-4-4-12 format (lower-case) string.
8785
/// Example: 0b6ed04c-a16d-fc46-5281-7f9978c13738
8886
explicit operator std::string() const;
8987

0 commit comments

Comments
 (0)