-
Notifications
You must be signed in to change notification settings - Fork 439
impl(spanner): add uuid type #15023
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
impl(spanner): add uuid type #15023
Changes from 3 commits
fd38f64
c5016f7
7f9286d
144e0db
872b789
b2e52cc
7c4a4ca
d37648c
8593205
5fd97bc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,158 @@ | ||
| // Copyright 2025 Google LLC | ||
| // | ||
| // Licensed 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 | ||
| // | ||
| // https://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 "google/cloud/spanner/uuid.h" | ||
| #include "google/cloud/internal/make_status.h" | ||
| #include "absl/container/flat_hash_map.h" | ||
| #include "absl/strings/strip.h" | ||
|
|
||
| namespace google { | ||
| namespace cloud { | ||
| namespace spanner { | ||
| GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN | ||
| namespace { | ||
|
|
||
| constexpr int kMaxUuidNumberOfHexDigits = 32; | ||
|
|
||
| // Helper function to parse a single hexadecimal block of a UUID. | ||
| // A hexadecimal block is a 16-digit hexadecimal number, which is represented | ||
| // as 8 bytes. | ||
| StatusOr<uint64_t> ParseHexBlock(absl::string_view& str, | ||
|
||
| absl::string_view original_str) { | ||
| constexpr int kMaxUuidBlockLength = 16; | ||
| static auto const* char_to_hex = new absl::flat_hash_map<char, std::uint8_t>( | ||
| {{'0', 0x00}, {'1', 0x01}, {'2', 0x02}, {'3', 0x03}, {'4', 0x04}, | ||
| {'5', 0x05}, {'6', 0x06}, {'7', 0x07}, {'8', 0x08}, {'9', 0x09}, | ||
| {'a', 0x0a}, {'b', 0x0b}, {'c', 0x0c}, {'d', 0x0d}, {'e', 0x0e}, | ||
| {'f', 0x0f}, {'A', 0x0a}, {'B', 0x0b}, {'C', 0x0c}, {'D', 0x0d}, | ||
| {'E', 0x0e}, {'F', 0x0f}}); | ||
| uint64_t block = 0; | ||
| for (int j = 0; j < kMaxUuidBlockLength; ++j) { | ||
| absl::ConsumePrefix(&str, "-"); | ||
| if (str.empty()) { | ||
| return internal::InvalidArgumentError( | ||
| absl::StrFormat("UUID must be at least %d characters long: %s", | ||
| kMaxUuidNumberOfHexDigits, original_str), | ||
|
||
| GCP_ERROR_INFO()); | ||
| } | ||
| if (str[0] == '-') { | ||
| return internal::InvalidArgumentError( | ||
| absl::StrFormat("UUID cannot contain consecutive hyphens: %s", | ||
| original_str), | ||
| GCP_ERROR_INFO()); | ||
| } | ||
|
||
|
|
||
| auto it = char_to_hex->find(str[0]); | ||
| if (it == char_to_hex->end()) { | ||
| return internal::InvalidArgumentError( | ||
| absl::StrFormat("UUID contains invalid character (%c): %s", str[0], | ||
| original_str), | ||
| GCP_ERROR_INFO()); | ||
| } | ||
| block = (block << 4) + it->second; | ||
| str.remove_prefix(1); | ||
| } | ||
| return block; | ||
| } | ||
| } // namespace | ||
|
|
||
| Uuid::Uuid(absl::uint128 value) : uuid_(value) {} | ||
|
|
||
| Uuid::Uuid(std::uint64_t high_bits, std::uint64_t low_bits) | ||
| : Uuid(absl::MakeUint128(high_bits, low_bits)) {} | ||
|
|
||
| bool operator==(Uuid const& lhs, Uuid const& rhs) { | ||
| return lhs.uuid_ == rhs.uuid_; | ||
| } | ||
|
|
||
| bool operator<(Uuid const& lhs, Uuid const& rhs) { | ||
| return lhs.uuid_ < rhs.uuid_; | ||
| } | ||
|
|
||
| Uuid::operator std::string() const { | ||
| constexpr int kUuidStringLen = 36; | ||
| constexpr int kHyphenPos[] = {8, 13, 18, 23}; | ||
| auto to_hex = [](std::uint64_t v, int start_index, int end_index, char* out) { | ||
| static constexpr char kHexChar[] = {'0', '1', '2', '3', '4', '5', '6', '7', | ||
| '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; | ||
| for (int i = start_index; i >= end_index; --i) { | ||
| *out++ = kHexChar[(v >> (i * 4)) & 0xf]; | ||
| } | ||
| }; | ||
|
|
||
| std::string output; | ||
| output.resize(kUuidStringLen); | ||
| char* target = &((output)[output.size() - kUuidStringLen]); | ||
|
||
| auto high_bits = Uint128High64(uuid_); | ||
| auto low_bits = Uint128Low64(uuid_); | ||
| to_hex(high_bits, 15, 8, target); | ||
| *(target + kHyphenPos[0]) = '-'; | ||
| to_hex(high_bits, 7, 4, target + kHyphenPos[0] + 1); | ||
| *(target + kHyphenPos[1]) = '-'; | ||
| to_hex(high_bits, 3, 0, target + kHyphenPos[1] + 1); | ||
| *(target + kHyphenPos[2]) = '-'; | ||
| to_hex(low_bits, 15, 12, target + kHyphenPos[2] + 1); | ||
| *(target + kHyphenPos[3]) = '-'; | ||
| to_hex(low_bits, 11, 0, target + kHyphenPos[3] + 1); | ||
|
||
|
|
||
| return output; | ||
| } | ||
|
|
||
| StatusOr<Uuid> MakeUuid(absl::string_view str) { | ||
| // Early checks for invalid length or leading hyphen | ||
| if (str.size() < kMaxUuidNumberOfHexDigits) { | ||
|
||
| return internal::InvalidArgumentError( | ||
| absl::StrFormat("UUID must be at least %d characters long: %s", | ||
| kMaxUuidNumberOfHexDigits, str), | ||
| GCP_ERROR_INFO()); | ||
| } | ||
| std::string original_str = std::string(str); | ||
|
||
| // Check and remove optional braces | ||
| bool has_braces = (str[0] == '{'); | ||
|
||
| if (has_braces) { | ||
| if (str[str.size() - 1] != '}') { | ||
| return internal::InvalidArgumentError( | ||
| absl::StrFormat("UUID missing closing '}': %s", original_str), | ||
| GCP_ERROR_INFO()); | ||
| } | ||
| str.remove_prefix(1); | ||
| str.remove_suffix(1); | ||
| } | ||
|
|
||
| // Check for leading hyphen after braces. | ||
|
||
| if (str[0] == '-') { | ||
| return internal::InvalidArgumentError( | ||
| absl::StrFormat("UUID cannot begin with '-': %s", original_str), | ||
| GCP_ERROR_INFO()); | ||
| } | ||
|
|
||
| auto high_bits = ParseHexBlock(str, original_str); | ||
| if (!high_bits.ok()) return std::move(high_bits.status()); | ||
| auto low_bits = ParseHexBlock(str, original_str); | ||
| if (!low_bits.ok()) return std::move(low_bits.status()); | ||
|
|
||
| if (!str.empty()) { | ||
| return internal::InvalidArgumentError( | ||
| absl::StrFormat("Extra characters (%d) found after parsing UUID: %s", | ||
| str.size(), original_str), | ||
|
||
| GCP_ERROR_INFO()); | ||
| } | ||
|
|
||
| return Uuid(absl::MakeUint128(*high_bits, *low_bits)); | ||
| } | ||
|
|
||
| GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END | ||
| } // namespace spanner | ||
| } // namespace cloud | ||
| } // namespace google | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,114 @@ | ||
| // Copyright 2025 Google LLC | ||
| // | ||
| // Licensed 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 | ||
| // | ||
| // https://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. | ||
|
|
||
| #ifndef GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_SPANNER_UUID_H | ||
| #define GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_SPANNER_UUID_H | ||
|
|
||
| #include "google/cloud/spanner/version.h" | ||
| #include "google/cloud/status_or.h" | ||
| #include "absl/numeric/int128.h" | ||
| #include "absl/strings/string_view.h" | ||
| #include <iosfwd> | ||
| #include <string> | ||
|
|
||
| namespace google { | ||
| namespace cloud { | ||
| namespace spanner { | ||
| GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN | ||
|
|
||
| /** | ||
| * A representation of the Spanner UUID type: A fixed size 16 byte value | ||
| * that can be represented as a 32-digit hexadecimal string. | ||
| * | ||
| * @see https://cloud.google.com/spanner/docs/data-types#uuid_type | ||
| */ | ||
| class Uuid { | ||
| public: | ||
| /// Default construction yields a zero value UUID. | ||
| Uuid() = default; | ||
|
|
||
| /// Construct a UUID from a packed integer. | ||
|
||
| explicit Uuid(absl::uint128 value); | ||
|
|
||
| /// Construct a UUID from two 64 bit pieces. | ||
| Uuid(std::uint64_t high_bits, std::uint64_t low_bits); | ||
|
||
|
|
||
| /// @name Regular value type, supporting copy, assign, move. | ||
| ///@{ | ||
| Uuid(Uuid&&) = default; | ||
| Uuid& operator=(Uuid&&) = default; | ||
| Uuid(Uuid const&) = default; | ||
| Uuid& operator=(Uuid const&) = default; | ||
| ///@} | ||
|
|
||
| /// @name Relational operators | ||
| /// | ||
| ///@{ | ||
| friend bool operator==(Uuid const& lhs, Uuid const& rhs); | ||
| friend bool operator!=(Uuid const& lhs, Uuid const& rhs) { | ||
| return !(lhs == rhs); | ||
| } | ||
| friend bool operator<(Uuid const& lhs, Uuid const& rhs); | ||
|
||
| friend bool operator<=(Uuid const& lhs, Uuid const& rhs) { | ||
| return !(rhs < lhs); | ||
| } | ||
| friend bool operator>=(Uuid const& lhs, Uuid const& rhs) { | ||
| return !(lhs < rhs); | ||
| } | ||
| friend bool operator>(Uuid const& lhs, Uuid const& rhs) { | ||
| return (rhs < lhs); | ||
|
||
| } | ||
| ///@} | ||
|
|
||
| /// @name Conversion to packed integer representation. | ||
| explicit operator absl::uint128() const { return uuid_; } | ||
|
|
||
| /// @name Conversion to a lower case string using formatted: | ||
|
||
| /// [8 hex-digits]-[4 hex-digits]-[4 hex-digits]-[4 hex-digits]-[12 | ||
| /// hex-digits] | ||
| /// Example: 0b6ed04c-a16d-fc46-5281-7f9978c13738 | ||
| explicit operator std::string() const; | ||
|
|
||
| /// @name Output streaming | ||
| friend std::ostream& operator<<(std::ostream& os, Uuid uuid) { | ||
| return os << std::string(uuid); | ||
| } | ||
|
|
||
| private: | ||
| absl::uint128 uuid_ = 0; | ||
| }; | ||
|
|
||
| /** | ||
| * Parses a textual representation a `Uuid` from a string of hexadecimal digits. | ||
| * Returns an error if unable to parse the given input. | ||
| * | ||
| * Acceptable input strings must consist of [0-f] digits with formatting such: | ||
|
||
| * - Upper, lower, or mixed case. | ||
| * - Optional curly bracers around the entire UUID string. | ||
| * - Hyphens between any pair of hexadecimal digits are allowed. | ||
| * | ||
| * Example acceptable inputs: | ||
| * - {0b6ed04c-a16d-fc46-5281-7f9978c13738} | ||
| * - 0b6ed04ca16dfc4652817f9978c13738 | ||
| * - 7Bf8-A7b8-1917-1919-2625-F208-c582-4254 | ||
cuiy0006 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| * - {DECAFBAD-DEAD-FADE-CAFE-FEEDFACEBEEF} | ||
| */ | ||
| StatusOr<Uuid> MakeUuid(absl::string_view s); | ||
|
|
||
| GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END | ||
| } // namespace spanner | ||
| } // namespace cloud | ||
| } // namespace google | ||
|
|
||
| #endif // GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_SPANNER_UUID_H | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why isn't this localized to
ParseHexBlock()just likekMaxUuidBlockLengthis?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.