From a7fad1d232f384618d66da3c176a29bb3d250aa0 Mon Sep 17 00:00:00 2001 From: allen <1007729991@qq.com> Date: Sun, 14 Sep 2025 10:58:22 +0800 Subject: [PATCH] add object id Signed-off-by: allen <1007729991@qq.com> --- include/pain/base/object_id.h | 110 ++++++++++ include/pain/base/uuid.h | 13 +- protocols/pain/proto/common.proto | 6 + src/base/test/test_object_id.cc | 313 ++++++++++++++++++++++++++++ src/common/BUILD | 1 + src/common/object_id_util.h | 38 ++++ src/common/test/test_object_util.cc | 29 +++ 7 files changed, 505 insertions(+), 5 deletions(-) create mode 100644 include/pain/base/object_id.h create mode 100644 src/base/test/test_object_id.cc create mode 100644 src/common/object_id_util.h create mode 100644 src/common/test/test_object_util.cc diff --git a/include/pain/base/object_id.h b/include/pain/base/object_id.h new file mode 100644 index 0000000..ce3da37 --- /dev/null +++ b/include/pain/base/object_id.h @@ -0,0 +1,110 @@ +#pragma once + +#include +#include +#include +#include + +namespace pain { + +class ObjectId { +public: + ObjectId(uint32_t partition_id, UUID uuid) : _reserved(0), _partition_id(partition_id), _uuid(uuid) {} + + uint32_t partition_id() const { + return _partition_id; + } + + UUID uuid() const { + return _uuid; + } + + bool operator==(const ObjectId& other) const { + return _partition_id == other._partition_id && _uuid == other._uuid; + } + + bool operator!=(const ObjectId& other) const { + return !(*this == other); + } + + bool operator<(const ObjectId& other) const { + return _partition_id < other._partition_id || (_partition_id == other._partition_id && _uuid < other._uuid); + } + + bool operator>(const ObjectId& other) const { + return other < *this; + } + + bool operator<=(const ObjectId& other) const { + return !(*this > other); + } + + bool operator>=(const ObjectId& other) const { + return !(*this < other); + } + + std::string to_string() const { + return fmt::format("{}-{}", _partition_id, _uuid.str()); + } + + static std::optional from_str(std::string_view str) { + auto pos = str.find('-'); + if (pos == std::string::npos) { + return std::nullopt; + } + auto [partition_id_str, uuid_str] = split(str); + if (partition_id_str.empty() || uuid_str.empty()) { + return std::nullopt; + } + uint32_t pid = 0; + try { + pid = std::stoull(partition_id_str.data()); + } catch (const std::exception& e) { + return std::nullopt; + } + auto uuid = UUID::from_str(uuid_str); + if (!uuid) { + return std::nullopt; + } + return ObjectId(pid, uuid.value()); + } + + static ObjectId from_str_or_die(std::string_view str) { + auto [partition_id_str, uuid_str] = split(str); + if (partition_id_str.empty() || uuid_str.empty()) { + BOOST_ASSERT_MSG(false, fmt::format("Invalid ObjectId string: {}", str).c_str()); + } + uint32_t pid = 0; + try { + pid = std::stoull(partition_id_str.data()); + } catch (const std::exception& e) { + BOOST_ASSERT_MSG(false, fmt::format("Invalid ObjectId string: {}", str).c_str()); + } + + auto uuid = UUID::from_str_or_die(uuid_str); + return ObjectId(pid, uuid); + } + +private: + static std::pair split(std::string_view str) { + auto pos = str.find('-'); + BOOST_ASSERT_MSG(pos != std::string::npos, "Invalid ObjectId string"); + return {str.substr(0, pos), str.substr(pos + 1)}; + } + +private: + uint32_t _reserved; + uint32_t _partition_id; + UUID _uuid; +}; + +} // namespace pain + +namespace std { +template <> +struct hash { + std::size_t operator()(const pain::ObjectId& obj) const { + return std::hash()(obj.partition_id()) ^ std::hash()(obj.uuid()); + } +}; +} // namespace std diff --git a/include/pain/base/uuid.h b/include/pain/base/uuid.h index 608ce39..a29b4d9 100644 --- a/include/pain/base/uuid.h +++ b/include/pain/base/uuid.h @@ -3,6 +3,8 @@ #include #include #include +#include +#include namespace pain { @@ -22,7 +24,7 @@ class UUID : public UUIDv4::UUID { return *reinterpret_cast(data + 8); // NOLINT } - static bool valid(const std::string& uuid) { + static bool valid(std::string_view uuid) { constexpr size_t guid_length = 36U; if (uuid.length() != guid_length) { return false; @@ -45,17 +47,18 @@ class UUID : public UUIDv4::UUID { return true; } - static std::optional from_str(const std::string& str) { + static std::optional from_str(std::string_view str) { if (!valid(str)) { return std::nullopt; } - auto uuid = UUIDv4::UUID::fromStrFactory(str); + auto uuid = UUIDv4::UUID::fromStrFactory(str.data()); return UUID(uuid); } - static UUID from_str_or_die(const std::string& str) { - auto uuid = UUIDv4::UUID::fromStrFactory(str); + static UUID from_str_or_die(std::string_view str) { + BOOST_ASSERT_MSG(valid(str), fmt::format("Invalid UUID string: {}", str).c_str()); + auto uuid = UUIDv4::UUID::fromStrFactory(str.data()); return UUID(uuid); } diff --git a/protocols/pain/proto/common.proto b/protocols/pain/proto/common.proto index 2650d00..c51eb1b 100644 --- a/protocols/pain/proto/common.proto +++ b/protocols/pain/proto/common.proto @@ -11,6 +11,12 @@ message UUID { uint64 high = 2; } +message ObjectId { + uint32 reserved = 1; + uint32 partition_id = 2; + UUID uuid = 3; +} + message Pair { string key = 1; string value = 2; diff --git a/src/base/test/test_object_id.cc b/src/base/test/test_object_id.cc new file mode 100644 index 0000000..18ec5ef --- /dev/null +++ b/src/base/test/test_object_id.cc @@ -0,0 +1,313 @@ +#include +#include +#include +#include +#include +#include + +// NOLINTBEGIN +namespace { +using namespace pain; + +class TestObjectId : public ::testing::Test { +protected: + void SetUp() override { + // 创建一些测试用的 UUID + test_uuid1 = UUID::from_str_or_die("050e8400-0000-0000-0000-000000000000"); + test_uuid2 = UUID::from_str_or_die("6ba7b810-9dad-11d1-80b4-80c04fd430c8"); + test_uuid3 = UUID::from_str_or_die("6ba7b811-9dad-11d1-90b4-90c04fd430c8"); + ASSERT_LT(test_uuid1, test_uuid2) << fmt::format( + "uuid1:{},{} uuid2:{},{}", test_uuid1.low(), test_uuid1.high(), test_uuid2.low(), test_uuid2.high()); + ASSERT_LT(test_uuid2, test_uuid3) << fmt::format( + "uuid2:{},{} uuid3:{},{}", test_uuid2.low(), test_uuid2.high(), test_uuid3.low(), test_uuid3.high()); + } + + UUID test_uuid1; + UUID test_uuid2; + UUID test_uuid3; +}; + +// 构造函数和访问器测试 +TEST_F(TestObjectId, ConstructorAndAccessors) { + ObjectId obj(123, test_uuid1); + + EXPECT_EQ(obj.partition_id(), 123); + EXPECT_EQ(obj.uuid(), test_uuid1); +} + +TEST_F(TestObjectId, ConstructorWithZeroPartitionId) { + ObjectId obj(0, test_uuid1); + + EXPECT_EQ(obj.partition_id(), 0); + EXPECT_EQ(obj.uuid(), test_uuid1); +} + +TEST_F(TestObjectId, ConstructorWithMaxPartitionId) { + ObjectId obj(UINT32_MAX, test_uuid1); + + EXPECT_EQ(obj.partition_id(), UINT32_MAX); + EXPECT_EQ(obj.uuid(), test_uuid1); +} + +// 相等性操作符测试 +TEST_F(TestObjectId, EqualityOperator) { + ObjectId obj1(123, test_uuid1); + ObjectId obj2(123, test_uuid1); + ObjectId obj3(456, test_uuid1); + ObjectId obj4(123, test_uuid2); + + EXPECT_TRUE(obj1 == obj2); + EXPECT_FALSE(obj1 == obj3); + EXPECT_FALSE(obj1 == obj4); +} + +TEST_F(TestObjectId, InequalityOperator) { + ObjectId obj1(123, test_uuid1); + ObjectId obj2(123, test_uuid1); + ObjectId obj3(456, test_uuid1); + ObjectId obj4(123, test_uuid2); + + EXPECT_FALSE(obj1 != obj2); + EXPECT_TRUE(obj1 != obj3); + EXPECT_TRUE(obj1 != obj4); +} + +// 比较操作符测试 +TEST_F(TestObjectId, LessThanOperator) { + ObjectId obj1(100, test_uuid1); + ObjectId obj2(200, test_uuid1); + ObjectId obj3(100, test_uuid2); + ObjectId obj4(100, test_uuid1); + + // 不同 partition_id + EXPECT_TRUE(obj1 < obj2); + EXPECT_FALSE(obj2 < obj1); + + // 相同 partition_id,不同 UUID + EXPECT_TRUE(obj1 < obj3); + EXPECT_FALSE(obj3 < obj1); + + // 相同 partition_id 和 UUID + EXPECT_FALSE(obj1 < obj4); + EXPECT_FALSE(obj4 < obj1); +} + +TEST_F(TestObjectId, GreaterThanOperator) { + ObjectId obj1(100, test_uuid1); + ObjectId obj2(200, test_uuid1); + ObjectId obj3(100, test_uuid2); + ObjectId obj4(100, test_uuid1); + + // 不同 partition_id + EXPECT_TRUE(obj2 > obj1); + EXPECT_FALSE(obj1 > obj2); + + // 相同 partition_id,不同 UUID + EXPECT_TRUE(obj3 > obj1); + EXPECT_FALSE(obj1 > obj3); + + // 相同 partition_id 和 UUID + EXPECT_FALSE(obj1 > obj4); + EXPECT_FALSE(obj4 > obj1); +} + +TEST_F(TestObjectId, LessThanOrEqualOperator) { + ObjectId obj1(100, test_uuid1); + ObjectId obj2(200, test_uuid1); + ObjectId obj3(100, test_uuid2); + ObjectId obj4(100, test_uuid1); + + // 不同 partition_id + EXPECT_TRUE(obj1 <= obj2); + EXPECT_FALSE(obj2 <= obj1); + + // 相同 partition_id,不同 UUID + EXPECT_TRUE(obj1 <= obj3); + EXPECT_FALSE(obj3 <= obj1); + + // 相同 partition_id 和 UUID + EXPECT_TRUE(obj1 <= obj4); + EXPECT_TRUE(obj4 <= obj1); +} + +TEST_F(TestObjectId, GreaterThanOrEqualOperator) { + ObjectId obj1(100, test_uuid1); + ObjectId obj2(200, test_uuid1); + ObjectId obj3(100, test_uuid2); + ObjectId obj4(100, test_uuid1); + + // 不同 partition_id + EXPECT_TRUE(obj2 >= obj1); + EXPECT_FALSE(obj1 >= obj2); + + // 相同 partition_id,不同 UUID + EXPECT_TRUE(obj3 >= obj1); + EXPECT_FALSE(obj1 >= obj3); + + // 相同 partition_id 和 UUID + EXPECT_TRUE(obj1 >= obj4); + EXPECT_TRUE(obj4 >= obj1); +} + +// 字符串转换测试 +TEST_F(TestObjectId, ToString) { + ObjectId obj(123, test_uuid1); + std::string str = obj.to_string(); + + EXPECT_EQ(str, fmt::format("123-{}", test_uuid1.str())); +} + +TEST_F(TestObjectId, ToStringWithZeroPartitionId) { + ObjectId obj(0, test_uuid1); + std::string str = obj.to_string(); + + EXPECT_EQ(str, fmt::format("0-{}", test_uuid1.str())); +} + +TEST_F(TestObjectId, ToStringWithMaxPartitionId) { + ObjectId obj(UINT32_MAX, test_uuid1); + std::string str = obj.to_string(); + + EXPECT_EQ(str, fmt::format("{}-{}", UINT32_MAX, test_uuid1.str())); +} + +TEST_F(TestObjectId, FromString) { + std::string str = fmt::format("123-{}", test_uuid1.str()); + ObjectId obj = ObjectId::from_str_or_die(str); + + EXPECT_EQ(obj.partition_id(), 123); + EXPECT_EQ(obj.uuid(), test_uuid1); +} + +TEST_F(TestObjectId, FromStringWithZeroPartitionId) { + std::string str = fmt::format("0-{}", test_uuid1.str()); + ObjectId obj = ObjectId::from_str_or_die(str); + + EXPECT_EQ(obj.partition_id(), 0); + EXPECT_EQ(obj.uuid(), test_uuid1); +} + +TEST_F(TestObjectId, FromStringWithMaxPartitionId) { + std::string str = fmt::format("{}-{}", UINT32_MAX, test_uuid1.str()); + ObjectId obj = ObjectId::from_str_or_die(str); + + EXPECT_EQ(obj.partition_id(), UINT32_MAX); + EXPECT_EQ(obj.uuid(), test_uuid1); +} + +// 往返转换测试 +TEST_F(TestObjectId, RoundTripConversion) { + ObjectId original(12345, test_uuid2); + std::string str = original.to_string(); + ObjectId converted = ObjectId::from_str_or_die(str); + + EXPECT_EQ(original, converted); + EXPECT_EQ(original.partition_id(), converted.partition_id()); + EXPECT_EQ(original.uuid(), converted.uuid()); +} + +TEST_F(TestObjectId, RoundTripConversionWithDifferentValues) { + std::vector partition_ids = {0, 1, 100, 1000, UINT32_MAX}; + std::vector uuids = {test_uuid1, test_uuid2, test_uuid3}; + + for (auto pid : partition_ids) { + for (const auto& uuid : uuids) { + ObjectId original(pid, uuid); + std::string str = original.to_string(); + ObjectId converted = ObjectId::from_str_or_die(str); + + EXPECT_EQ(original, converted) << "Failed for partition_id=" << pid; + } + } +} + +// 排序测试 +TEST_F(TestObjectId, Sorting) { + std::vector objects = {ObjectId(200, test_uuid2), + ObjectId(100, test_uuid3), + ObjectId(100, test_uuid1), + ObjectId(150, test_uuid1), + ObjectId(100, test_uuid2)}; + + std::sort(objects.begin(), objects.end()); + + // 验证排序结果 + EXPECT_LT(objects[0], objects[1]); + EXPECT_LT(objects[1], objects[2]); + EXPECT_LT(objects[2], objects[3]); + EXPECT_LT(objects[3], objects[4]); +} + +// 边界情况测试 +TEST_F(TestObjectId, EdgeCasePartitionIds) { + std::vector edge_cases = {0, 1, UINT32_MAX - 1, UINT32_MAX}; + + for (auto pid : edge_cases) { + ObjectId obj(pid, test_uuid1); + EXPECT_EQ(obj.partition_id(), pid); + + // 测试往返转换 + std::string str = obj.to_string(); + ObjectId converted = ObjectId::from_str_or_die(str); + EXPECT_EQ(obj, converted); + } +} + +// 错误处理测试 +TEST_F(TestObjectId, FromStringInvalidFormat) { + // 测试缺少分隔符的情况 + EXPECT_DEATH({ ObjectId::from_str_or_die("123550e8400-e29b-41d4-a716-446655440000"); }, "Invalid UUID string"); + + // 测试空字符串 + EXPECT_DEATH({ ObjectId::from_str_or_die(""); }, "Invalid ObjectId string"); + + // 测试只有分隔符 + EXPECT_DEATH({ ObjectId::from_str_or_die("-"); }, "Invalid ObjectId string"); + + // 测试多个分隔符 + EXPECT_DEATH({ ObjectId::from_str_or_die("123-456-550e8400-e29b-41d4-a716-446655440000"); }, "Invalid UUID string"); +} + +TEST_F(TestObjectId, FromStringInvalidUUID) { + // 测试无效的 UUID 格式 + EXPECT_DEATH({ ObjectId::from_str_or_die("123-invalid-uuid"); }, ".*"); + + // 测试空 UUID + EXPECT_DEATH({ ObjectId::from_str_or_die("123-"); }, ".*"); +} + +TEST_F(TestObjectId, FromStringInvalidPartitionId) { + // 测试非数字 partition_id + EXPECT_DEATH({ ObjectId::from_str_or_die("abc-550e8400-e29b-41d4-a716-446655440000"); }, ".*"); + + // 测试负数 partition_id(虽然 uint64_t 不能为负,但 stoull 会处理) + EXPECT_DEATH({ ObjectId::from_str_or_die("-123-550e8400-e29b-41d4-a716-446655440000"); }, ".*"); +} + +// 性能测试(基本功能验证) +TEST_F(TestObjectId, PerformanceBasic) { + const int iterations = 1000; + + for (int i = 0; i < iterations; ++i) { + ObjectId obj(i, test_uuid1); + std::string str = obj.to_string(); + ObjectId converted = ObjectId::from_str_or_die(str); + EXPECT_EQ(obj, converted); + } +} + +// 哈希兼容性测试(如果用于容器) +TEST_F(TestObjectId, HashCompatibility) { + ObjectId obj1(123, test_uuid1); + ObjectId obj2(123, test_uuid1); + ObjectId obj3(456, test_uuid1); + + // 相同对象应该有相同的哈希值 + EXPECT_EQ(std::hash{}(obj1), std::hash{}(obj2)); + + // 不同对象应该有不同的哈希值(高概率) + EXPECT_NE(std::hash{}(obj1), std::hash{}(obj3)); +} + +} // namespace +// NOLINTEND diff --git a/src/common/BUILD b/src/common/BUILD index 199caab..9649789 100644 --- a/src/common/BUILD +++ b/src/common/BUILD @@ -7,6 +7,7 @@ cc_library( linkopts = PAIN_LINKOPTS, deps = [ "//src/base:pain_base", + "//protocols/pain/proto:cc_pain_common_proto", "@boost.assert", "@boost.smart_ptr", "@rocksdb", diff --git a/src/common/object_id_util.h b/src/common/object_id_util.h new file mode 100644 index 0000000..0bd69f3 --- /dev/null +++ b/src/common/object_id_util.h @@ -0,0 +1,38 @@ +#pragma once + +#include +#include "pain/proto/common.pb.h" + +namespace pain::common { +inline proto::UUID to_proto(const UUID& uuid) { + proto::UUID proto; + proto.set_low(uuid.low()); + proto.set_high(uuid.high()); + return proto; +} + +inline void to_proto(const UUID& uuid, proto::UUID* proto) { + proto->set_low(uuid.low()); + proto->set_high(uuid.high()); +} + +inline UUID from_proto(const proto::UUID& proto) { + return UUID(proto.high(), proto.low()); +} + +inline proto::ObjectId to_proto(const ObjectId& obj) { + proto::ObjectId proto; + proto.set_partition_id(obj.partition_id()); + to_proto(obj.uuid(), proto.mutable_uuid()); + return proto; +} + +inline void to_proto(const ObjectId& obj, proto::ObjectId* proto) { + proto->set_partition_id(obj.partition_id()); + to_proto(obj.uuid(), proto->mutable_uuid()); +} + +inline ObjectId from_proto(const proto::ObjectId& proto) { + return ObjectId(proto.partition_id(), from_proto(proto.uuid())); +} +} // namespace pain::common diff --git a/src/common/test/test_object_util.cc b/src/common/test/test_object_util.cc new file mode 100644 index 0000000..6135704 --- /dev/null +++ b/src/common/test/test_object_util.cc @@ -0,0 +1,29 @@ +#include +#include "pain/proto/common.pb.h" +#include "common/object_id_util.h" + +// NOLINTBEGIN(readability-magic-numbers) + +namespace { +using namespace pain; +using namespace pain::common; + +TEST(ObjectIdUtil, ToProto) { + ObjectId obj(123, UUID::from_str_or_die("050e8400-0000-0000-0000-000000000000")); + auto proto = to_proto(obj); + EXPECT_EQ(proto.partition_id(), 123); + EXPECT_EQ(proto.uuid().high(), 0); + EXPECT_EQ(proto.uuid().low(), 0x840e05); +} + +TEST(ObjectIdUtil, FromProto) { + proto::ObjectId proto; + proto.set_partition_id(123); + to_proto(UUID::from_str_or_die("050e8400-0000-0000-0000-000000000000"), proto.mutable_uuid()); + auto obj = from_proto(proto); + EXPECT_EQ(obj.partition_id(), 123); + EXPECT_EQ(obj.uuid().str(), "050e8400-0000-0000-0000-000000000000"); +} +} // namespace + +// NOLINTEND(readability-magic-numbers)