From 1c6ef463eacfb069c8155c1e28e1075f6c16f1d5 Mon Sep 17 00:00:00 2001 From: bigdaronlee Date: Fri, 27 Sep 2024 10:17:03 +0700 Subject: [PATCH] feat: add hash basic cmd --- include/pika_command.h | 4 +- include/pika_pkhash.h | 304 +++++++ src/pika_command.cc | 82 +- src/pika_pkhash.cc | 472 +++++++++- src/storage/include/storage/storage.h | 59 +- src/storage/src/redis.h | 33 + src/storage/src/redis_pkhashes.cc | 827 ++++++++++++++++- src/storage/src/storage.cc | 77 ++ src/storage/tests/pkhashes_test.cc | 1190 ++++++++++++++++++++++++- 9 files changed, 3001 insertions(+), 47 deletions(-) diff --git a/include/pika_command.h b/include/pika_command.h index 73be4e595..f3cb78e33 100644 --- a/include/pika_command.h +++ b/include/pika_command.h @@ -151,7 +151,6 @@ const std::string kCmdNamePKHDel = "pkhdel"; const std::string kCmdNamePKHLen = "pkhlen"; const std::string kCmdNamePKHStrlen = "pkhstrlen"; const std::string kCmdNamePKHIncrby = "pkhincrby"; -const std::string kCmdNamePKHIncrbyfloat = "pkhincrbyfloat"; const std::string kCmdNamePKHMSet = "pkhmset"; const std::string kCmdNamePKHMSetex = "pkhmsetex"; const std::string kCmdNamePKHMGet = "pkhmget"; @@ -159,8 +158,7 @@ const std::string kCmdNamePKHKeys = "pkhkeys"; const std::string kCmdNamePKHVals = "pkhvals"; const std::string kCmdNamePKHGetall = "pkhgetall"; const std::string kCmdNamePKHScan = "pkhscan"; -const std::string kCmdNamePKEHScanRange = "pkehscanrange"; -const std::string kCmdNamePKEHRScanRange = "pkehrscanrange"; + // List const std::string kCmdNameLIndex = "lindex"; diff --git a/include/pika_pkhash.h b/include/pika_pkhash.h index 8be2ec004..6d8df9cc6 100644 --- a/include/pika_pkhash.h +++ b/include/pika_pkhash.h @@ -186,4 +186,308 @@ class PKHSetCmd : public Cmd { rocksdb::Status s_; }; +// 下面的都是新的命令。 +class PKHSetexCmd : public Cmd { + public: + PKHSetexCmd(const std::string& name, int arity, uint32_t flag) + : Cmd(name, arity, flag, static_cast(AclCategory::PKHASH)) {} + std::vector current_key() const override { + std::vector res; + res.push_back(key_); + return res; + } + void Do() override; + void DoThroughDB() override; + void DoUpdateCache() override; + void Split(const HintKeys& hint_keys) override {}; + void Merge() override {}; + Cmd* Clone() override { return new PKHSetexCmd(*this); } + + private: + std::string key_, field_, value_; + int64_t sec_; + void DoInitial() override; + rocksdb::Status s_; +}; + +class PKHExistsCmd : public Cmd { + public: + PKHExistsCmd(const std::string& name, int arity, uint32_t flag) + : Cmd(name, arity, flag, static_cast(AclCategory::PKHASH)) {} + std::vector current_key() const override { + std::vector res; + res.push_back(key_); + return res; + } + void Do() override; + void DoThroughDB() override; + void DoUpdateCache() override; + void Split(const HintKeys& hint_keys) override {}; + void Merge() override {}; + Cmd* Clone() override { return new PKHExistsCmd(*this); } + + private: + std::string key_, field_; + void DoInitial() override; + rocksdb::Status s_; +}; + +class PKHDelCmd : public Cmd { + public: + PKHDelCmd(const std::string& name, int arity, uint32_t flag) + : Cmd(name, arity, flag, static_cast(AclCategory::PKHASH)) {} + std::vector current_key() const override { + std::vector res; + res.push_back(key_); + return res; + } + void Do() override; + void DoThroughDB() override; + void DoUpdateCache() override; + void Split(const HintKeys& hint_keys) override {}; + void Merge() override {}; + Cmd* Clone() override { return new PKHDelCmd(*this); } + + private: + std::string key_; + std::vector fields_; + int32_t deleted_ = 0; + void DoInitial() override; + rocksdb::Status s_; +}; + +class PKHLenCmd : public Cmd { + public: + PKHLenCmd(const std::string& name, int arity, uint32_t flag) + : Cmd(name, arity, flag, static_cast(AclCategory::PKHASH)) {} + std::vector current_key() const override { + std::vector res; + res.push_back(key_); + return res; + } + void Do() override; + void DoThroughDB() override; + void DoUpdateCache() override; + void Split(const HintKeys& hint_keys) override {}; + void Merge() override {}; + Cmd* Clone() override { return new PKHLenCmd(*this); } + + private: + std::string key_; + bool is_force_; // 是否强制从db中获取 + void DoInitial() override; + rocksdb::Status s_; +}; + +class PKHStrLenCmd : public Cmd { + public: + PKHStrLenCmd(const std::string& name, int arity, uint32_t flag) + : Cmd(name, arity, flag, static_cast(AclCategory::PKHASH)) {} + std::vector current_key() const override { + std::vector res; + res.push_back(key_); + return res; + } + void Do() override; + void DoThroughDB() override; + void DoUpdateCache() override; + void Split(const HintKeys& hint_keys) override {}; + void Merge() override {}; + Cmd* Clone() override { return new PKHStrLenCmd(*this); } + + private: + std::string key_, field_; + void DoInitial() override; + rocksdb::Status s_; +}; + +class PKHIncrbyCmd : public Cmd { + public: + PKHIncrbyCmd(const std::string& name, int arity, uint32_t flag) + : Cmd(name, arity, flag, static_cast(AclCategory::PKHASH)) {} + std::vector current_key() const override { + std::vector res; + res.push_back(key_); + return res; + } + void Do() override; + void DoThroughDB() override; + void DoUpdateCache() override; + void Split(const HintKeys& hint_keys) override {}; + void Merge() override {}; + Cmd* Clone() override { return new PKHIncrbyCmd(*this); } + + private: + std::string key_, field_; + int64_t by_; + int64_t sec_; // TODO(DDD):接受 ttl 参数。 + void DoInitial() override; + rocksdb::Status s_; +}; + +class PKHMSetCmd : public Cmd { + public: + PKHMSetCmd(const std::string& name, int arity, uint32_t flag) + : Cmd(name, arity, flag, static_cast(AclCategory::PKHASH)) {} + std::vector current_key() const override { + std::vector res; + res.push_back(key_); + return res; + } + void Do() override; + void DoThroughDB() override; + void DoUpdateCache() override; + void Split(const HintKeys& hint_keys) override {}; + void Merge() override {}; + Cmd* Clone() override { return new PKHMSetCmd(*this); } + + private: + std::string key_; + std::vector fvs_; + void DoInitial() override; + rocksdb::Status s_; +}; +class PKHMSetexCmd : public Cmd { + public: + PKHMSetexCmd(const std::string& name, int arity, uint32_t flag) + : Cmd(name, arity, flag, static_cast(AclCategory::PKHASH)) {} + std::vector current_key() const override { + std::vector res; + res.push_back(key_); + return res; + } + void Do() override; + void DoThroughDB() override; + void DoUpdateCache() override; + void Split(const HintKeys& hint_keys) override {}; + void Merge() override {}; + Cmd* Clone() override { return new PKHMSetexCmd(*this); } + + private: + std::string key_; + std::vector fvts_; + void DoInitial() override; + rocksdb::Status s_; +}; + +class PKHMGetCmd : public Cmd { + public: + PKHMGetCmd(const std::string& name, int arity, uint32_t flag) + : Cmd(name, arity, flag, static_cast(AclCategory::PKHASH)) {} + std::vector current_key() const override { + std::vector res; + res.push_back(key_); + return res; + } + void Do() override; + void DoThroughDB() override; + void DoUpdateCache() override; + void Split(const HintKeys& hint_keys) override {}; + void Merge() override {}; + Cmd* Clone() override { return new PKHMGetCmd(*this); } + + private: + std::string key_; + std::vector fields_; + void DoInitial() override; + rocksdb::Status s_; +}; + +class PKHKeysCmd : public Cmd { + public: + PKHKeysCmd(const std::string& name, int arity, uint32_t flag) + : Cmd(name, arity, flag, static_cast(AclCategory::PKHASH)) {} + std::vector current_key() const override { + std::vector res; + res.push_back(key_); + return res; + } + void Do() override; + void DoThroughDB() override; + void DoUpdateCache() override; + void Split(const HintKeys& hint_keys) override {}; + void Merge() override {}; + Cmd* Clone() override { return new PKHKeysCmd(*this); } + + private: + std::string key_; + void DoInitial() override; + rocksdb::Status s_; +}; + +class PKHValsCmd : public Cmd { + public: + PKHValsCmd(const std::string& name, int arity, uint32_t flag) + : Cmd(name, arity, flag, static_cast(AclCategory::PKHASH)) {} + std::vector current_key() const override { + std::vector res; + res.push_back(key_); + return res; + } + void Do() override; + void DoThroughDB() override; + void DoUpdateCache() override; + void Split(const HintKeys& hint_keys) override {}; + void Merge() override {}; + Cmd* Clone() override { return new PKHValsCmd(*this); } + + private: + std::string key_; + void DoInitial() override; + rocksdb::Status s_; +}; + +class PKHGetAllCmd : public Cmd { + public: + PKHGetAllCmd(const std::string& name, int arity, uint32_t flag) + : Cmd(name, arity, flag, static_cast(AclCategory::PKHASH)) {} + std::vector current_key() const override { + std::vector res; + res.push_back(key_); + return res; + } + void Do() override; + void DoThroughDB() override; + void DoUpdateCache() override; + void Split(const HintKeys& hint_keys) override {}; + void Merge() override {}; + Cmd* Clone() override { return new PKHGetAllCmd(*this); } + + private: + std::string key_; + bool is_wt_; + void DoInitial() override; + rocksdb::Status s_; +}; + +class PKHScanCmd : public Cmd { + public: + PKHScanCmd(const std::string& name, int arity, uint32_t flag) + : Cmd(name, arity, flag, static_cast(AclCategory::PKHASH)) {} + std::vector current_key() const override { + std::vector res; + res.push_back(key_); + return res; + } + void Do() override; + void DoThroughDB() override; + void DoUpdateCache() override; + void Split(const HintKeys& hint_keys) override {}; + void Merge() override {}; + Cmd* Clone() override { return new PKHScanCmd(*this); } + + private: + std::string key_, pattern_; + int64_t cursor_, count_; + bool is_wt_; + virtual void Clear() { + pattern_ = "*"; + count_ = 10; + is_wt_ = false; + } + + void DoInitial() override; + rocksdb::Status s_; +}; + #endif diff --git a/src/pika_command.cc b/src/pika_command.cc index 6bc9029b5..ac3cabdb3 100644 --- a/src/pika_command.cc +++ b/src/pika_command.cc @@ -483,32 +483,88 @@ void InitCmdTable(CmdTable* cmd_table) { cmd_table->insert(std::pair>(kCmdNamePKHRScanRange, std::move(pkhrscanrangeptr))); // Pika Expire Hash - std::unique_ptr ehsetptr = std::make_unique(kCmdNamePKHSet, -4, kCmdFlagsWrite | kCmdFlagsPKHash); + std::unique_ptr ehsetptr = std::make_unique( + kCmdNamePKHSet, -4, kCmdFlagsWrite | kCmdFlagsDoThroughDB | kCmdFlagsPKHash | kCmdFlagsFast); cmd_table->insert(std::pair>(kCmdNamePKHSet, std::move(ehsetptr))); ////Ehexpire - std::unique_ptr ehexpireptr = - std::make_unique(kCmdNamePKHExpire, -4, kCmdFlagsWrite | kCmdFlagsPKHash); + std::unique_ptr ehexpireptr = std::make_unique( + kCmdNamePKHExpire, -4, kCmdFlagsWrite | kCmdFlagsDoThroughDB | kCmdFlagsPKHash | kCmdFlagsFast); cmd_table->insert(std::pair>(kCmdNamePKHExpire, std::move(ehexpireptr))); ////Ehexpireat - std::unique_ptr ehexpireatptr = - std::make_unique(kCmdNamePKHExpireat, -4, kCmdFlagsWrite | kCmdFlagsPKHash); + std::unique_ptr ehexpireatptr = std::make_unique( + kCmdNamePKHExpireat, -4, kCmdFlagsWrite | kCmdFlagsDoThroughDB | kCmdFlagsPKHash | kCmdFlagsFast); cmd_table->insert(std::pair>(kCmdNamePKHExpireat, std::move(ehexpireatptr))); ////Ehexpiretime - std::unique_ptr ehexpiretimeptr = - std::make_unique(kCmdNamePKHExpiretime, -4, kCmdFlagsWrite | kCmdFlagsPKHash); + std::unique_ptr ehexpiretimeptr = std::make_unique( + kCmdNamePKHExpiretime, -4, kCmdFlagsWrite | kCmdFlagsDoThroughDB | kCmdFlagsPKHash | kCmdFlagsFast); cmd_table->insert(std::pair>(kCmdNamePKHExpiretime, std::move(ehexpiretimeptr))); ////Ehttl - std::unique_ptr ehttlptr = std::make_unique(kCmdNamePKHTTL, -4, kCmdFlagsWrite | kCmdFlagsPKHash); + std::unique_ptr ehttlptr = std::make_unique( + kCmdNamePKHTTL, -4, kCmdFlagsWrite | kCmdFlagsDoThroughDB | kCmdFlagsPKHash | kCmdFlagsFast); cmd_table->insert(std::pair>(kCmdNamePKHTTL, std::move(ehttlptr))); ////Ehpersist - std::unique_ptr ehpersistptr = - std::make_unique(kCmdNamePKHPersist, -4, kCmdFlagsWrite | kCmdFlagsPKHash); + std::unique_ptr ehpersistptr = std::make_unique( + kCmdNamePKHPersist, -4, kCmdFlagsWrite | kCmdFlagsDoThroughDB | kCmdFlagsPKHash | kCmdFlagsFast); cmd_table->insert(std::pair>(kCmdNamePKHPersist, std::move(ehpersistptr))); ////Ehget - std::unique_ptr ehgetptr = std::make_unique(kCmdNamePKHGet, 3, kCmdFlagsRead | kCmdFlagsPKHash); + std::unique_ptr ehgetptr = std::make_unique( + kCmdNamePKHGet, 3, kCmdFlagsRead | kCmdFlagsDoThroughDB | kCmdFlagsPKHash | kCmdFlagsFast); cmd_table->insert(std::pair>(kCmdNamePKHGet, std::move(ehgetptr))); + // TODO 可能为-6 + std::unique_ptr ehsetexptr = std::make_unique( + kCmdNamePKHSetex, -4, kCmdFlagsWrite | kCmdFlagsDoThroughDB | kCmdFlagsPKHash | kCmdFlagsFast); + cmd_table->insert(std::pair>(kCmdNamePKHSetex, std::move(ehsetexptr))); + + std::unique_ptr ehexistsptr = std::make_unique( + kCmdNamePKHExists, 3, kCmdFlagsRead | kCmdFlagsDoThroughDB | kCmdFlagsPKHash | kCmdFlagsFast); + cmd_table->insert(std::pair>(kCmdNamePKHExists, std::move(ehexistsptr))); + + std::unique_ptr ehdelptr = std::make_unique( + kCmdNamePKHDel, -3, kCmdFlagsWrite | kCmdFlagsDoThroughDB | kCmdFlagsPKHash | kCmdFlagsFast); + cmd_table->insert(std::pair>(kCmdNamePKHDel, std::move(ehdelptr))); + + std::unique_ptr ehlenptr = std::make_unique( + kCmdNamePKHLen, 2, kCmdFlagsRead | kCmdFlagsDoThroughDB | kCmdFlagsPKHash | kCmdFlagsFast); + cmd_table->insert(std::pair>(kCmdNamePKHLen, std::move(ehlenptr))); + + std::unique_ptr ehstrlenptr = std::make_unique( + kCmdNamePKHStrlen, 3, kCmdFlagsRead | kCmdFlagsDoThroughDB | kCmdFlagsPKHash | kCmdFlagsFast); + cmd_table->insert(std::pair>(kCmdNamePKHStrlen, std::move(ehstrlenptr))); + + std::unique_ptr ehincrbyptr = std::make_unique( + kCmdNamePKHIncrby, 4, kCmdFlagsWrite | kCmdFlagsDoThroughDB | kCmdFlagsPKHash | kCmdFlagsFast); + cmd_table->insert(std::pair>(kCmdNamePKHIncrby, std::move(ehincrbyptr))); + + std::unique_ptr ehmsetptr = std::make_unique( + kCmdNamePKHMSet, -4, kCmdFlagsWrite | kCmdFlagsDoThroughDB | kCmdFlagsPKHash | kCmdFlagsFast); + cmd_table->insert(std::pair>(kCmdNamePKHMSet, std::move(ehmsetptr))); + // 参数可能是 -6 需要ttl + std::unique_ptr ehmsetexptr = std::make_unique( + kCmdNamePKHMSetex, -4, kCmdFlagsWrite | kCmdFlagsDoThroughDB | kCmdFlagsPKHash | kCmdFlagsFast); + cmd_table->insert(std::pair>(kCmdNamePKHMSetex, std::move(ehmsetexptr))); + + std::unique_ptr ehmgetptr = std::make_unique( + kCmdNamePKHMGet, -3, kCmdFlagsRead | kCmdFlagsDoThroughDB | kCmdFlagsPKHash | kCmdFlagsFast); + cmd_table->insert(std::pair>(kCmdNamePKHMGet, std::move(ehmgetptr))); + + std::unique_ptr ehkeysptr = std::make_unique( + kCmdNamePKHKeys, 2, kCmdFlagsRead | kCmdFlagsDoThroughDB | kCmdFlagsPKHash | kCmdFlagsFast); + cmd_table->insert(std::pair>(kCmdNamePKHKeys, std::move(ehkeysptr))); + // TODO(DDD) 为啥vals是慢的命令。 + // TODO(DDD) 这些标志位都是啥意思。 + std::unique_ptr ehvalsptr = std::make_unique( + kCmdNamePKHVals, 2, kCmdFlagsRead | kCmdFlagsDoThroughDB | kCmdFlagsPKHash | kCmdFlagsSlow); + cmd_table->insert(std::pair>(kCmdNamePKHVals, std::move(ehvalsptr))); + + std::unique_ptr ehgetallptr = std::make_unique( + kCmdNamePKHGetall, 2, kCmdFlagsRead | kCmdFlagsDoThroughDB | kCmdFlagsPKHash | kCmdFlagsSlow); + cmd_table->insert(std::pair>(kCmdNamePKHGetall, std::move(ehgetallptr))); + + std::unique_ptr ehscanptr = std::make_unique( + kCmdNamePKHScan, -3, kCmdFlagsRead | kCmdFlagsDoThroughDB | kCmdFlagsPKHash | kCmdFlagsSlow); + cmd_table->insert(std::pair>(kCmdNamePKHScan, std::move(ehscanptr))); // List std::unique_ptr lindexptr = std::make_unique( @@ -565,8 +621,8 @@ void InitCmdTable(CmdTable* cmd_table) { std::unique_ptr rpushptr = std::make_unique( kCmdNameRPush, -3, kCmdFlagsWrite | kCmdFlagsList | kCmdFlagsDoThroughDB | kCmdFlagsUpdateCache | kCmdFlagsFast); cmd_table->insert(std::pair>(kCmdNameRPush, std::move(rpushptr))); - std::unique_ptr rpushxptr = - std::make_unique(kCmdNameRPushx, -3, kCmdFlagsWrite | kCmdFlagsList | kCmdFlagsDoThroughDB | kCmdFlagsUpdateCache | kCmdFlagsFast); + std::unique_ptr rpushxptr = std::make_unique( + kCmdNameRPushx, -3, kCmdFlagsWrite | kCmdFlagsList | kCmdFlagsDoThroughDB | kCmdFlagsUpdateCache | kCmdFlagsFast); cmd_table->insert(std::pair>(kCmdNameRPushx, std::move(rpushxptr))); // Zset diff --git a/src/pika_pkhash.cc b/src/pika_pkhash.cc index bec81a22f..4a157f71f 100644 --- a/src/pika_pkhash.cc +++ b/src/pika_pkhash.cc @@ -199,7 +199,7 @@ void PKHTTLCmd::Do() { void PKHGetCmd::DoInitial() { if (!CheckArg(argv_.size())) { - res_.SetRes(CmdRes::kWrongNum, kCmdNameHGet); + res_.SetRes(CmdRes::kWrongNum, kCmdNamePKHSet); return; } key_ = argv_[1]; @@ -238,12 +238,11 @@ void PKHGetCmd::DoThroughDB() { res_.clear(); Do(); } -void PKHGetCmd::DoUpdateCache() { -} +void PKHGetCmd::DoUpdateCache() {} void PKHSetCmd::DoInitial() { if (!CheckArg(argv_.size())) { - res_.SetRes(CmdRes::kWrongNum, kCmdNameHSet); + res_.SetRes(CmdRes::kWrongNum, kCmdNamePKHSet); return; } key_ = argv_[1]; @@ -266,5 +265,466 @@ void PKHSetCmd::Do() { void PKHSetCmd::DoThroughDB() { Do(); } -void PKHSetCmd::DoUpdateCache() { -} \ No newline at end of file +void PKHSetCmd::DoUpdateCache() {} +// 下面是新的命令。 + +void PKHSetexCmd::DoInitial() { + if (!CheckArg(argv_.size())) { + res_.SetRes(CmdRes::kWrongNum, kCmdNamePKHSetex); + return; + } + key_ = argv_[1]; + field_ = argv_[2]; + value_ = argv_[3]; +} + +void PKHSetexCmd::Do() { + int32_t ret = 0; + s_ = db_->storage()->PKHSet(key_, field_, value_, &ret); + if (s_.ok()) { + res_.AppendContent(":" + std::to_string(ret)); + AddSlotKey("h", key_, db_); + } else if (s_.IsInvalidArgument()) { + res_.SetRes(CmdRes::kMultiKey); + } else { + res_.SetRes(CmdRes::kErrOther, s_.ToString()); + } +} + +void PKHSetexCmd::DoThroughDB() { Do(); } + +void PKHSetexCmd::DoUpdateCache() {} + +void PKHExistsCmd::DoInitial() { + if (!CheckArg(argv_.size())) { + res_.SetRes(CmdRes::kWrongNum, kCmdNamePKHExists); + return; + } + key_ = argv_[1]; + field_ = argv_[2]; +} + +void PKHExistsCmd::Do() { + s_ = db_->storage()->HExists(key_, field_); + if (s_.ok()) { + res_.AppendContent(":1"); + } else if (s_.IsInvalidArgument()) { + res_.SetRes(CmdRes::kMultiKey); + } else if (s_.IsNotFound()) { + res_.AppendContent(":0"); + } else { + res_.SetRes(CmdRes::kErrOther, s_.ToString()); + } +} + +void PKHExistsCmd::DoThroughDB() { Do(); } + +void PKHExistsCmd::DoUpdateCache() {} + +void PKHDelCmd::DoInitial() { + if (!CheckArg(argv_.size())) { + res_.SetRes(CmdRes::kWrongNum, kCmdNamePKHDel); + return; + } + key_ = argv_[1]; + auto iter = argv_.begin(); + iter++; + iter++; + fields_.assign(iter, argv_.end()); +} + +void PKHDelCmd::Do() { + s_ = db_->storage()->HDel(key_, fields_, &deleted_); + + if (s_.ok() || s_.IsNotFound()) { + res_.AppendInteger(deleted_); + } else if (s_.IsInvalidArgument()) { + res_.SetRes(CmdRes::kMultiKey); + } else { + res_.SetRes(CmdRes::kErrOther, s_.ToString()); + } +} + +void PKHDelCmd::DoThroughDB() { Do(); } + +void PKHDelCmd::DoUpdateCache() {} + +void PKHLenCmd::DoInitial() { + if (!CheckArg(argv_.size())) { + res_.SetRes(CmdRes::kWrongNum, kCmdNamePKHLen); + return; + } + key_ = argv_[1]; +} + +void PKHLenCmd::Do() { + int32_t len = 0; + s_ = db_->storage()->HLen(key_, &len); + if (s_.ok() || s_.IsNotFound()) { + res_.AppendInteger(len); + } else if (s_.IsInvalidArgument()) { + res_.SetRes(CmdRes::kMultiKey); + } else { + res_.SetRes(CmdRes::kErrOther, "something wrong in hlen"); + } +} + +void PKHLenCmd::DoThroughDB() { Do(); } + +void PKHLenCmd::DoUpdateCache() {} + +void PKHStrLenCmd::DoInitial() { + if (!CheckArg(argv_.size())) { + res_.SetRes(CmdRes::kWrongNum, kCmdNamePKHStrlen); + return; + } + key_ = argv_[1]; + field_ = argv_[2]; +} + +void PKHStrLenCmd::Do() { + int32_t len = 0; + s_ = db_->storage()->HStrlen(key_, field_, &len); + if (s_.ok() || s_.IsNotFound()) { + res_.AppendInteger(len); + } else if (s_.IsInvalidArgument()) { + res_.SetRes(CmdRes::kMultiKey); + } else { + res_.SetRes(CmdRes::kErrOther, "something wrong in hstrlen"); + } +} + +void PKHStrLenCmd::DoThroughDB() { + res_.clear(); + Do(); +} + +void PKHStrLenCmd::DoUpdateCache() {} + +// 111 +void PKHIncrbyCmd::DoInitial() { + if (!CheckArg(argv_.size())) { + res_.SetRes(CmdRes::kWrongNum, kCmdNamePKHIncrby); + return; + } + key_ = argv_[1]; + field_ = argv_[2]; + if (argv_[3].find(' ') != std::string::npos || (pstd::string2int(argv_[3].data(), argv_[3].size(), &by_) == 0)) { + res_.SetRes(CmdRes::kInvalidInt); + return; + } +} + +void PKHIncrbyCmd::Do() { + int64_t new_value = 0; + s_ = db_->storage()->HIncrby(key_, field_, by_, &new_value); + if (s_.ok() || s_.IsNotFound()) { + res_.AppendContent(":" + std::to_string(new_value)); + AddSlotKey("h", key_, db_); + } else if (s_.IsInvalidArgument() && + s_.ToString().substr(0, std::char_traits::length(ErrTypeMessage)) == ErrTypeMessage) { + res_.SetRes(CmdRes::kMultiKey); + } else if (s_.IsCorruption() && s_.ToString() == "Corruption: hash value is not an integer") { + res_.SetRes(CmdRes::kInvalidInt); + } else if (s_.IsInvalidArgument()) { + res_.SetRes(CmdRes::kOverFlow); + } else { + res_.SetRes(CmdRes::kErrOther, s_.ToString()); + } +} + +void PKHIncrbyCmd::DoThroughDB() { Do(); } + +void PKHIncrbyCmd::DoUpdateCache() {} + +// 111 +void PKHMSetCmd::DoInitial() { + if (!CheckArg(argv_.size())) { + res_.SetRes(CmdRes::kWrongNum, kCmdNamePKHMSet); + return; + } + key_ = argv_[1]; + size_t argc = argv_.size(); + if (argc % 2 != 0) { + res_.SetRes(CmdRes::kWrongNum, kCmdNamePKHMSet); + return; + } + size_t index = 2; + fvs_.clear(); + for (; index < argc; index += 2) { + fvs_.push_back({argv_[index], argv_[index + 1]}); + } +} + +void PKHMSetCmd::Do() { + s_ = db_->storage()->HMSet(key_, fvs_); + if (s_.ok()) { + res_.SetRes(CmdRes::kOk); + AddSlotKey("h", key_, db_); + } else if (s_.IsInvalidArgument()) { + res_.SetRes(CmdRes::kMultiKey); + } else { + res_.SetRes(CmdRes::kErrOther, s_.ToString()); + } +} + +void PKHMSetCmd::DoThroughDB() { Do(); } + +void PKHMSetCmd::DoUpdateCache() {} + +// 111 +void PKHMSetexCmd::DoInitial() { + if (!CheckArg(argv_.size())) { + res_.SetRes(CmdRes::kWrongNum, kCmdNamePKHMSetex); + return; + } + key_ = argv_[1]; + size_t argc = argv_.size() - 2; + if (argc % 3 != 0) { + res_.SetRes(CmdRes::kWrongNum, kCmdNamePKHMSetex); + return; + } + size_t index = 2; + fvts_.clear(); + for (; index < argc; index += 3) { + int64_t ttl; + if (!pstd::string2int(argv_[index + 2].data(), argv_[index + 2].size(), &ttl)) { + res_.SetRes(CmdRes::kInvalidInt); + return; + } + fvts_.push_back({argv_[index], argv_[index + 1], static_cast(ttl)}); + } + return; +} + +void PKHMSetexCmd::Do() { + s_ = db_->storage()->PKHMSetex(key_, fvts_); + if (s_.ok()) { + res_.SetRes(CmdRes::kOk); + // TODO(DDD) 这个是干啥的? + AddSlotKey("h", key_, db_); + } else { + res_.SetRes(CmdRes::kErrOther, s_.ToString()); + } + return; +} + +void PKHMSetexCmd::DoThroughDB() { Do(); } + +void PKHMSetexCmd::DoUpdateCache() {} + +// 111 +void PKHMGetCmd::DoInitial() { + if (!CheckArg(argv_.size())) { + res_.SetRes(CmdRes::kWrongNum, kCmdNamePKHMGet); + return; + } + key_ = argv_[1]; + auto iter = argv_.begin(); + iter++; + iter++; + fields_.assign(iter, argv_.end()); +} + +void PKHMGetCmd::Do() { + std::vector vss; + s_ = db_->storage()->HMGet(key_, fields_, &vss); + if (s_.ok() || s_.IsNotFound()) { + res_.AppendArrayLenUint64(vss.size()); + for (const auto& vs : vss) { + if (vs.status.ok()) { + res_.AppendStringLenUint64(vs.value.size()); + res_.AppendContent(vs.value); + } else { + res_.AppendContent("$-1"); + } + } + } else if (s_.IsInvalidArgument()) { + res_.SetRes(CmdRes::kMultiKey); + } else { + res_.SetRes(CmdRes::kErrOther, s_.ToString()); + } +} + +void PKHMGetCmd::DoThroughDB() { Do(); } + +void PKHMGetCmd::DoUpdateCache() {} + +// 111 +void PKHKeysCmd::DoInitial() { + if (!CheckArg(argv_.size())) { + res_.SetRes(CmdRes::kWrongNum, kCmdNamePKHKeys); + return; + } + key_ = argv_[1]; +} + +void PKHKeysCmd::Do() { + std::vector fields; + s_ = db_->storage()->HKeys(key_, &fields); + if (s_.ok() || s_.IsNotFound()) { + res_.AppendArrayLenUint64(fields.size()); + for (const auto& field : fields) { + res_.AppendString(field); + } + } else if (s_.IsInvalidArgument()) { + res_.SetRes(CmdRes::kMultiKey); + } else { + res_.SetRes(CmdRes::kErrOther, s_.ToString()); + } +} + +void PKHKeysCmd::DoThroughDB() { Do(); } + +void PKHKeysCmd::DoUpdateCache() {} + +// 111 +void PKHValsCmd::DoInitial() { + if (!CheckArg(argv_.size())) { + res_.SetRes(CmdRes::kWrongNum, kCmdNamePKHVals); + return; + } + key_ = argv_[1]; +} + +void PKHValsCmd::Do() { + std::vector values; + s_ = db_->storage()->HVals(key_, &values); + if (s_.ok() || s_.IsNotFound()) { + res_.AppendArrayLenUint64(values.size()); + for (const auto& value : values) { + res_.AppendStringLenUint64(value.size()); + res_.AppendContent(value); + } + } else if (s_.IsInvalidArgument()) { + res_.SetRes(CmdRes::kMultiKey); + } else { + res_.SetRes(CmdRes::kErrOther, s_.ToString()); + } +} + +void PKHValsCmd::DoThroughDB() { Do(); } + +void PKHValsCmd::DoUpdateCache() {} + +// 111 +void PKHGetAllCmd::DoInitial() { + if (!CheckArg(argv_.size())) { + res_.SetRes(CmdRes::kWrongNum, kCmdNamePKHGetall); + return; + } + key_ = argv_[1]; +} + +void PKHGetAllCmd::Do() { + int64_t total_fv = 0; + int64_t cursor = 0; + int64_t next_cursor = 0; + size_t raw_limit = g_pika_conf->max_client_response_size(); + std::string raw; + std::vector fvs; + + do { + fvs.clear(); + s_ = db_->storage()->HScan(key_, cursor, "*", PIKA_SCAN_STEP_LENGTH, &fvs, &next_cursor); + if (!s_.ok()) { + raw.clear(); + total_fv = 0; + break; + } else { + for (const auto& fv : fvs) { + RedisAppendLenUint64(raw, fv.field.size(), "$"); + RedisAppendContent(raw, fv.field); + RedisAppendLenUint64(raw, fv.value.size(), "$"); + RedisAppendContent(raw, fv.value); + } + if (raw.size() >= raw_limit) { + res_.SetRes(CmdRes::kErrOther, "Response exceeds the max-client-response-size limit"); + return; + } + total_fv += static_cast(fvs.size()); + cursor = next_cursor; + } + } while (cursor != 0); + + if (s_.ok() || s_.IsNotFound()) { + res_.AppendArrayLen(total_fv * 2); + res_.AppendStringRaw(raw); + } else { + res_.SetRes(CmdRes::kErrOther, s_.ToString()); + } +} + +void PKHGetAllCmd::DoThroughDB() { Do(); } + +void PKHGetAllCmd::DoUpdateCache() {} + +// 111 +void PKHScanCmd::DoInitial() { + if (!CheckArg(argv_.size())) { + res_.SetRes(CmdRes::kWrongNum, kCmdNamePKHScan); + return; + } + key_ = argv_[1]; + if (pstd::string2int(argv_[2].data(), argv_[2].size(), &cursor_) == 0) { + res_.SetRes(CmdRes::kInvalidInt); + return; + } + size_t index = 3; + size_t argc = argv_.size(); + + while (index < argc) { + std::string opt = argv_[index]; + if ((strcasecmp(opt.data(), "match") == 0) || (strcasecmp(opt.data(), "count") == 0)) { + index++; + if (index >= argc) { + res_.SetRes(CmdRes::kSyntaxErr); + return; + } + if (strcasecmp(opt.data(), "match") == 0) { + pattern_ = argv_[index]; + } else if (pstd::string2int(argv_[index].data(), argv_[index].size(), &count_) == 0) { + res_.SetRes(CmdRes::kInvalidInt); + return; + } + } else { + res_.SetRes(CmdRes::kSyntaxErr); + return; + } + index++; + } + if (count_ < 0) { + res_.SetRes(CmdRes::kSyntaxErr); + return; + } +} + +void PKHScanCmd::Do() { + int64_t next_cursor = 0; + std::vector field_values; + auto s = db_->storage()->HScan(key_, cursor_, pattern_, count_, &field_values, &next_cursor); + + if (s.ok() || s.IsNotFound()) { + res_.AppendContent("*2"); + char buf[32]; + int32_t len = pstd::ll2string(buf, sizeof(buf), next_cursor); + res_.AppendStringLen(len); + res_.AppendContent(buf); + + res_.AppendArrayLenUint64(field_values.size() * 2); + for (const auto& field_value : field_values) { + res_.AppendString(field_value.field); + res_.AppendString(field_value.value); + } + } else if (s_.IsInvalidArgument()) { + res_.SetRes(CmdRes::kMultiKey); + } else { + res_.SetRes(CmdRes::kErrOther, s.ToString()); + } +} + +void PKHScanCmd::DoThroughDB() { Do(); } + +void PKHScanCmd::DoUpdateCache() {} + +// 111 \ No newline at end of file diff --git a/src/storage/include/storage/storage.h b/src/storage/include/storage/storage.h index fd63bc813..152536a53 100644 --- a/src/storage/include/storage/storage.h +++ b/src/storage/include/storage/storage.h @@ -121,6 +121,13 @@ struct FieldValue { bool operator==(const FieldValue& fv) const { return (fv.field == field && fv.value == value); } }; +struct FieldValueTTL { + std::string field; + std::string value; + int32_t ttl; + bool operator==(const FieldValueTTL& fv) const { return (fv.field == field && fv.value == value && fv.ttl == ttl); } +}; + struct IdMessage { std::string field; std::string value; @@ -406,7 +413,6 @@ class Storage { int32_t limit, std::vector* field_values, std::string* next_field); // Pika Hash Commands - // redis hash 命令放在这里。 Status PKHExpire(const Slice& key, int32_t ttl, int32_t numfields, const std::vector& fields, std::vector* rets); Status PKHExpireat(const Slice& key, int64_t timestamp, int32_t numfields, const std::vector& fields, @@ -420,31 +426,38 @@ class Storage { std::vector* ttls); Status PKHSet(const Slice& key, const Slice& field, const Slice& value, int32_t* res); - // Status PKHSetnx(const Slice& key, const Slice& field, const Slice& value, int32_t* ret, int32_t ttl = 0); - // Status PKHSetxx(const Slice& key, const Slice& field, const Slice& value, int32_t* ret, int32_t ttl = 0); - // // 支持过期的Set,上面的两个不支持过期的Set,ttl 默认值为 0 - // Status PKHSetex(const Slice& key, const Slice& field, const Slice& value, int32_t ttl); Status PKHGet(const Slice& key, const Slice& field, std::string* value); - // Status PKHExists(const Slice& key, const Slice& field); - // Status PKHDel(const Slice& key, const std::vector& fields, int32_t* ret); - // Status PKHen(const Slice& key, int32_t* ret); + + Status PKHSetex(const Slice& key, const Slice& field, const Slice& value, int32_t ttl); + + Status PKHExists(const Slice& key, const Slice& field); + + Status PKHDel(const Slice& key, const std::vector& fields, int32_t* ret); + + Status PKHLen(const Slice& key, int32_t* ret); + // Status PKHLenForce(const Slice& key, int32_t* ret); - // Status PKHStrlen(const Slice& key, const Slice& field, int32_t* len); - // Status PKHIncrby(const Slice& key, const Slice& field, int64_t value, int64_t* ret, int32_t ttl = 0); - // // 源码中存在,但是文档中,没有实现最小的功能。 - // Status PKHIncrbynxex(const Slice& key, const Slice& field, int64_t value, int64_t* ret, int32_t ttl); - // Status PKHIncrbyxxex(const Slice& key, const Slice& field, int64_t value, int64_t* ret, int32_t ttl); - // Status PKHIncrbyfloat(const Slice& key, const Slice& field, const Slice& by, std::string* new_value, int32_t ttl = - // 0); Status PKHIncrbyfloatnxex(const Slice& key, const Slice& field, const Slice& by, std::string* new_value, - // int32_t ttl); Status PKHIncrbyfloatxxex(const Slice& key, const Slice& field, const Slice& by, std::string* - // new_value, int32_t ttl); Status PKHMSet(const Slice& key, const std::vector& fvs); Status - // PKHMSetex(const Slice& key, const std::vector& fvts); Status PKHMget(const Slice& key, const - // std::vector& fields, std::vector* vss); Status PKHKeys(const Slice& key, - // std::vector* fields); Status PKHVals(const Slice& key, std::vector* values); Status - // PKHGetall(const Slice& key, std::vector* fvts); Status PKHScan(const Slice& key, int64_t cursor, - // const std::string& pattern, int64_t count, - // std::vector* fvts, int64_t* next_cursor); + + Status PKHStrlen(const Slice& key, const Slice& field, int32_t* len); + + Status PKHIncrby(const Slice& key, const Slice& field, int64_t value, int64_t* ret, int32_t ttl = 0); + + Status PKHMSet(const Slice& key, const std::vector& fvs); + + Status PKHMSetex(const Slice& key, const std::vector& fvts); + + Status PKHMGet(const Slice& key, const std::vector& fields, std::vector* vss); + + Status PKHKeys(const Slice& key, std::vector* fields); + + Status PKHVals(const Slice& key, std::vector* values); + + Status PKHGetall(const Slice& key, std::vector* fvts); + + Status PKHScan(const Slice& key, int64_t cursor, const std::string& pattern, int64_t count, + std::vector* fvts, int64_t* next_cursor); + // Status PKHScanx(const Slice& key, const std::string start_field, const std::string& pattern, int64_t count, // std::vector* fvts, std::string* next_field); diff --git a/src/storage/src/redis.h b/src/storage/src/redis.h index 086b8e7ec..4717e9b38 100644 --- a/src/storage/src/redis.h +++ b/src/storage/src/redis.h @@ -261,8 +261,41 @@ class Redis { Status PKHPersist(const Slice& key, int32_t numfields, const std::vector& fields, std::vector* rets); Status PKHGet(const Slice& key, const Slice& field, std::string* value); + Status PKHSet(const Slice& key, const Slice& field, const Slice& value, int32_t* res); + Status PKHSetex(const Slice& key, const Slice& field, const Slice& value, int32_t ttl); + + Status PKHExists(const Slice& key, const Slice& field); + + Status PKHDel(const Slice& key, const std::vector& fields, int32_t* ret); + + Status PKHLen(const Slice& key, int32_t* ret, std::string&& prefetch_meta = {}); + + // Status PKHLenForce(const Slice& key, int32_t* ret); + + Status PKHStrlen(const Slice& key, const Slice& field, int32_t* len); + + Status PKHIncrby(const Slice& key, const Slice& field, int64_t value, int64_t* ret, int32_t ttl = 0); + + Status PKHMSet(const Slice& key, const std::vector& fvs); + + Status PKHMSetex(const Slice& key, const std::vector& fvts); + + Status PKHMGet(const Slice& key, const std::vector& fields, std::vector* vss); + + Status PKHKeys(const Slice& key, std::vector* fields); + + Status PKHVals(const Slice& key, std::vector* values); + + Status PKHGetall(const Slice& key, std::vector* fvts); + + Status PKHScan(const Slice& key, int64_t cursor, const std::string& pattern, int64_t count, + std::vector* fvts, int64_t* next_cursor); + + // Status PKHScanx(const Slice& key, const std::string start_field, const std::string& pattern, int64_t count, + // std::vector* fvts, std::string* next_field); + // Sets Commands Status SAdd(const Slice& key, const std::vector& members, int32_t* ret); Status SCard(const Slice& key, int32_t* ret, std::string&& prefetch_meta = {}); diff --git a/src/storage/src/redis_pkhashes.cc b/src/storage/src/redis_pkhashes.cc index e9d53f7fd..938764936 100644 --- a/src/storage/src/redis_pkhashes.cc +++ b/src/storage/src/redis_pkhashes.cc @@ -74,6 +74,7 @@ Status Redis::PKHSet(const Slice& key, const Slice& field, const Slice& value, i BaseMetaKey base_meta_key(key); Status s = db_->Get(default_read_options_, handles_[kMetaCF], base_meta_key.Encode(), &meta_value); char meta_value_buf[4] = {0}; + // 1. 判断类型是否匹配和key是否过期。 if (s.ok() && !ExpectedMetaValue(DataType::kPKHashes, meta_value)) { if (ExpectedStale(meta_value)) { s = Status::NotFound(); @@ -83,7 +84,7 @@ Status Redis::PKHSet(const Slice& key, const Slice& field, const Slice& value, i ", get type: " + DataTypeStrings[static_cast(GetMetaValueType(meta_value))]); } } - + // 2. 健存在,并且没有过期,则更新。 if (s.ok()) { ParsedHashesMetaValue parsed_hashes_meta_value(&meta_value); if (parsed_hashes_meta_value.Count() == 0 || parsed_hashes_meta_value.IsStale()) { @@ -122,6 +123,7 @@ Status Redis::PKHSet(const Slice& key, const Slice& field, const Slice& value, i return s; } } + // 2.2 键已经过期了。则创建新的key value } else if (s.IsNotFound()) { EncodeFixed32(meta_value_buf, 1); HashesMetaValue hashes_meta_value(DataType::kPKHashes, Slice(meta_value_buf, 4)); @@ -451,4 +453,827 @@ Status Redis::PKHPersist(const Slice& key, int32_t numfields, const std::vector< return s; } +Status Redis::PKHSetex(const Slice& key, const Slice& field, const Slice& value, int32_t ttl) { + if (ttl <= 0) { + return Status::InvalidArgument("invalid expire time"); + } + + rocksdb::WriteBatch batch; + ScopeRecordLock l(lock_mgr_, key); + + int32_t version = 0; + std::string meta_value; + uint32_t statistic = 0; + + BaseMetaKey base_meta_key(key); + Status s = db_->Get(default_read_options_, handles_[kMetaCF], base_meta_key.Encode(), &meta_value); + char meta_value_buf[4] = {0}; + // 1. 判断类型是否匹配和key是否过期。 + if (s.ok() && !ExpectedMetaValue(DataType::kPKHashes, meta_value)) { + if (ExpectedStale(meta_value)) { + s = Status::NotFound(); + } else { + return Status::InvalidArgument("WRONGTYPE, key: " + key.ToString() + + ", expect type: " + DataTypeStrings[static_cast(DataType::kPKHashes)] + + ", get type: " + DataTypeStrings[static_cast(GetMetaValueType(meta_value))]); + } + } + + if (s.ok()) { + ParsedHashesMetaValue parsed_hashes_meta_value(&meta_value); + if (parsed_hashes_meta_value.Count() == 0 || parsed_hashes_meta_value.IsStale()) { + version = parsed_hashes_meta_value.InitialMetaValue(); + parsed_hashes_meta_value.SetCount(1); + batch.Put(handles_[kMetaCF], base_meta_key.Encode(), meta_value); + HashesDataKey data_key(key, version, field); + PKHashDataValue ehashes_value(value); + ehashes_value.SetRelativeTimestamp(ttl); + batch.Put(handles_[kPKHashDataCF], data_key.Encode(), ehashes_value.Encode()); + // *res = 1; // TODO: add return value + } else { + version = parsed_hashes_meta_value.Version(); + std::string data_value; + HashesDataKey hashes_data_key(key, version, field); + s = db_->Get(default_read_options_, handles_[kPKHashDataCF], hashes_data_key.Encode(), &data_value); + if (s.ok()) { + // *res = 0 ;// TODO: add return value + // 判断data_value 和 value 是否相等。 + if (data_value == value.ToString()) { + // TODO(DDD): 更新ttl + return Status::OK(); + } else { + PKHashDataValue ehashes_value(value); + ehashes_value.SetRelativeTimestamp(ttl); + batch.Put(handles_[kPKHashDataCF], hashes_data_key.Encode(), ehashes_value.Encode()); + statistic++; + } + + } else if (s.IsNotFound()) { + if (!parsed_hashes_meta_value.CheckModifyCount(1)) { + return Status::InvalidArgument("hash size overflow"); + } + parsed_hashes_meta_value.ModifyCount(1); + batch.Put(handles_[kMetaCF], key, meta_value); + PKHashDataValue ehashes_value(value); + ehashes_value.SetRelativeTimestamp(ttl); + batch.Put(handles_[kPKHashDataCF], hashes_data_key.Encode(), ehashes_value.Encode()); + // *res = 1; // TODO: add return value + } else { + return s; + } + } + } else if (s.IsNotFound()) { + // char str[4]; + EncodeFixed32(meta_value_buf, 1); + HashesMetaValue hashes_meta_value(DataType::kPKHashes, Slice(meta_value_buf, 4)); + version = hashes_meta_value.UpdateVersion(); + batch.Put(handles_[kMetaCF], key, hashes_meta_value.Encode()); + HashesDataKey data_key(key, version, field); + PKHashDataValue ehashes_value(value); + ehashes_value.SetRelativeTimestamp(ttl); + batch.Put(handles_[kPKHashDataCF], data_key.Encode(), ehashes_value.Encode()); + } else { + return s; + } + + return db_->Write(default_write_options_, &batch); +} + +Status Redis::PKHExists(const Slice& key, const Slice& field) { + std::string value; + return PKHGet(key, field, &value); +} + +Status Redis::PKHDel(const Slice& key, const std::vector& fields, int32_t* ret) { + uint32_t statistic = 0; + std::vector filtered_fields; + std::unordered_set field_set; + for (const auto& iter : fields) { + const std::string& field = iter; + if (field_set.find(field) == field_set.end()) { + field_set.insert(field); + filtered_fields.push_back(iter); + } + } + + rocksdb::WriteBatch batch; + rocksdb::ReadOptions read_options; + const rocksdb::Snapshot* snapshot; + + std::string meta_value; + int32_t del_cnt = 0; + uint64_t version = 0; + ScopeRecordLock l(lock_mgr_, key); + ScopeSnapshot ss(db_, &snapshot); + read_options.snapshot = snapshot; + + BaseMetaKey base_meta_key(key); + Status s = db_->Get(read_options, handles_[kMetaCF], base_meta_key.Encode(), &meta_value); + if (s.ok() && !ExpectedMetaValue(DataType::kPKHashes, meta_value)) { + if (ExpectedStale(meta_value)) { + s = Status::NotFound(); + } else { + return Status::InvalidArgument("WRONGTYPE, key: " + key.ToString() + + ", expect type: " + DataTypeStrings[static_cast(DataType::kPKHashes)] + + ", get type: " + DataTypeStrings[static_cast(GetMetaValueType(meta_value))]); + } + } + if (s.ok()) { + ParsedHashesMetaValue parsed_hashes_meta_value(&meta_value); + if (parsed_hashes_meta_value.IsStale() || parsed_hashes_meta_value.Count() == 0) { + *ret = 0; + return Status::OK(); + } else { + std::string data_value; + version = parsed_hashes_meta_value.Version(); + for (const auto& field : filtered_fields) { + HashesDataKey hashes_data_key(key, version, field); + s = db_->Get(read_options, handles_[kPKHashDataCF], hashes_data_key.Encode(), &data_value); + if (s.ok()) { + del_cnt++; + statistic++; + batch.Delete(handles_[kPKHashDataCF], hashes_data_key.Encode()); + } else if (s.IsNotFound()) { + continue; + } else { + return s; + } + } + *ret = del_cnt; + if (!parsed_hashes_meta_value.CheckModifyCount(-del_cnt)) { + return Status::InvalidArgument("hash size overflow"); + } + parsed_hashes_meta_value.ModifyCount(-del_cnt); + batch.Put(handles_[kMetaCF], base_meta_key.Encode(), meta_value); + } + } else if (s.IsNotFound()) { + *ret = 0; + return Status::OK(); + } else { + return s; + } + s = db_->Write(default_write_options_, &batch); + UpdateSpecificKeyStatistics(DataType::kPKHashes, key.ToString(), statistic); + return s; +} + +Status Redis::PKHLen(const Slice& key, int32_t* ret, std::string&& prefetch_meta) { + *ret = 0; + Status s; + std::string meta_value(std::move(prefetch_meta)); + + // meta_value is empty means no meta value get before, + // we should get meta first + if (meta_value.empty()) { + BaseMetaKey base_meta_key(key); + s = db_->Get(default_read_options_, handles_[kMetaCF], base_meta_key.Encode(), &meta_value); + if (s.ok() && !ExpectedMetaValue(DataType::kPKHashes, meta_value)) { + if (ExpectedStale(meta_value)) { + s = Status::NotFound(); + } else { + return Status::InvalidArgument("WRONGTYPE, key: " + key.ToString() + ", expect type: " + + DataTypeStrings[static_cast(DataType::kPKHashes)] + ", get type: " + + DataTypeStrings[static_cast(GetMetaValueType(meta_value))]); + } + } + } + if (s.ok()) { + ParsedHashesMetaValue parsed_hashes_meta_value(&meta_value); + if (parsed_hashes_meta_value.IsStale()) { + *ret = 0; + return Status::NotFound("Stale"); + } else if (parsed_hashes_meta_value.Count() == 0) { + return Status::NotFound(); + } else { + *ret = parsed_hashes_meta_value.Count(); + } + } else if (s.IsNotFound()) { + *ret = 0; + } + return s; +} + +// Status Redis::PKHLenForce(const Slice& key, int32_t* ret) {} + +Status Redis::PKHStrlen(const Slice& key, const Slice& field, int32_t* len) { + std::string value; + Status s = PKHGet(key, field, &value); + if (s.ok()) { + *len = static_cast(value.size()); + } else { + *len = 0; + } + return s; +} + +Status Redis::PKHIncrby(const Slice& key, const Slice& field, int64_t value, int64_t* ret, int32_t ttl) { + *ret = 0; + rocksdb::WriteBatch batch; + ScopeRecordLock l(lock_mgr_, key); + + uint64_t version = 0; + uint32_t statistic = 0; + std::string old_value; + std::string meta_value; + + BaseMetaKey base_meta_key(key); + Status s = db_->Get(default_read_options_, handles_[kMetaCF], base_meta_key.Encode(), &meta_value); + char value_buf[32] = {0}; + char meta_value_buf[4] = {0}; + if (s.ok() && !ExpectedMetaValue(DataType::kPKHashes, meta_value)) { + if (ExpectedStale(meta_value)) { + s = Status::NotFound(); + } else { + return Status::InvalidArgument("WRONGTYPE, key: " + key.ToString() + + ", expect type: " + DataTypeStrings[static_cast(DataType::kPKHashes)] + + ", get type: " + DataTypeStrings[static_cast(GetMetaValueType(meta_value))]); + } + } + if (s.ok()) { + ParsedHashesMetaValue parsed_hashes_meta_value(&meta_value); + if (parsed_hashes_meta_value.IsStale() || parsed_hashes_meta_value.Count() == 0) { + version = parsed_hashes_meta_value.UpdateVersion(); + parsed_hashes_meta_value.SetCount(1); + parsed_hashes_meta_value.SetEtime(0); + batch.Put(handles_[kMetaCF], base_meta_key.Encode(), meta_value); + HashesDataKey hashes_data_key(key, version, field); + Int64ToStr(value_buf, 32, value); + BaseDataValue internal_value(value_buf); + batch.Put(handles_[kHashesDataCF], hashes_data_key.Encode(), internal_value.Encode()); + *ret = value; + } else { + version = parsed_hashes_meta_value.Version(); + HashesDataKey hashes_data_key(key, version, field); + s = db_->Get(default_read_options_, handles_[kHashesDataCF], hashes_data_key.Encode(), &old_value); + if (s.ok()) { + ParsedBaseDataValue parsed_internal_value(&old_value); + parsed_internal_value.StripSuffix(); + int64_t ival = 0; + if (StrToInt64(old_value.data(), old_value.size(), &ival) == 0) { + return Status::Corruption("hash value is not an integer"); + } + if ((value >= 0 && LLONG_MAX - value < ival) || (value < 0 && LLONG_MIN - value > ival)) { + return Status::InvalidArgument("Overflow"); + } + *ret = ival + value; + Int64ToStr(value_buf, 32, *ret); + BaseDataValue internal_value(value_buf); + batch.Put(handles_[kHashesDataCF], hashes_data_key.Encode(), internal_value.Encode()); + statistic++; + } else if (s.IsNotFound()) { + Int64ToStr(value_buf, 32, value); + if (!parsed_hashes_meta_value.CheckModifyCount(1)) { + return Status::InvalidArgument("hash size overflow"); + } + BaseDataValue internal_value(value_buf); + parsed_hashes_meta_value.ModifyCount(1); + batch.Put(handles_[kMetaCF], base_meta_key.Encode(), meta_value); + batch.Put(handles_[kHashesDataCF], hashes_data_key.Encode(), internal_value.Encode()); + *ret = value; + } else { + return s; + } + } + } else if (s.IsNotFound()) { + EncodeFixed32(meta_value_buf, 1); + HashesMetaValue hashes_meta_value(DataType::kPKHashes, Slice(meta_value_buf, 4)); + version = hashes_meta_value.UpdateVersion(); + batch.Put(handles_[kMetaCF], base_meta_key.Encode(), hashes_meta_value.Encode()); + HashesDataKey hashes_data_key(key, version, field); + + Int64ToStr(value_buf, 32, value); + BaseDataValue internal_value(value_buf); + batch.Put(handles_[kHashesDataCF], hashes_data_key.Encode(), internal_value.Encode()); + *ret = value; + } else { + return s; + } + s = db_->Write(default_write_options_, &batch); + UpdateSpecificKeyStatistics(DataType::kPKHashes, key.ToString(), statistic); + return s; +} + +Status Redis::PKHMSet(const Slice& key, const std::vector& fvs) { + uint32_t statistic = 0; + std::unordered_set fields; + std::vector filtered_fvs; + for (auto iter = fvs.rbegin(); iter != fvs.rend(); ++iter) { + std::string field = iter->field; + if (fields.find(field) == fields.end()) { + fields.insert(field); + filtered_fvs.push_back(*iter); + } + } + + rocksdb::WriteBatch batch; + ScopeRecordLock l(lock_mgr_, key); + + uint64_t version = 0; + std::string meta_value; + + BaseMetaKey base_meta_key(key); + Status s = db_->Get(default_read_options_, handles_[kMetaCF], base_meta_key.Encode(), &meta_value); + char meta_value_buf[4] = {0}; + if (s.ok() && !ExpectedMetaValue(DataType::kPKHashes, meta_value)) { + if (ExpectedStale(meta_value)) { + s = Status::NotFound(); + } else { + return Status::InvalidArgument("WRONGTYPE, key: " + key.ToString() + + ", expect type: " + DataTypeStrings[static_cast(DataType::kPKHashes)] + + ", get type: " + DataTypeStrings[static_cast(GetMetaValueType(meta_value))]); + } + } + if (s.ok()) { + ParsedHashesMetaValue parsed_hashes_meta_value(&meta_value); + if (parsed_hashes_meta_value.IsStale() || parsed_hashes_meta_value.Count() == 0) { + version = parsed_hashes_meta_value.InitialMetaValue(); + if (!parsed_hashes_meta_value.check_set_count(static_cast(filtered_fvs.size()))) { + return Status::InvalidArgument("hash size overflow"); + } + parsed_hashes_meta_value.SetCount(static_cast(filtered_fvs.size())); + batch.Put(handles_[kMetaCF], base_meta_key.Encode(), meta_value); + for (const auto& fv : filtered_fvs) { + HashesDataKey hashes_data_key(key, version, fv.field); + BaseDataValue inter_value(fv.value); + batch.Put(handles_[kHashesDataCF], hashes_data_key.Encode(), inter_value.Encode()); + } + } else { + int32_t count = 0; + std::string data_value; + version = parsed_hashes_meta_value.Version(); + for (const auto& fv : filtered_fvs) { + HashesDataKey hashes_data_key(key, version, fv.field); + BaseDataValue inter_value(fv.value); + s = db_->Get(default_read_options_, handles_[kHashesDataCF], hashes_data_key.Encode(), &data_value); + if (s.ok()) { + statistic++; + batch.Put(handles_[kHashesDataCF], hashes_data_key.Encode(), inter_value.Encode()); + } else if (s.IsNotFound()) { + count++; + batch.Put(handles_[kHashesDataCF], hashes_data_key.Encode(), inter_value.Encode()); + } else { + return s; + } + } + if (!parsed_hashes_meta_value.CheckModifyCount(count)) { + return Status::InvalidArgument("hash size overflow"); + } + parsed_hashes_meta_value.ModifyCount(count); + batch.Put(handles_[kMetaCF], base_meta_key.Encode(), meta_value); + } + } else if (s.IsNotFound()) { + EncodeFixed32(meta_value_buf, filtered_fvs.size()); + HashesMetaValue hashes_meta_value(DataType::kPKHashes, Slice(meta_value_buf, 4)); + version = hashes_meta_value.UpdateVersion(); + batch.Put(handles_[kMetaCF], base_meta_key.Encode(), hashes_meta_value.Encode()); + for (const auto& fv : filtered_fvs) { + HashesDataKey hashes_data_key(key, version, fv.field); + BaseDataValue inter_value(fv.value); + batch.Put(handles_[kHashesDataCF], hashes_data_key.Encode(), inter_value.Encode()); + } + } + s = db_->Write(default_write_options_, &batch); + UpdateSpecificKeyStatistics(DataType::kPKHashes, key.ToString(), statistic); + return s; +} +// TODO(DDD) field 带有过期时间。 +Status Redis::PKHMSetex(const Slice& key, const std::vector& fvts) { + uint32_t statistic = 0; + std::unordered_set fields; + std::vector filtered_fvs; + for (auto iter = fvts.rbegin(); iter != fvts.rend(); ++iter) { + std::string field = iter->field; + if (fields.find(field) == fields.end()) { + fields.insert(field); + filtered_fvs.push_back(*iter); + } + } + + rocksdb::WriteBatch batch; + ScopeRecordLock l(lock_mgr_, key); + + int32_t version = 0; + std::string meta_value; + + BaseMetaKey base_meta_key(key); + Status s = db_->Get(default_read_options_, handles_[kMetaCF], base_meta_key.Encode(), &meta_value); + char meta_value_buf[4] = {0}; + if (s.ok() && !ExpectedMetaValue(DataType::kPKHashes, meta_value)) { + if (ExpectedStale(meta_value)) { + s = Status::NotFound(); + } else { + return Status::InvalidArgument("WRONGTYPE, key: " + key.ToString() + + ", expect type: " + DataTypeStrings[static_cast(DataType::kPKHashes)] + + ", get type: " + DataTypeStrings[static_cast(GetMetaValueType(meta_value))]); + } + } + + if (s.ok()) { + ParsedHashesMetaValue parsed_hashes_meta_value(&meta_value); + if (parsed_hashes_meta_value.Count() == 0 || parsed_hashes_meta_value.IsStale()) { + version = parsed_hashes_meta_value.InitialMetaValue(); + if (!parsed_hashes_meta_value.check_set_count(static_cast(filtered_fvs.size()))) { + return Status::InvalidArgument("hash size overflow"); + } + parsed_hashes_meta_value.SetCount(static_cast(filtered_fvs.size())); + // parsed_hashes_meta_value.set_timestamp(0); + batch.Put(handles_[kMetaCF], base_meta_key.Encode(), meta_value); + for (const auto& fv : filtered_fvs) { + HashesDataKey hashes_data_key(key, version, fv.field); + PKHashDataValue ehashes_value(fv.value); + if (fv.ttl > 0) { + ehashes_value.SetRelativeTimestamp(fv.ttl); + } + batch.Put(handles_[kPKHashDataCF], hashes_data_key.Encode(), ehashes_value.Encode()); + } + } else { + int32_t count = 0; + std::string data_value; + version = parsed_hashes_meta_value.Version(); + for (const auto& fv : filtered_fvs) { + HashesDataKey hashes_data_key(key, version, fv.field); + s = db_->Get(default_read_options_, handles_[kPKHashDataCF], hashes_data_key.Encode(), &data_value); + if (s.ok()) { + statistic++; + PKHashDataValue ehashes_value(fv.value); + if (fv.ttl > 0) { + ehashes_value.SetRelativeTimestamp(fv.ttl); + } + batch.Put(handles_[kPKHashDataCF], hashes_data_key.Encode(), ehashes_value.Encode()); + } else if (s.IsNotFound()) { + count++; + PKHashDataValue ehashes_value(fv.value); + if (fv.ttl > 0) { + ehashes_value.SetRelativeTimestamp(fv.ttl); + } + batch.Put(handles_[kPKHashDataCF], hashes_data_key.Encode(), ehashes_value.Encode()); + } else { + return s; + } + } + + if (!parsed_hashes_meta_value.CheckModifyCount(count)) { + return Status::InvalidArgument("hash size overflow"); + } + + parsed_hashes_meta_value.ModifyCount(count); + batch.Put(handles_[kMetaCF], base_meta_key.Encode(), meta_value); + } + } else if (s.IsNotFound()) { + // char str[4]; + EncodeFixed32(meta_value_buf, filtered_fvs.size()); + HashesMetaValue hashes_meta_value(DataType::kPKHashes, Slice(meta_value_buf, 4)); + version = hashes_meta_value.UpdateVersion(); + batch.Put(handles_[kMetaCF], base_meta_key.Encode(), hashes_meta_value.Encode()); + for (const auto& fv : filtered_fvs) { + HashesDataKey hashes_data_key(key, version, fv.field); + PKHashDataValue ehashes_value(fv.value); + if (fv.ttl > 0) { + ehashes_value.SetRelativeTimestamp(fv.ttl); + } + batch.Put(handles_[kPKHashDataCF], hashes_data_key.Encode(), ehashes_value.Encode()); + } + } + s = db_->Write(default_write_options_, &batch); + UpdateSpecificKeyStatistics(DataType::kPKHashes, key.ToString(), statistic); + return s; +} + +Status Redis::PKHMGet(const Slice& key, const std::vector& fields, std::vector* vss) { + vss->clear(); + + uint64_t version = 0; + bool is_stale = false; + std::string value; + std::string meta_value; + rocksdb::ReadOptions read_options; + const rocksdb::Snapshot* snapshot; + ScopeSnapshot ss(db_, &snapshot); + read_options.snapshot = snapshot; + BaseMetaKey base_meta_key(key); + Status s = db_->Get(read_options, handles_[kMetaCF], base_meta_key.Encode(), &meta_value); + if (s.ok() && !ExpectedMetaValue(DataType::kPKHashes, meta_value)) { + if (ExpectedStale(meta_value)) { + s = Status::NotFound(); + } else { + return Status::InvalidArgument("WRONGTYPE, key: " + key.ToString() + + ", expect type: " + DataTypeStrings[static_cast(DataType::kPKHashes)] + + ", get type: " + DataTypeStrings[static_cast(GetMetaValueType(meta_value))]); + } + } + if (s.ok()) { + ParsedHashesMetaValue parsed_hashes_meta_value(&meta_value); + if ((is_stale = parsed_hashes_meta_value.IsStale()) || parsed_hashes_meta_value.Count() == 0) { + for (size_t idx = 0; idx < fields.size(); ++idx) { + vss->push_back({std::string(), Status::NotFound()}); + } + return Status::NotFound(is_stale ? "Stale" : ""); + } else { + version = parsed_hashes_meta_value.Version(); + for (const auto& field : fields) { + HashesDataKey hashes_data_key(key, version, field); + s = db_->Get(read_options, handles_[kHashesDataCF], hashes_data_key.Encode(), &value); + if (s.ok()) { + ParsedBaseDataValue parsed_internal_value(&value); + parsed_internal_value.StripSuffix(); + vss->push_back({value, Status::OK()}); + } else if (s.IsNotFound()) { + vss->push_back({std::string(), Status::NotFound()}); + } else { + vss->clear(); + return s; + } + } + } + return Status::OK(); + } else if (s.IsNotFound()) { + for (size_t idx = 0; idx < fields.size(); ++idx) { + vss->push_back({std::string(), Status::NotFound()}); + } + } + return s; +} + +Status Redis::PKHKeys(const Slice& key, std::vector* fields) { + rocksdb::ReadOptions read_options; + const rocksdb::Snapshot* snapshot; + + std::string meta_value; + uint64_t version = 0; + ScopeSnapshot ss(db_, &snapshot); + read_options.snapshot = snapshot; + + BaseMetaKey base_meta_key(key); + Status s = db_->Get(read_options, handles_[kMetaCF], base_meta_key.Encode(), &meta_value); + if (s.ok() && !ExpectedMetaValue(DataType::kPKHashes, meta_value)) { + if (ExpectedStale(meta_value)) { + s = Status::NotFound(); + } else { + return Status::InvalidArgument("WRONGTYPE, key: " + key.ToString() + + ", expect type: " + DataTypeStrings[static_cast(DataType::kPKHashes)] + + ", get type: " + DataTypeStrings[static_cast(GetMetaValueType(meta_value))]); + } + } + if (s.ok()) { + ParsedHashesMetaValue parsed_hashes_meta_value(&meta_value); + if (parsed_hashes_meta_value.IsStale()) { + return Status::NotFound("Stale"); + } else if (parsed_hashes_meta_value.Count() == 0) { + return Status::NotFound(); + } else { + version = parsed_hashes_meta_value.Version(); + HashesDataKey hashes_data_key(key, version, ""); + Slice prefix = hashes_data_key.EncodeSeekKey(); + KeyStatisticsDurationGuard guard(this, DataType::kPKHashes, key.ToString()); + auto iter = db_->NewIterator(read_options, handles_[kHashesDataCF]); + for (iter->Seek(prefix); iter->Valid() && iter->key().starts_with(prefix); iter->Next()) { + ParsedHashesDataKey parsed_hashes_data_key(iter->key()); + fields->push_back(parsed_hashes_data_key.field().ToString()); + } + delete iter; + } + } + return s; +} + +Status Redis::PKHVals(const Slice& key, std::vector* values) { + rocksdb::ReadOptions read_options; + const rocksdb::Snapshot* snapshot; + + std::string meta_value; + uint64_t version = 0; + ScopeSnapshot ss(db_, &snapshot); + read_options.snapshot = snapshot; + + BaseMetaKey base_meta_key(key); + Status s = db_->Get(read_options, handles_[kMetaCF], base_meta_key.Encode(), &meta_value); + if (s.ok() && !ExpectedMetaValue(DataType::kPKHashes, meta_value)) { + if (ExpectedStale(meta_value)) { + s = Status::NotFound(); + } else { + return Status::InvalidArgument("WRONGTYPE, key: " + key.ToString() + + ", expect type: " + DataTypeStrings[static_cast(DataType::kPKHashes)] + + ", get type: " + DataTypeStrings[static_cast(GetMetaValueType(meta_value))]); + } + } + if (s.ok()) { + ParsedHashesMetaValue parsed_hashes_meta_value(&meta_value); + if (parsed_hashes_meta_value.IsStale()) { + return Status::NotFound("Stale"); + } else if (parsed_hashes_meta_value.Count() == 0) { + return Status::NotFound(); + } else { + version = parsed_hashes_meta_value.Version(); + HashesDataKey hashes_data_key(key, version, ""); + Slice prefix = hashes_data_key.EncodeSeekKey(); + KeyStatisticsDurationGuard guard(this, DataType::kPKHashes, key.ToString()); + auto iter = db_->NewIterator(read_options, handles_[kHashesDataCF]); + for (iter->Seek(prefix); iter->Valid() && iter->key().starts_with(prefix); iter->Next()) { + ParsedBaseDataValue parsed_internal_value(iter->value()); + values->push_back(parsed_internal_value.UserValue().ToString()); + } + delete iter; + } + } + return s; +} + +Status Redis::PKHGetall(const Slice& key, std::vector* fvts) { + rocksdb::ReadOptions read_options; + const rocksdb::Snapshot* snapshot; + + std::string meta_value; + uint64_t version = 0; + ScopeSnapshot ss(db_, &snapshot); + read_options.snapshot = snapshot; + + BaseMetaKey base_meta_key(key); + Status s = db_->Get(read_options, handles_[kMetaCF], base_meta_key.Encode(), &meta_value); + if (s.ok() && !ExpectedMetaValue(DataType::kPKHashes, meta_value)) { + if (ExpectedStale(meta_value)) { + s = Status::NotFound(); + } else { + return Status::InvalidArgument("WRONGTYPE, key: " + key.ToString() + + ", expect type: " + DataTypeStrings[static_cast(DataType::kPKHashes)] + + ", get type: " + DataTypeStrings[static_cast(GetMetaValueType(meta_value))]); + } + } + if (s.ok()) { + ParsedHashesMetaValue parsed_hashes_meta_value(&meta_value); + if (parsed_hashes_meta_value.IsStale()) { + return Status::NotFound("Stale"); + } else if (parsed_hashes_meta_value.Count() == 0) { + return Status::NotFound(); + } else { + version = parsed_hashes_meta_value.Version(); + HashesDataKey hashes_data_key(key, version, ""); + Slice prefix = hashes_data_key.EncodeSeekKey(); + KeyStatisticsDurationGuard guard(this, DataType::kPKHashes, key.ToString()); + auto iter = db_->NewIterator(read_options, handles_[kHashesDataCF]); + for (iter->Seek(prefix); iter->Valid() && iter->key().starts_with(prefix); iter->Next()) { + ParsedHashesDataKey parsed_hashes_data_key(iter->key()); + ParsedBaseDataValue parsed_internal_value(iter->value()); + fvts->push_back({parsed_hashes_data_key.field().ToString(), parsed_internal_value.UserValue().ToString()}); + } + delete iter; + } + } + return s; +} + +Status Redis::PKHScan(const Slice& key, int64_t cursor, const std::string& pattern, int64_t count, + std::vector* fvts, int64_t* next_cursor) { + *next_cursor = 0; + fvts->clear(); + if (cursor < 0) { + *next_cursor = 0; + return Status::OK(); + } + + int64_t rest = count; + int64_t step_length = count; + rocksdb::ReadOptions read_options; + const rocksdb::Snapshot* snapshot; + + std::string meta_value; + ScopeSnapshot ss(db_, &snapshot); + read_options.snapshot = snapshot; + + BaseMetaKey base_meta_key(key); + Status s = db_->Get(read_options, handles_[kMetaCF], base_meta_key.Encode(), &meta_value); + if (s.ok() && !ExpectedMetaValue(DataType::kPKHashes, meta_value)) { + if (ExpectedStale(meta_value)) { + s = Status::NotFound(); + } else { + return Status::InvalidArgument("WRONGTYPE, key: " + key.ToString() + + ", expect type: " + DataTypeStrings[static_cast(DataType::kPKHashes)] + + ", get type: " + DataTypeStrings[static_cast(GetMetaValueType(meta_value))]); + } + } + if (s.ok()) { + ParsedHashesMetaValue parsed_hashes_meta_value(&meta_value); + if (parsed_hashes_meta_value.IsStale() || parsed_hashes_meta_value.Count() == 0) { + *next_cursor = 0; + return Status::NotFound(); + } else { + std::string sub_field; + std::string start_point; + uint64_t version = parsed_hashes_meta_value.Version(); + s = GetScanStartPoint(DataType::kPKHashes, key, pattern, cursor, &start_point); + if (s.IsNotFound()) { + cursor = 0; + if (isTailWildcard(pattern)) { + start_point = pattern.substr(0, pattern.size() - 1); + } + } + if (isTailWildcard(pattern)) { + sub_field = pattern.substr(0, pattern.size() - 1); + } + + HashesDataKey hashes_data_prefix(key, version, sub_field); + HashesDataKey hashes_start_data_key(key, version, start_point); + std::string prefix = hashes_data_prefix.EncodeSeekKey().ToString(); + KeyStatisticsDurationGuard guard(this, DataType::kPKHashes, key.ToString()); + rocksdb::Iterator* iter = db_->NewIterator(read_options, handles_[kHashesDataCF]); + // 迭代器 也是给出key value 然后再进行解释。 + for (iter->Seek(hashes_start_data_key.Encode()); iter->Valid() && rest > 0 && iter->key().starts_with(prefix); + iter->Next()) { + ParsedHashesDataKey parsed_hashes_data_key(iter->key()); + std::string field = parsed_hashes_data_key.field().ToString(); + if (StringMatch(pattern.data(), pattern.size(), field.data(), field.size(), 0) != 0) { + ParsedPKHashDataValue parsed_internal_value(iter->value()); + + if (!parsed_internal_value.IsStale()) { + int64_t ttl; + int64_t timestamp = parsed_internal_value.Etime(); + if (timestamp == 0) { + ttl = -1; + } else { + int64_t curtime; + rocksdb::Env::Default()->GetCurrentTime(&curtime); + ttl = (timestamp - curtime >= 0) ? timestamp - curtime : -2; + } + fvts->push_back({field, parsed_internal_value.UserValue().ToString(), static_cast(ttl)}); + rest--; + } + } + rest--; + } + + if (iter->Valid() && (iter->key().compare(prefix) <= 0 || iter->key().starts_with(prefix))) { + *next_cursor = cursor + step_length; + ParsedHashesDataKey parsed_hashes_data_key(iter->key()); + std::string next_field = parsed_hashes_data_key.field().ToString(); + StoreScanNextPoint(DataType::kPKHashes, key, pattern, *next_cursor, next_field); + } else { + *next_cursor = 0; + } + delete iter; + } + } else { + *next_cursor = 0; + return s; + } + return Status::OK(); +} + +// Status Redis::PKHScanx(const Slice& key, const std::string start_field, const std::string& pattern, int64_t count, +// std::vector* fvts, std::string* next_field) { +// next_field->clear(); +// fvts->clear(); +// int64_t rest = count; +// std::string meta_value; +// rocksdb::ReadOptions read_options; +// const rocksdb::Snapshot* snapshot; +// ScopeSnapshot ss(db_, &snapshot); +// read_options.snapshot = snapshot; +// BaseMetaKey base_meta_key(key); +// Status s = db_->Get(read_options, handles_[kMetaCF], base_meta_key.Encode(), &meta_value); +// if (s.ok() && !ExpectedMetaValue(DataType::kPKHashes, meta_value)) { +// if (ExpectedStale(meta_value)) { +// s = Status::NotFound(); +// } else { +// return Status::InvalidArgument("WRONGTYPE, key: " + key.ToString() + +// ", expect type: " + DataTypeStrings[static_cast(DataType::kPKHashes)] + +// ", get type: " + +// DataTypeStrings[static_cast(GetMetaValueType(meta_value))]); +// } +// } +// if (s.ok()) { +// ParsedHashesMetaValue parsed_hashes_meta_value(&meta_value); +// if (parsed_hashes_meta_value.IsStale() || parsed_hashes_meta_value.Count() == 0) { +// *next_field = ""; +// return Status::NotFound(); +// } else { +// uint64_t version = parsed_hashes_meta_value.Version(); +// HashesDataKey hashes_data_prefix(key, version, Slice()); +// HashesDataKey hashes_start_data_key(key, version, start_field); +// std::string prefix = hashes_data_prefix.EncodeSeekKey().ToString(); +// KeyStatisticsDurationGuard guard(this, DataType::kPKHashes, key.ToString()); +// rocksdb::Iterator* iter = db_->NewIterator(read_options, handles_[kHashesDataCF]); +// for (iter->Seek(hashes_start_data_key.Encode()); iter->Valid() && rest > 0 && iter->key().starts_with(prefix); +// iter->Next()) { +// ParsedHashesDataKey parsed_hashes_data_key(iter->key()); +// std::string field = parsed_hashes_data_key.field().ToString(); +// if (StringMatch(pattern.data(), pattern.size(), field.data(), field.size(), 0) != 0) { +// ParsedBaseDataValue parsed_value(iter->value()); +// fvts->emplace_back(field, parsed_value.UserValue().ToString()); +// } +// rest--; +// } +// if (iter->Valid() && iter->key().starts_with(prefix)) { +// ParsedHashesDataKey parsed_hashes_data_key(iter->key()); +// *next_field = parsed_hashes_data_key.field().ToString(); +// } else { +// *next_field = ""; +// } +// delete iter; +// } +// } else { +// *next_field = ""; +// return s; +// } +// return Status::OK(); +// } + } // namespace storage diff --git a/src/storage/src/storage.cc b/src/storage/src/storage.cc index 69ae575c5..9f9dcf809 100644 --- a/src/storage/src/storage.cc +++ b/src/storage/src/storage.cc @@ -517,6 +517,83 @@ Status Storage::PKHSet(const Slice& key, const Slice& field, const Slice& value, return inst->PKHSet(key, field, value, res); } +Status Storage::PKHSetex(const Slice& key, const Slice& field, const Slice& value, int32_t ttl) { + auto& inst = GetDBInstance(key); + return inst->PKHSetex(key, field, value, ttl); +} + +Status Storage::PKHExists(const Slice& key, const Slice& field) { + auto& inst = GetDBInstance(key); + return inst->PKHExists(key, field); +} + +Status Storage::PKHDel(const Slice& key, const std::vector& fields, int32_t* ret) { + auto& inst = GetDBInstance(key); + return inst->PKHDel(key, fields, ret); +} + +Status Storage::PKHLen(const Slice& key, int32_t* ret) { + auto& inst = GetDBInstance(key); + return inst->PKHLen(key, ret); +} + +// Status Storage::PKHLenForce(const Slice& key, int32_t* ret) { +// auto& inst = GetDBInstance(key); +// return inst->PKHLenForce(key, ret); +// } + +Status Storage::PKHStrlen(const Slice& key, const Slice& field, int32_t* len) { + auto& inst = GetDBInstance(key); + return inst->PKHStrlen(key, field, len); +} + +Status Storage::PKHIncrby(const Slice& key, const Slice& field, int64_t value, int64_t* ret, int32_t ttl) { + auto& inst = GetDBInstance(key); + return inst->PKHIncrby(key, field, value, ret, ttl); +} + +Status Storage::PKHMSet(const Slice& key, const std::vector& fvs) { + auto& inst = GetDBInstance(key); + return inst->PKHMSet(key, fvs); +} + +Status Storage::PKHMSetex(const Slice& key, const std::vector& fvts) { + auto& inst = GetDBInstance(key); + return inst->PKHMSetex(key, fvts); +} + +Status Storage::PKHMGet(const Slice& key, const std::vector& fields, std::vector* vss) { + auto& inst = GetDBInstance(key); + return inst->PKHMGet(key, fields, vss); +} + +Status Storage::PKHKeys(const Slice& key, std::vector* fields) { + auto& inst = GetDBInstance(key); + return inst->PKHKeys(key, fields); +} + +Status Storage::PKHVals(const Slice& key, std::vector* values) { + auto& inst = GetDBInstance(key); + return inst->PKHVals(key, values); +} + +Status Storage::PKHGetall(const Slice& key, std::vector* fvts) { + auto& inst = GetDBInstance(key); + return inst->PKHGetall(key, fvts); +} + +Status Storage::PKHScan(const Slice& key, int64_t cursor, const std::string& pattern, int64_t count, + std::vector* fvts, int64_t* next_cursor) { + auto& inst = GetDBInstance(key); + return inst->PKHScan(key, cursor, pattern, count, fvts, next_cursor); +} + +// Status Storage::PKHScanx(const Slice& key, const std::string start_field, const std::string& pattern, int64_t count, +// std::vector* fvts, std::string* next_field) { +// auto& inst = GetDBInstance(key); +// return inst->PKHScanx(key, start_field, pattern, count, fvts, next_field); +// } + // Sets Commands Status Storage::SAdd(const Slice& key, const std::vector& members, int32_t* ret) { auto& inst = GetDBInstance(key); diff --git a/src/storage/tests/pkhashes_test.cc b/src/storage/tests/pkhashes_test.cc index 6864c9a87..d8516211e 100644 --- a/src/storage/tests/pkhashes_test.cc +++ b/src/storage/tests/pkhashes_test.cc @@ -100,7 +100,6 @@ static bool make_expired(storage::Storage* const db, const Slice& key) { std::this_thread::sleep_for(std::chrono::milliseconds(2000)); return true; } - // HDel TEST_F(PKHashesTest, HDel) { int32_t ret = 0; @@ -140,6 +139,391 @@ TEST_F(PKHashesTest, HDel) { ASSERT_EQ(ret, 0); } +// HExists +TEST_F(PKHashesTest, HExistsTest) { + int32_t ret; + s = db.HSet("HEXIST_KEY", "HEXIST_FIELD", "HEXIST_VALUE", &ret); + ASSERT_TRUE(s.ok()); + + s = db.HExists("HEXIST_KEY", "HEXIST_FIELD"); + ASSERT_TRUE(s.ok()); + + // If key does not exist. + s = db.HExists("HEXIST_NOT_EXIST_KEY", "HEXIST_FIELD"); + ASSERT_TRUE(s.IsNotFound()); + + // If field is not present in the hash + s = db.HExists("HEXIST_KEY", "HEXIST_NOT_EXIST_FIELD"); + ASSERT_TRUE(s.IsNotFound()); +} + +// HGet +TEST_F(PKHashesTest, HGetTest) { + int32_t ret = 0; + std::string value; + s = db.HSet("HGET_KEY", "HGET_TEST_FIELD", "HGET_TEST_VALUE", &ret); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(ret, 1); + s = db.HGet("HGET_KEY", "HGET_TEST_FIELD", &value); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(value, "HGET_TEST_VALUE"); + + // If key does not exist. + s = db.HGet("HGET_NOT_EXIST_KEY", "HGET_TEST_FIELD", &value); + ASSERT_TRUE(s.IsNotFound()); + + // If field is not present in the hash + s = db.HGet("HGET_KEY", "HGET_NOT_EXIST_FIELD", &value); + ASSERT_TRUE(s.IsNotFound()); +} + +// HGetall +TEST_F(PKHashesTest, HGetall) { + int32_t ret = 0; + std::vector mid_fvs_in; + mid_fvs_in.push_back({"MID_TEST_FIELD1", "MID_TEST_VALUE1"}); + mid_fvs_in.push_back({"MID_TEST_FIELD2", "MID_TEST_VALUE2"}); + mid_fvs_in.push_back({"MID_TEST_FIELD3", "MID_TEST_VALUE3"}); + s = db.HMSet("B_HGETALL_KEY", mid_fvs_in); + ASSERT_TRUE(s.ok()); + + std::vector fvs_out; + s = db.HGetall("B_HGETALL_KEY", &fvs_out); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(fvs_out.size(), 3); + ASSERT_EQ(fvs_out[0].field, "MID_TEST_FIELD1"); + ASSERT_EQ(fvs_out[0].value, "MID_TEST_VALUE1"); + ASSERT_EQ(fvs_out[1].field, "MID_TEST_FIELD2"); + ASSERT_EQ(fvs_out[1].value, "MID_TEST_VALUE2"); + ASSERT_EQ(fvs_out[2].field, "MID_TEST_FIELD3"); + ASSERT_EQ(fvs_out[2].value, "MID_TEST_VALUE3"); + + // Insert some kv who's position above "mid kv" + std::vector pre_fvs_in; + pre_fvs_in.push_back({"PRE_TEST_FIELD1", "PRE_TEST_VALUE1"}); + pre_fvs_in.push_back({"PRE_TEST_FIELD2", "PRE_TEST_VALUE2"}); + pre_fvs_in.push_back({"PRE_TEST_FIELD3", "PRE_TEST_VALUE3"}); + s = db.HMSet("A_HGETALL_KEY", pre_fvs_in); + ASSERT_TRUE(s.ok()); + fvs_out.clear(); + s = db.HGetall("B_HGETALL_KEY", &fvs_out); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(fvs_out.size(), 3); + ASSERT_EQ(fvs_out[0].field, "MID_TEST_FIELD1"); + ASSERT_EQ(fvs_out[0].value, "MID_TEST_VALUE1"); + ASSERT_EQ(fvs_out[1].field, "MID_TEST_FIELD2"); + ASSERT_EQ(fvs_out[1].value, "MID_TEST_VALUE2"); + ASSERT_EQ(fvs_out[2].field, "MID_TEST_FIELD3"); + ASSERT_EQ(fvs_out[2].value, "MID_TEST_VALUE3"); + + // Insert some kv who's position below "mid kv" + std::vector suf_fvs_in; + suf_fvs_in.push_back({"SUF_TEST_FIELD1", "SUF_TEST_VALUE1"}); + suf_fvs_in.push_back({"SUF_TEST_FIELD2", "SUF_TEST_VALUE2"}); + suf_fvs_in.push_back({"SUF_TEST_FIELD3", "SUF_TEST_VALUE3"}); + s = db.HMSet("C_HGETALL_KEY", suf_fvs_in); + ASSERT_TRUE(s.ok()); + fvs_out.clear(); + s = db.HGetall("B_HGETALL_KEY", &fvs_out); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(fvs_out.size(), 3); + ASSERT_EQ(fvs_out[0].field, "MID_TEST_FIELD1"); + ASSERT_EQ(fvs_out[0].value, "MID_TEST_VALUE1"); + ASSERT_EQ(fvs_out[1].field, "MID_TEST_FIELD2"); + ASSERT_EQ(fvs_out[1].value, "MID_TEST_VALUE2"); + ASSERT_EQ(fvs_out[2].field, "MID_TEST_FIELD3"); + ASSERT_EQ(fvs_out[2].value, "MID_TEST_VALUE3"); + + // HGetall timeout hash table + fvs_out.clear(); + std::map type_status; + db.Expire("B_HGETALL_KEY", 1); + ASSERT_TRUE(type_status[storage::DataType::kHashes].ok()); + std::this_thread::sleep_for(std::chrono::milliseconds(2000)); + s = db.HGetall("B_HGETALL_KEY", &fvs_out); + ASSERT_TRUE(s.IsNotFound()); + ASSERT_EQ(fvs_out.size(), 0); + + // HGetall not exist hash table + fvs_out.clear(); + s = db.HGetall("HGETALL_NOT_EXIST_KEY", &fvs_out); + ASSERT_TRUE(s.IsNotFound()); + ASSERT_EQ(fvs_out.size(), 0); +} + +// HIncrby +TEST_F(PKHashesTest, HIncrby) { + int32_t ret; + int64_t value; + std::string str_value; + + // ***************** Group 1 Test ***************** + s = db.HSet("GP1_HINCRBY_KEY", "GP1_HINCRBY_FIELD", "1", &ret); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(ret, 1); + + s = db.HIncrby("GP1_HINCRBY_KEY", "GP1_HINCRBY_FIELD", 1, &value); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(value, 2); + + // ***************** Group 2 Test ***************** + s = db.HSet("GP2_HINCRBY_KEY", "GP2_HINCRBY_FIELD", " 1", &ret); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(ret, 1); + + s = db.HIncrby("GP2_HINCRBY_KEY", "GP2_HINCRBY_FIELD", 1, &value); + ASSERT_TRUE(s.IsCorruption()); + ASSERT_EQ(value, 0); + + // ***************** Group 3 Test ***************** + s = db.HSet("GP3_HINCRBY_KEY", "GP3_HINCRBY_FIELD", "1 ", &ret); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(ret, 1); + + s = db.HIncrby("GP3_HINCRBY_KEY", "GP3_HINCRBY_FIELD", 1, &value); + ASSERT_TRUE(s.IsCorruption()); + ASSERT_EQ(value, 0); + + // If key does not exist the value is set to 0 before the + // operation is performed + s = db.HIncrby("HINCRBY_NEW_KEY", "HINCRBY_EXIST_FIELD", 1000, &value); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(value, 1000); + s = db.HGet("HINCRBY_NEW_KEY", "HINCRBY_EXIST_FIELD", &str_value); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(atoll(str_value.data()), 1000); + + // If the hash field contains a string that can not be + // represented as integer + s = db.HSet("HINCRBY_KEY", "HINCRBY_STR_FIELD", "HINCRBY_VALEU", &ret); + ASSERT_TRUE(s.ok()); + s = db.HIncrby("HINCRBY_KEY", "HINCRBY_STR_FIELD", 100, &value); + ASSERT_TRUE(s.IsCorruption()); + + // If field does not exist the value is set to 0 before the + // operation is performed + s = db.HIncrby("HINCRBY_KEY", "HINCRBY_NOT_EXIST_FIELD", 100, &value); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(value, 100); + s = db.HGet("HINCRBY_KEY", "HINCRBY_NOT_EXIST_FIELD", &str_value); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(atoll(str_value.data()), 100); + + s = db.HSet("HINCRBY_KEY", "HINCRBY_NUM_FIELD", "100", &ret); + ASSERT_TRUE(s.ok()); + + // Positive test + s = db.HIncrby("HINCRBY_KEY", "HINCRBY_NUM_FIELD", 100, &value); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(value, 200); + s = db.HGet("HINCRBY_KEY", "HINCRBY_NUM_FIELD", &str_value); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(atoll(str_value.data()), 200); + + // Negative test + s = db.HIncrby("HINCRBY_KEY", "HINCRBY_NUM_FIELD", -100, &value); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(value, 100); + s = db.HGet("HINCRBY_KEY", "HINCRBY_NUM_FIELD", &str_value); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(atoll(str_value.data()), 100); + + // Larger than the maximum number 9223372036854775807 + s = db.HSet("HINCRBY_KEY", "HINCRBY_NUM_FIELD", "10", &ret); + ASSERT_TRUE(s.ok()); + s = db.HIncrby("HINCRBY_KEY", "HINCRBY_NUM_FIELD", 9223372036854775807, &value); + ASSERT_TRUE(s.IsInvalidArgument()); + + // Less than the minimum number -9223372036854775808 + s = db.HSet("HINCRBY_KEY", "HINCRBY_NUM_FIELD", "-10", &ret); + ASSERT_TRUE(s.ok()); + s = db.HIncrby("HINCRBY_KEY", "HINCRBY_NUM_FIELD", -9223372036854775807, &value); + ASSERT_TRUE(s.IsInvalidArgument()); +} + +// HIncrbyfloat +TEST_F(PKHashesTest, HIncrbyfloat) { + int32_t ret; + std::string new_value; + + // ***************** Group 1 Test ***************** + s = db.HSet("GP1_HINCRBYFLOAT_KEY", "GP1_HINCRBYFLOAT_FIELD", "1.234", &ret); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(ret, 1); + + s = db.HIncrbyfloat("GP1_HINCRBYFLOAT_KEY", "GP1_HINCRBYFLOAT_FIELD", "1.234", &new_value); + ASSERT_TRUE(s.ok()); + // ASSERT_EQ(new_value, "2.468"); + + // ***************** Group 2 Test ***************** + s = db.HSet("GP2_HINCRBYFLOAT_KEY", "GP2_HINCRBYFLOAT_FIELD", " 1.234", &ret); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(ret, 1); + + s = db.HIncrbyfloat("GP2_HINCRBYFLOAT_KEY", "GP2_HINCRBYFLOAT_FIELD", "1.234", &new_value); + ASSERT_TRUE(s.IsCorruption()); + ASSERT_EQ(new_value, ""); + + // ***************** Group 3 Test ***************** + s = db.HSet("GP3_HINCRBYFLOAT_KEY", "GP3_HINCRBYFLOAT_FIELD", "1.234 ", &ret); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(ret, 1); + + s = db.HIncrbyfloat("GP3_HINCRBYFLOAT_KEY", "GP3_HINCRBYFLOAT_FIELD", "1.234", &new_value); + ASSERT_TRUE(s.IsCorruption()); + ASSERT_EQ(new_value, ""); + + // If the specified increment are not parsable as a double precision + // floating point number + s = db.HIncrbyfloat("HINCRBYFLOAT_KEY", "HINCRBYFLOAT_FIELD", "HINCRBYFLOAT_BY", &new_value); + ASSERT_TRUE(s.IsCorruption()); + s = db.HGet("HINCRBYFLOAT_KEY", "HINCRBYFLOAT_FIELD", &new_value); + ASSERT_TRUE(s.IsNotFound()); + + // If key does not exist the value is set to 0 before the + // operation is performed + s = db.HIncrbyfloat("HINCRBYFLOAT_KEY", "HINCRBYFLOAT_FIELD", "12.3456", &new_value); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(new_value, "12.3456"); + s = db.HGet("HINCRBYFLOAT_KEY", "HINCRBYFLOAT_FIELD", &new_value); + ASSERT_TRUE(s.ok()); + // ASSERT_EQ(new_value, "12.3456"); + s = db.HLen("HINCRBYFLOAT_KEY", &ret); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(ret, 1); + + // If the current field content are not parsable as a double precision + // floating point number + s = db.HSet("HINCRBYFLOAT_KEY", "HINCRBYFLOAT_STR_FIELD", "HINCRBYFLOAT_VALUE", &ret); + ASSERT_TRUE(s.ok()); + s = db.HIncrbyfloat("HINCRBYFLOAT_KEY", "HINCRBYFLOAT_STR_FIELD", "123.456", &new_value); + ASSERT_TRUE(s.IsCorruption()); + s = db.HLen("HINCRBYFLOAT_KEY", &ret); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(ret, 2); + + // If field does not exist the value is set to 0 before the + // operation is performed + s = db.HIncrbyfloat("HINCRBYFLOAT_KEY", "HINCRBYFLOAT_NOT_EXIST_FIELD", "65.4321000", &new_value); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(new_value, "65.4321"); + s = db.HGet("HINCRBYFLOAT_KEY", "HINCRBYFLOAT_NOT_EXIST_FIELD", &new_value); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(new_value, "65.4321"); + s = db.HLen("HINCRBYFLOAT_KEY", &ret); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(ret, 3); + + s = db.HSet("HINCRBYFLOAT_KEY", "HINCRBYFLOAT_NUM_FIELD", "1000", &ret); + ASSERT_TRUE(s.ok()); + + // Positive test + s = db.HIncrbyfloat("HINCRBYFLOAT_KEY", "HINCRBYFLOAT_NUM_FIELD", "+123.456789", &new_value); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(new_value, "1123.456789"); + s = db.HGet("HINCRBYFLOAT_KEY", "HINCRBYFLOAT_NUM_FIELD", &new_value); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(new_value, "1123.456789"); + + // Negative test + s = db.HIncrbyfloat("HINCRBYFLOAT_KEY", "HINCRBYFLOAT_NUM_FIELD", "-123.456789", &new_value); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(new_value, "1000"); + s = db.HGet("HINCRBYFLOAT_KEY", "HINCRBYFLOAT_NUM_FIELD", &new_value); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(new_value, "1000"); + + s = db.HLen("HINCRBYFLOAT_KEY", &ret); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(ret, 4); + + // ***** Special test ***** + // case 1 + s = db.HIncrbyfloat("HINCRBYFLOAT_KEY", "HINCRBYFLOAT_SP_FIELD1", "2.0e2", &new_value); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(new_value, "200"); + s = db.HGet("HINCRBYFLOAT_KEY", "HINCRBYFLOAT_SP_FIELD1", &new_value); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(new_value, "200"); + + // case2 + s = db.HIncrbyfloat("HINCRBYFLOAT_KEY", "HINCRBYFLOAT_SP_FIELD2", "5.0e3", &new_value); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(new_value, "5000"); + s = db.HIncrbyfloat("HINCRBYFLOAT_KEY", "HINCRBYFLOAT_SP_FIELD2", "2.0e2", &new_value); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(new_value, "5200"); + s = db.HGet("HINCRBYFLOAT_KEY", "HINCRBYFLOAT_SP_FIELD2", &new_value); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(new_value, "5200"); + + // case 3 + s = db.HIncrbyfloat("HINCRBYFLOAT_KEY", "HINCRBYFLOAT_SP_FIELD3", "5.0e3", &new_value); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(new_value, "5000"); + s = db.HIncrbyfloat("HINCRBYFLOAT_KEY", "HINCRBYFLOAT_SP_FIELD3", "-2.0e2", &new_value); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(new_value, "4800"); + s = db.HGet("HINCRBYFLOAT_KEY", "HINCRBYFLOAT_SP_FIELD3", &new_value); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(new_value, "4800"); + + // case 4 + s = db.HIncrbyfloat("HINCRBYFLOAT_KEY", "HINCRBYFLOAT_SP_FIELD4", ".456789", &new_value); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(new_value, "0.456789"); + s = db.HGet("HINCRBYFLOAT_KEY", "HINCRBYFLOAT_SP_FIELD4", &new_value); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(new_value, "0.456789"); + + // case5 + s = db.HIncrbyfloat("HINCRBYFLOAT_KEY", "HINCRBYFLOAT_SP_FIELD5", "-.456789", &new_value); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(new_value, "-0.456789"); + s = db.HGet("HINCRBYFLOAT_KEY", "HINCRBYFLOAT_SP_FIELD5", &new_value); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(new_value, "-0.456789"); + + // case6 + s = db.HIncrbyfloat("HINCRBYFLOAT_KEY", "HINCRBYFLOAT_SP_FIELD6", "+.456789", &new_value); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(new_value, "0.456789"); + s = db.HGet("HINCRBYFLOAT_KEY", "HINCRBYFLOAT_SP_FIELD6", &new_value); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(new_value, "0.456789"); + + // case7 + s = db.HIncrbyfloat("HINCRBYFLOAT_KEY", "HINCRBYFLOAT_SP_FIELD7", "+.456789", &new_value); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(new_value, "0.456789"); + s = db.HIncrbyfloat("HINCRBYFLOAT_KEY", "HINCRBYFLOAT_SP_FIELD7", "-.456789", &new_value); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(new_value, "0"); + s = db.HGet("HINCRBYFLOAT_KEY", "HINCRBYFLOAT_SP_FIELD7", &new_value); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(new_value, "0"); + + // case8 + s = db.HIncrbyfloat("HINCRBYFLOAT_KEY", "HINCRBYFLOAT_SP_FIELD8", "-00000.456789000", &new_value); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(new_value, "-0.456789"); + s = db.HGet("HINCRBYFLOAT_KEY", "HINCRBYFLOAT_SP_FIELD8", &new_value); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(new_value, "-0.456789"); + + // case9 + s = db.HIncrbyfloat("HINCRBYFLOAT_KEY", "HINCRBYFLOAT_SP_FIELD9", "+00000.456789000", &new_value); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(new_value, "0.456789"); + s = db.HGet("HINCRBYFLOAT_KEY", "HINCRBYFLOAT_SP_FIELD9", &new_value); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(new_value, "0.456789"); + + s = db.HLen("HINCRBYFLOAT_KEY", &ret); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(ret, 13); +} + // HKeys TEST_F(PKHashesTest, HKeys) { int32_t ret = 0; @@ -205,6 +589,810 @@ TEST_F(PKHashesTest, HKeys) { ASSERT_EQ(fields.size(), 0); } +// HLen +TEST_F(PKHashesTest, HLenTest) { + int32_t ret = 0; + + // ***************** Group 1 Test ***************** + std::vector fvs1; + fvs1.push_back({"GP1_TEST_FIELD1", "GP1_TEST_VALUE1"}); + fvs1.push_back({"GP1_TEST_FIELD2", "GP1_TEST_VALUE2"}); + fvs1.push_back({"GP1_TEST_FIELD3", "GP1_TEST_VALUE3"}); + s = db.HMSet("GP1_HLEN_KEY", fvs1); + ASSERT_TRUE(s.ok()); + + s = db.HLen("GP1_HLEN_KEY", &ret); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(ret, 3); + + // ***************** Group 2 Test ***************** + std::vector fvs2; + fvs2.push_back({"GP2_TEST_FIELD1", "GP2_TEST_VALUE1"}); + fvs2.push_back({"GP2_TEST_FIELD2", "GP2_TEST_VALUE2"}); + fvs2.push_back({"GP2_TEST_FIELD3", "GP2_TEST_VALUE3"}); + s = db.HMSet("GP2_HLEN_KEY", fvs2); + ASSERT_TRUE(s.ok()); + + s = db.HDel("GP2_HLEN_KEY", {"GP2_TEST_FIELD1", "GP2_TEST_FIELD2"}, &ret); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(ret, 2); + + s = db.HLen("GP2_HLEN_KEY", &ret); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(ret, 1); + + s = db.HDel("GP2_HLEN_KEY", {"GP2_TEST_FIELD3"}, &ret); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(ret, 1); + + s = db.HLen("GP2_HLEN_KEY", &ret); + ASSERT_TRUE(s.IsNotFound()); + ASSERT_EQ(ret, 0); +} + +// HMGet +TEST_F(PKHashesTest, HMGetTest) { + int32_t ret = 0; + std::vector vss; + + // ***************** Group 1 Test ***************** + std::vector fvs1; + fvs1.push_back({"TEST_FIELD1", "TEST_VALUE1"}); + fvs1.push_back({"TEST_FIELD2", "TEST_VALUE2"}); + fvs1.push_back({"TEST_FIELD3", "TEST_VALUE3"}); + s = db.HMSet("GP1_HMGET_KEY", fvs1); + ASSERT_TRUE(s.ok()); + + s = db.HLen("GP1_HMGET_KEY", &ret); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(ret, 3); + + std::vector fields1{"TEST_FIELD1", "TEST_FIELD2", "TEST_FIELD3", "TEST_NOT_EXIST_FIELD"}; + s = db.HMGet("GP1_HMGET_KEY", fields1, &vss); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(vss.size(), 4); + + ASSERT_TRUE(vss[0].status.ok()); + ASSERT_EQ(vss[0].value, "TEST_VALUE1"); + ASSERT_TRUE(vss[1].status.ok()); + ASSERT_EQ(vss[1].value, "TEST_VALUE2"); + ASSERT_TRUE(vss[2].status.ok()); + ASSERT_EQ(vss[2].value, "TEST_VALUE3"); + ASSERT_TRUE(vss[3].status.IsNotFound()); + ASSERT_EQ(vss[3].value, ""); + + // ***************** Group 2 Test ***************** + std::vector fvs2; + fvs2.push_back({"TEST_FIELD1", "TEST_VALUE1"}); + fvs2.push_back({"TEST_FIELD2", ""}); + s = db.HMSet("GP2_HMGET_KEY", fvs2); + ASSERT_TRUE(s.ok()); + + s = db.HLen("GP2_HMGET_KEY", &ret); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(ret, 2); + + vss.clear(); + std::vector fields2{"TEST_FIELD1", "TEST_FIELD2", "TEST_NOT_EXIST_FIELD"}; + s = db.HMGet("GP2_HMGET_KEY", fields2, &vss); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(vss.size(), 3); + + ASSERT_TRUE(vss[0].status.ok()); + ASSERT_EQ(vss[0].value, "TEST_VALUE1"); + ASSERT_TRUE(vss[1].status.ok()); + ASSERT_EQ(vss[1].value, ""); + ASSERT_TRUE(vss[2].status.IsNotFound()); + ASSERT_EQ(vss[2].value, ""); + + // ***************** Group 3 Test ***************** + vss.clear(); + std::vector fields3{"TEST_FIELD1", "TEST_FIELD2", "TEST_FIELD3"}; + s = db.HMGet("GP3_HMGET_KEY", fields3, &vss); + ASSERT_TRUE(s.IsNotFound()); + ASSERT_EQ(vss.size(), 3); + + ASSERT_TRUE(vss[0].status.IsNotFound()); + ASSERT_EQ(vss[0].value, ""); + ASSERT_TRUE(vss[1].status.IsNotFound()); + ASSERT_EQ(vss[1].value, ""); + ASSERT_TRUE(vss[2].status.IsNotFound()); + ASSERT_EQ(vss[2].value, ""); + + // ***************** Group 4 Test ***************** + std::vector fvs4; + fvs4.push_back({"TEST_FIELD1", "TEST_VALUE1"}); + fvs4.push_back({"TEST_FIELD2", "TEST_VALUE2"}); + fvs4.push_back({"TEST_FIELD3", "TEST_VALUE3"}); + + s = db.HMSet("GP4_HMGET_KEY", fvs4); + ASSERT_TRUE(s.ok()); + + ASSERT_TRUE(make_expired(&db, "GP4_HMGET_KEY")); + + vss.clear(); + std::vector fields4{"TEST_FIELD1", "TEST_FIELD2", "TEST_FIELD3"}; + s = db.HMGet("GP4_HMGET_KEY", fields4, &vss); + ASSERT_TRUE(s.IsNotFound()); + ASSERT_EQ(vss.size(), 3); + + ASSERT_TRUE(vss[0].status.IsNotFound()); + ASSERT_EQ(vss[0].value, ""); + ASSERT_TRUE(vss[1].status.IsNotFound()); + ASSERT_EQ(vss[1].value, ""); + ASSERT_TRUE(vss[2].status.IsNotFound()); + ASSERT_EQ(vss[2].value, ""); +} + +// HMSet +TEST_F(PKHashesTest, HMSetTest) { + int32_t ret = 0; + std::vector fvs1; + fvs1.push_back({"TEST_FIELD1", "TEST_VALUE1"}); + fvs1.push_back({"TEST_FIELD2", "TEST_VALUE2"}); + + // If field already exists in the hash, it is overwritten + std::vector fvs2; + fvs2.push_back({"TEST_FIELD2", "TEST_VALUE2"}); + fvs2.push_back({"TEST_FIELD3", "TEST_VALUE3"}); + fvs2.push_back({"TEST_FIELD4", "TEST_VALUE4"}); + fvs2.push_back({"TEST_FIELD3", "TEST_VALUE5"}); + + s = db.HMSet("HMSET_KEY", fvs1); + ASSERT_TRUE(s.ok()); + s = db.HLen("HMSET_KEY", &ret); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(ret, 2); + + s = db.HMSet("HMSET_KEY", fvs2); + ASSERT_TRUE(s.ok()); + + s = db.HLen("HMSET_KEY", &ret); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(ret, 4); + + std::vector vss1; + std::vector fields1{"TEST_FIELD1", "TEST_FIELD2", "TEST_FIELD3", "TEST_FIELD4"}; + s = db.HMGet("HMSET_KEY", fields1, &vss1); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(vss1.size(), 4); + + ASSERT_EQ(vss1[0].value, "TEST_VALUE1"); + ASSERT_EQ(vss1[1].value, "TEST_VALUE2"); + ASSERT_EQ(vss1[2].value, "TEST_VALUE5"); + ASSERT_EQ(vss1[3].value, "TEST_VALUE4"); + + std::map type_status; + db.Expire("HMSET_KEY", 1); + ASSERT_TRUE(type_status[storage::DataType::kHashes].ok()); + + // The key has timeout + std::this_thread::sleep_for(std::chrono::milliseconds(2000)); + std::vector fvs3; + fvs3.push_back({"TEST_FIELD3", "TEST_VALUE3"}); + fvs3.push_back({"TEST_FIELD4", "TEST_VALUE4"}); + fvs3.push_back({"TEST_FIELD5", "TEST_VALUE5"}); + s = db.HMSet("HMSET_KEY", fvs3); + ASSERT_TRUE(s.ok()); + + s = db.HLen("HMSET_KEY", &ret); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(ret, 3); + + std::vector vss2; + std::vector fields2{"TEST_FIELD3", "TEST_FIELD4", "TEST_FIELD5"}; + s = db.HMGet("HMSET_KEY", fields2, &vss2); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(vss2.size(), 3); + + ASSERT_EQ(vss2[0].value, "TEST_VALUE3"); + ASSERT_EQ(vss2[1].value, "TEST_VALUE4"); + ASSERT_EQ(vss2[2].value, "TEST_VALUE5"); +} + +// HSet +TEST_F(PKHashesTest, HSetTest) { + int32_t ret = 0; + std::string value; + + // ***************** Group 1 Test ***************** + // If field is a new field in the hash and value was set. + s = db.HSet("GP1_HSET_KEY", "HSET_TEST_FIELD", "HSET_TEST_VALUE", &ret); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(ret, 1); + s = db.HLen("GP1_HSET_KEY", &ret); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(ret, 1); + s = db.HGet("GP1_HSET_KEY", "HSET_TEST_FIELD", &value); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(value, "HSET_TEST_VALUE"); + + // If field already exists in the hash and the value was updated. + s = db.HSet("GP1_HSET_KEY", "HSET_TEST_FIELD", "HSET_TEST_NEW_VALUE", &ret); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(ret, 0); + s = db.HLen("GP1_HSET_KEY", &ret); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(ret, 1); + s = db.HGet("GP1_HSET_KEY", "HSET_TEST_FIELD", &value); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(value, "HSET_TEST_NEW_VALUE"); + + // If field already exists in the hash and the value was equal. + s = db.HSet("GP1_HSET_KEY", "HSET_TEST_FIELD", "HSET_TEST_NEW_VALUE", &ret); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(ret, 0); + s = db.HLen("GP1_HSET_KEY", &ret); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(ret, 1); + s = db.HGet("GP1_HSET_KEY", "HSET_TEST_FIELD", &value); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(value, "HSET_TEST_NEW_VALUE"); + + // ***************** Group 2 Test ***************** + s = db.HSet("GP2_HSET_KEY", "HSET_TEST_FIELD", "HSET_TEST_VALUE", &ret); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(ret, 1); + s = db.HLen("GP2_HSET_KEY", &ret); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(ret, 1); + s = db.HGet("GP2_HSET_KEY", "HSET_TEST_FIELD", &value); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(value, "HSET_TEST_VALUE"); + + ASSERT_TRUE(make_expired(&db, "GP2_HSET_KEY")); + + s = db.HSet("GP2_HSET_KEY", "HSET_TEST_FIELD", "HSET_TEST_VALUE", &ret); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(ret, 1); + s = db.HLen("GP2_HSET_KEY", &ret); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(ret, 1); + s = db.HGet("GP2_HSET_KEY", "HSET_TEST_FIELD", &value); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(value, "HSET_TEST_VALUE"); + + // ***************** Group 3 Test ***************** + s = db.HSet("GP3_HSET_KEY", "HSET_TEST_FIELD", "HSET_TEST_NEW_VALUE", &ret); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(ret, 1); + s = db.HLen("GP3_HSET_KEY", &ret); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(ret, 1); + s = db.HGet("GP3_HSET_KEY", "HSET_TEST_FIELD", &value); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(value, "HSET_TEST_NEW_VALUE"); + + ASSERT_TRUE(make_expired(&db, "GP3_HSET_KEY")); + + s = db.HSet("GP3_HSET_KEY", "HSET_TEST_NEW_FIELD", "HSET_TEST_NEW_VALUE", &ret); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(ret, 1); + s = db.HLen("GP3_HSET_KEY", &ret); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(ret, 1); + s = db.HGet("GP3_HSET_KEY", "HSET_TEST_NEW_FIELD", &value); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(value, "HSET_TEST_NEW_VALUE"); + + // ***************** Group 4 Test ***************** + // hset after string type key expires, should success + s = db.Setex("GP4_HSET_KEY", "STRING_VALUE_WITH_TTL", 1); + ASSERT_TRUE(s.ok()); + std::this_thread::sleep_for(std::chrono::milliseconds(2100)); + s = db.HSet("GP4_HSET_KEY", "HSET_TEST_NEW_FIELD", "HSET_TEST_NEW_VALUE", &ret); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(ret, 1); +} + +// HSetnx +TEST_F(PKHashesTest, HSetnxTest) { + int32_t ret; + std::string value; + // If field is a new field in the hash and value was set. + s = db.HSetnx("HSETNX_KEY", "HSETNX_TEST_FIELD", "HSETNX_TEST_VALUE", &ret); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(ret, 1); + s = db.HGet("HSETNX_KEY", "HSETNX_TEST_FIELD", &value); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(value, "HSETNX_TEST_VALUE"); + + // If field already exists, this operation has no effect. + s = db.HSetnx("HSETNX_KEY", "HSETNX_TEST_FIELD", "HSETNX_TEST_NEW_VALUE", &ret); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(ret, 0); + s = db.HGet("HSETNX_KEY", "HSETNX_TEST_FIELD", &value); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(value, "HSETNX_TEST_VALUE"); +} + +// HVals +TEST_F(PKHashesTest, HVals) { + int32_t ret = 0; + std::vector mid_fvs_in; + mid_fvs_in.push_back({"MID_TEST_FIELD1", "MID_TEST_VALUE1"}); + mid_fvs_in.push_back({"MID_TEST_FIELD2", "MID_TEST_VALUE2"}); + mid_fvs_in.push_back({"MID_TEST_FIELD3", "MID_TEST_VALUE3"}); + s = db.HMSet("B_HVALS_KEY", mid_fvs_in); + ASSERT_TRUE(s.ok()); + + std::vector values; + s = db.HVals("B_HVALS_KEY", &values); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(values.size(), 3); + ASSERT_EQ(values[0], "MID_TEST_VALUE1"); + ASSERT_EQ(values[1], "MID_TEST_VALUE2"); + ASSERT_EQ(values[2], "MID_TEST_VALUE3"); + + // Insert some kv who's position above "mid kv" + std::vector pre_fvs_in; + pre_fvs_in.push_back({"PRE_TEST_FIELD1", "PRE_TEST_VALUE1"}); + pre_fvs_in.push_back({"PRE_TEST_FIELD2", "PRE_TEST_VALUE2"}); + pre_fvs_in.push_back({"PRE_TEST_FIELD3", "PRE_TEST_VALUE3"}); + s = db.HMSet("A_HVALS_KEY", pre_fvs_in); + ASSERT_TRUE(s.ok()); + values.clear(); + s = db.HVals("B_HVALS_KEY", &values); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(values.size(), 3); + ASSERT_EQ(values[0], "MID_TEST_VALUE1"); + ASSERT_EQ(values[1], "MID_TEST_VALUE2"); + ASSERT_EQ(values[2], "MID_TEST_VALUE3"); + + // Insert some kv who's position below "mid kv" + std::vector suf_fvs_in; + suf_fvs_in.push_back({"SUF_TEST_FIELD1", "SUF_TEST_VALUE1"}); + suf_fvs_in.push_back({"SUF_TEST_FIELD2", "SUF_TEST_VALUE2"}); + suf_fvs_in.push_back({"SUF_TEST_FIELD3", "SUF_TEST_VALUE3"}); + s = db.HMSet("C_HVALS_KEY", suf_fvs_in); + ASSERT_TRUE(s.ok()); + values.clear(); + s = db.HVals("B_HVALS_KEY", &values); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(values.size(), 3); + ASSERT_EQ(values[0], "MID_TEST_VALUE1"); + ASSERT_EQ(values[1], "MID_TEST_VALUE2"); + ASSERT_EQ(values[2], "MID_TEST_VALUE3"); + + // HVals timeout hash table + values.clear(); + std::map type_status; + db.Expire("B_HVALS_KEY", 1); + ASSERT_TRUE(type_status[storage::DataType::kHashes].ok()); + std::this_thread::sleep_for(std::chrono::milliseconds(2000)); + s = db.HVals("B_HVALS_KEY", &values); + ASSERT_TRUE(s.IsNotFound()); + ASSERT_EQ(values.size(), 0); + + // HVals not exist hash table + values.clear(); + s = db.HVals("HVALS_NOT_EXIST_KEY", &values); + ASSERT_TRUE(s.IsNotFound()); + ASSERT_EQ(values.size(), 0); +} + +// HStrlen +TEST_F(PKHashesTest, HStrlenTest) { + int32_t ret = 0; + int32_t len = 0; + s = db.HSet("HSTRLEN_KEY", "HSTRLEN_TEST_FIELD", "HSTRLEN_TEST_VALUE", &ret); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(ret, 1); + + s = db.HStrlen("HSTRLEN_KEY", "HSTRLEN_TEST_FIELD", &len); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(len, 18); + + // If the key or the field do not exist, 0 is returned + s = db.HStrlen("HSTRLEN_KEY", "HSTRLEN_NOT_EXIST_FIELD", &len); + ASSERT_TRUE(s.IsNotFound()); + ASSERT_EQ(len, 0); +} + +// HScan +TEST_F(PKHashesTest, HScanTest) { // NOLINT + int64_t cursor = 0; + int64_t next_cursor = 0; + std::vector field_value_out; + + // ***************** Group 1 Test ***************** + // {a,v} {b,v} {c,v} {d,v} {e,v} {f,v} {g,v} {h,v} + // 0 1 2 3 4 5 6 7 + std::vector gp1_field_value{{"a", "v"}, {"b", "v"}, {"c", "v"}, {"d", "v"}, + {"e", "v"}, {"f", "v"}, {"g", "v"}, {"h", "v"}}; + s = db.HMSet("GP1_HSCAN_KEY", gp1_field_value); + ASSERT_TRUE(s.ok()); + ASSERT_TRUE(size_match(&db, "GP1_HSCAN_KEY", 8)); + + s = db.HScan("GP1_HSCAN_KEY", 0, "*", 3, &field_value_out, &next_cursor); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(field_value_out.size(), 3); + ASSERT_EQ(next_cursor, 3); + ASSERT_TRUE(field_value_match(field_value_out, {{"a", "v"}, {"b", "v"}, {"c", "v"}})); + + field_value_out.clear(); + cursor = next_cursor, next_cursor = 0; + s = db.HScan("GP1_HSCAN_KEY", cursor, "*", 3, &field_value_out, &next_cursor); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(field_value_out.size(), 3); + ASSERT_EQ(next_cursor, 6); + ASSERT_TRUE(field_value_match(field_value_out, {{"d", "v"}, {"e", "v"}, {"f", "v"}})); + + field_value_out.clear(); + cursor = next_cursor, next_cursor = 0; + s = db.HScan("GP1_HSCAN_KEY", cursor, "*", 3, &field_value_out, &next_cursor); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(field_value_out.size(), 2); + ASSERT_EQ(next_cursor, 0); + ASSERT_TRUE(field_value_match(field_value_out, {{"g", "v"}, {"h", "v"}})); + + // ***************** Group 2 Test ***************** + // {a,v} {b,v} {c,v} {d,v} {e,v} {f,v} {g,v} {h,v} + // 0 1 2 3 4 5 6 7 + std::vector gp2_field_value{{"a", "v"}, {"b", "v"}, {"c", "v"}, {"d", "v"}, + {"e", "v"}, {"f", "v"}, {"g", "v"}, {"h", "v"}}; + s = db.HMSet("GP2_HSCAN_KEY", gp2_field_value); + ASSERT_TRUE(s.ok()); + ASSERT_TRUE(size_match(&db, "GP2_HSCAN_KEY", 8)); + + field_value_out.clear(); + cursor = 0, next_cursor = 0; + s = db.HScan("GP2_HSCAN_KEY", cursor, "*", 1, &field_value_out, &next_cursor); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(field_value_out.size(), 1); + ASSERT_EQ(next_cursor, 1); + ASSERT_TRUE(field_value_match(field_value_out, {{"a", "v"}})); + + field_value_out.clear(); + cursor = next_cursor, next_cursor = 0; + s = db.HScan("GP2_HSCAN_KEY", cursor, "*", 1, &field_value_out, &next_cursor); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(field_value_out.size(), 1); + ASSERT_EQ(next_cursor, 2); + ASSERT_TRUE(field_value_match(field_value_out, {{"b", "v"}})); + + field_value_out.clear(); + cursor = next_cursor, next_cursor = 0; + s = db.HScan("GP2_HSCAN_KEY", cursor, "*", 1, &field_value_out, &next_cursor); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(field_value_out.size(), 1); + ASSERT_EQ(next_cursor, 3); + ASSERT_TRUE(field_value_match(field_value_out, {{"c", "v"}})); + + field_value_out.clear(); + cursor = next_cursor, next_cursor = 0; + s = db.HScan("GP2_HSCAN_KEY", cursor, "*", 1, &field_value_out, &next_cursor); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(field_value_out.size(), 1); + ASSERT_EQ(next_cursor, 4); + ASSERT_TRUE(field_value_match(field_value_out, {{"d", "v"}})); + + field_value_out.clear(); + cursor = next_cursor, next_cursor = 0; + s = db.HScan("GP2_HSCAN_KEY", cursor, "*", 1, &field_value_out, &next_cursor); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(field_value_out.size(), 1); + ASSERT_EQ(next_cursor, 5); + ASSERT_TRUE(field_value_match(field_value_out, {{"e", "v"}})); + + field_value_out.clear(); + cursor = next_cursor, next_cursor = 0; + s = db.HScan("GP2_HSCAN_KEY", cursor, "*", 1, &field_value_out, &next_cursor); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(field_value_out.size(), 1); + ASSERT_EQ(next_cursor, 6); + ASSERT_TRUE(field_value_match(field_value_out, {{"f", "v"}})); + + field_value_out.clear(); + cursor = next_cursor, next_cursor = 0; + s = db.HScan("GP2_HSCAN_KEY", cursor, "*", 1, &field_value_out, &next_cursor); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(field_value_out.size(), 1); + ASSERT_EQ(next_cursor, 7); + ASSERT_TRUE(field_value_match(field_value_out, {{"g", "v"}})); + + field_value_out.clear(); + cursor = next_cursor, next_cursor = 0; + s = db.HScan("GP2_HSCAN_KEY", cursor, "*", 1, &field_value_out, &next_cursor); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(field_value_out.size(), 1); + ASSERT_EQ(next_cursor, 0); + ASSERT_TRUE(field_value_match(field_value_out, {{"h", "v"}})); + + // ***************** Group 3 Test ***************** + // {a,v} {b,v} {c,v} {d,v} {e,v} {f,v} {g,v} {h,v} + // 0 1 2 3 4 5 6 7 + std::vector gp3_field_value{{"a", "v"}, {"b", "v"}, {"c", "v"}, {"d", "v"}, + {"e", "v"}, {"f", "v"}, {"g", "v"}, {"h", "v"}}; + s = db.HMSet("GP3_HSCAN_KEY", gp3_field_value); + ASSERT_TRUE(s.ok()); + ASSERT_TRUE(size_match(&db, "GP3_HSCAN_KEY", 8)); + + field_value_out.clear(); + cursor = 0, next_cursor = 0; + s = db.HScan("GP3_HSCAN_KEY", cursor, "*", 5, &field_value_out, &next_cursor); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(field_value_out.size(), 5); + ASSERT_EQ(next_cursor, 5); + ASSERT_TRUE(field_value_match(field_value_out, {{"a", "v"}, {"b", "v"}, {"c", "v"}, {"d", "v"}, {"e", "v"}})); + + field_value_out.clear(); + cursor = next_cursor, next_cursor = 0; + s = db.HScan("GP3_HSCAN_KEY", cursor, "*", 5, &field_value_out, &next_cursor); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(field_value_out.size(), 3); + ASSERT_EQ(next_cursor, 0); + ASSERT_TRUE(field_value_match(field_value_out, {{"f", "v"}, {"g", "v"}, {"h", "v"}})); + + // ***************** Group 4 Test ***************** + // {a,v} {b,v} {c,v} {d,v} {e,v} {f,v} {g,v} {h,v} + // 0 1 2 3 4 5 6 7 + std::vector gp4_field_value{{"a", "v"}, {"b", "v"}, {"c", "v"}, {"d", "v"}, + {"e", "v"}, {"f", "v"}, {"g", "v"}, {"h", "v"}}; + s = db.HMSet("GP4_HSCAN_KEY", gp4_field_value); + ASSERT_TRUE(s.ok()); + ASSERT_TRUE(size_match(&db, "GP4_HSCAN_KEY", 8)); + + field_value_out.clear(); + cursor = 0, next_cursor = 0; + s = db.HScan("GP4_HSCAN_KEY", cursor, "*", 10, &field_value_out, &next_cursor); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(field_value_out.size(), 8); + ASSERT_EQ(next_cursor, 0); + ASSERT_TRUE(field_value_match( + field_value_out, + {{"a", "v"}, {"b", "v"}, {"c", "v"}, {"d", "v"}, {"e", "v"}, {"f", "v"}, {"g", "v"}, {"h", "v"}})); + + // ***************** Group 5 Test ***************** + // {a_1_,v} {a_2_,v} {a_3_,v} {b_1_,v} {b_2_,v} {b_3_,v} {c_1_,v} {c_2_,v} {c_3_,v} + // 0 1 2 3 4 5 6 7 8 + std::vector gp5_field_value{{"a_1_", "v"}, {"a_2_", "v"}, {"a_3_", "v"}, {"b_1_", "v"}, {"b_2_", "v"}, + {"b_3_", "v"}, {"c_1_", "v"}, {"c_2_", "v"}, {"c_3_", "v"}}; + s = db.HMSet("GP5_HSCAN_KEY", gp5_field_value); + ASSERT_TRUE(s.ok()); + ASSERT_TRUE(size_match(&db, "GP5_HSCAN_KEY", 9)); + + field_value_out.clear(); + cursor = 0, next_cursor = 0; + s = db.HScan("GP5_HSCAN_KEY", cursor, "*1*", 3, &field_value_out, &next_cursor); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(field_value_out.size(), 1); + ASSERT_EQ(next_cursor, 3); + ASSERT_TRUE(field_value_match(field_value_out, {{"a_1_", "v"}})); + + field_value_out.clear(); + cursor = next_cursor, next_cursor = 0; + s = db.HScan("GP5_HSCAN_KEY", cursor, "*1*", 3, &field_value_out, &next_cursor); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(field_value_out.size(), 1); + ASSERT_EQ(next_cursor, 6); + ASSERT_TRUE(field_value_match(field_value_out, {{"b_1_", "v"}})); + + field_value_out.clear(); + cursor = next_cursor, next_cursor = 0; + s = db.HScan("GP5_HSCAN_KEY", cursor, "*1*", 3, &field_value_out, &next_cursor); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(field_value_out.size(), 1); + ASSERT_EQ(next_cursor, 0); + ASSERT_TRUE(field_value_match(field_value_out, {{"c_1_", "v"}})); + + // ***************** Group 6 Test ***************** + // {a_1_,v} {a_2_,v} {a_3_,v} {b_1_,v} {b_2_,v} {b_3_,v} {c_1_,v} {c_2_,v} {c_3_,v} + // 0 1 2 3 4 5 6 7 8 + std::vector gp6_field_value{{"a_1_", "v"}, {"a_2_", "v"}, {"a_3_", "v"}, {"b_1_", "v"}, {"b_2_", "v"}, + {"b_3_", "v"}, {"c_1_", "v"}, {"c_2_", "v"}, {"c_3_", "v"}}; + s = db.HMSet("GP6_HSCAN_KEY", gp6_field_value); + ASSERT_TRUE(s.ok()); + ASSERT_TRUE(size_match(&db, "GP6_HSCAN_KEY", 9)); + + field_value_out.clear(); + cursor = 0, next_cursor = 0; + s = db.HScan("GP6_HSCAN_KEY", cursor, "a*", 3, &field_value_out, &next_cursor); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(field_value_out.size(), 3); + ASSERT_EQ(next_cursor, 0); + ASSERT_TRUE(field_value_match(field_value_out, {{"a_1_", "v"}, {"a_2_", "v"}, {"a_3_", "v"}})); + + field_value_out.clear(); + cursor = 0, next_cursor = 0; + s = db.HScan("GP6_HSCAN_KEY", cursor, "a*", 2, &field_value_out, &next_cursor); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(field_value_out.size(), 2); + ASSERT_EQ(next_cursor, 2); + ASSERT_TRUE(field_value_match(field_value_out, {{"a_1_", "v"}, {"a_2_", "v"}})); + + field_value_out.clear(); + cursor = next_cursor, next_cursor = 0; + s = db.HScan("GP6_HSCAN_KEY", cursor, "a*", 2, &field_value_out, &next_cursor); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(field_value_out.size(), 1); + ASSERT_EQ(next_cursor, 0); + ASSERT_TRUE(field_value_match(field_value_out, {{"a_3_", "v"}})); + + field_value_out.clear(); + cursor = 0, next_cursor = 0; + s = db.HScan("GP6_HSCAN_KEY", cursor, "a*", 1, &field_value_out, &next_cursor); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(field_value_out.size(), 1); + ASSERT_EQ(next_cursor, 1); + ASSERT_TRUE(field_value_match(field_value_out, {{"a_1_", "v"}})); + + field_value_out.clear(); + cursor = next_cursor, next_cursor = 0; + s = db.HScan("GP6_HSCAN_KEY", cursor, "a*", 1, &field_value_out, &next_cursor); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(field_value_out.size(), 1); + ASSERT_EQ(next_cursor, 2); + ASSERT_TRUE(field_value_match(field_value_out, {{"a_2_", "v"}})); + + field_value_out.clear(); + cursor = next_cursor, next_cursor = 0; + s = db.HScan("GP6_HSCAN_KEY", cursor, "a*", 1, &field_value_out, &next_cursor); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(field_value_out.size(), 1); + ASSERT_EQ(next_cursor, 0); + ASSERT_TRUE(field_value_match(field_value_out, {{"a_3_", "v"}})); + + // ***************** Group 7 Test ***************** + // {a_1_,v} {a_2_,v} {a_3_,v} {b_1_,v} {b_2_,v} {b_3_,v} {c_1_,v} {c_2_,v} {c_3_,v} + // 0 1 2 3 4 5 6 7 8 + std::vector gp7_field_value{{"a_1_", "v"}, {"a_2_", "v"}, {"a_3_", "v"}, {"b_1_", "v"}, {"b_2_", "v"}, + {"b_3_", "v"}, {"c_1_", "v"}, {"c_2_", "v"}, {"c_3_", "v"}}; + s = db.HMSet("GP7_HSCAN_KEY", gp7_field_value); + ASSERT_TRUE(s.ok()); + ASSERT_TRUE(size_match(&db, "GP7_HSCAN_KEY", 9)); + + field_value_out.clear(); + cursor = 0, next_cursor = 0; + s = db.HScan("GP7_HSCAN_KEY", cursor, "b*", 3, &field_value_out, &next_cursor); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(field_value_out.size(), 3); + ASSERT_EQ(next_cursor, 0); + ASSERT_TRUE(field_value_match(field_value_out, {{"b_1_", "v"}, {"b_2_", "v"}, {"b_3_", "v"}})); + + field_value_out.clear(); + cursor = 0, next_cursor = 0; + s = db.HScan("GP7_HSCAN_KEY", cursor, "b*", 2, &field_value_out, &next_cursor); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(field_value_out.size(), 2); + ASSERT_EQ(next_cursor, 2); + ASSERT_TRUE(field_value_match(field_value_out, {{"b_1_", "v"}, {"b_2_", "v"}})); + + field_value_out.clear(); + cursor = next_cursor, next_cursor = 0; + s = db.HScan("GP7_HSCAN_KEY", cursor, "b*", 2, &field_value_out, &next_cursor); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(field_value_out.size(), 1); + ASSERT_EQ(next_cursor, 0); + ASSERT_TRUE(field_value_match(field_value_out, {{"b_3_", "v"}})); + + field_value_out.clear(); + cursor = 0, next_cursor = 0; + s = db.HScan("GP7_HSCAN_KEY", cursor, "b*", 1, &field_value_out, &next_cursor); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(field_value_out.size(), 1); + ASSERT_EQ(next_cursor, 1); + ASSERT_TRUE(field_value_match(field_value_out, {{"b_1_", "v"}})); + + field_value_out.clear(); + cursor = next_cursor, next_cursor = 0; + s = db.HScan("GP7_HSCAN_KEY", cursor, "b*", 1, &field_value_out, &next_cursor); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(field_value_out.size(), 1); + ASSERT_EQ(next_cursor, 2); + ASSERT_TRUE(field_value_match(field_value_out, {{"b_2_", "v"}})); + + field_value_out.clear(); + cursor = next_cursor, next_cursor = 0; + s = db.HScan("GP7_HSCAN_KEY", cursor, "b*", 1, &field_value_out, &next_cursor); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(field_value_out.size(), 1); + ASSERT_EQ(next_cursor, 0); + ASSERT_TRUE(field_value_match(field_value_out, {{"b_3_", "v"}})); + + // ***************** Group 8 Test ***************** + // {a_1_,v} {a_2_,v} {a_3_,v} {b_1_,v} {b_2_,v} {b_3_,v} {c_1_,v} {c_2_,v} {c_3_,v} + // 0 1 2 3 4 5 6 7 8 + std::vector gp8_field_value{{"a_1_", "v"}, {"a_2_", "v"}, {"a_3_", "v"}, {"b_1_", "v"}, {"b_2_", "v"}, + {"b_3_", "v"}, {"c_1_", "v"}, {"c_2_", "v"}, {"c_3_", "v"}}; + s = db.HMSet("GP8_HSCAN_KEY", gp8_field_value); + ASSERT_TRUE(s.ok()); + ASSERT_TRUE(size_match(&db, "GP8_HSCAN_KEY", 9)); + + field_value_out.clear(); + cursor = 0, next_cursor = 0; + s = db.HScan("GP8_HSCAN_KEY", cursor, "c*", 3, &field_value_out, &next_cursor); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(field_value_out.size(), 3); + ASSERT_EQ(next_cursor, 0); + ASSERT_TRUE(field_value_match(field_value_out, {{"c_1_", "v"}, {"c_2_", "v"}, {"c_3_", "v"}})); + + field_value_out.clear(); + cursor = next_cursor, next_cursor = 0; + s = db.HScan("GP8_HSCAN_KEY", cursor, "c*", 2, &field_value_out, &next_cursor); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(field_value_out.size(), 2); + ASSERT_EQ(next_cursor, 2); + ASSERT_TRUE(field_value_match(field_value_out, {{"c_1_", "v"}, {"c_2_", "v"}})); + + field_value_out.clear(); + cursor = next_cursor, next_cursor = 0; + s = db.HScan("GP8_HSCAN_KEY", cursor, "c*", 2, &field_value_out, &next_cursor); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(field_value_out.size(), 1); + ASSERT_EQ(next_cursor, 0); + ASSERT_TRUE(field_value_match(field_value_out, {{"c_3_", "v"}})); + + field_value_out.clear(); + cursor = 0, next_cursor = 0; + s = db.HScan("GP8_HSCAN_KEY", cursor, "c*", 1, &field_value_out, &next_cursor); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(field_value_out.size(), 1); + ASSERT_EQ(next_cursor, 1); + ASSERT_TRUE(field_value_match(field_value_out, {{"c_1_", "v"}})); + + field_value_out.clear(); + cursor = next_cursor, next_cursor = 0; + s = db.HScan("GP8_HSCAN_KEY", cursor, "c*", 1, &field_value_out, &next_cursor); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(field_value_out.size(), 1); + ASSERT_EQ(next_cursor, 2); + ASSERT_TRUE(field_value_match(field_value_out, {{"c_2_", "v"}})); + + field_value_out.clear(); + cursor = next_cursor, next_cursor = 0; + s = db.HScan("GP8_HSCAN_KEY", cursor, "c*", 1, &field_value_out, &next_cursor); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(field_value_out.size(), 1); + ASSERT_EQ(next_cursor, 0); + ASSERT_TRUE(field_value_match(field_value_out, {{"c_3_", "v"}})); + + // ***************** Group 9 Test ***************** + // {a_1_,v} {a_2_,v} {a_3_,v} {b_1_,v} {b_2_,v} {b_3_,v} {c_1_,v} {c_2_,v} {c_3_,v} + // 0 1 2 3 4 5 6 7 8 + std::vector gp9_field_value{{"a_1_", "v"}, {"a_2_", "v"}, {"a_3_", "v"}, {"b_1_", "v"}, {"b_2_", "v"}, + {"b_3_", "v"}, {"c_1_", "v"}, {"c_2_", "v"}, {"c_3_", "v"}}; + s = db.HMSet("GP9_HSCAN_KEY", gp9_field_value); + ASSERT_TRUE(s.ok()); + ASSERT_TRUE(size_match(&db, "GP9_HSCAN_KEY", 9)); + + field_value_out.clear(); + cursor = 0, next_cursor = 0; + s = db.HScan("GP9_HSCAN_KEY", cursor, "d*", 3, &field_value_out, &next_cursor); + ASSERT_TRUE(s.ok()); + ASSERT_EQ(field_value_out.size(), 0); + ASSERT_EQ(next_cursor, 0); + ASSERT_TRUE(field_value_match(field_value_out, {})); + + // ***************** Group 10 Test ***************** + // {a_1_,v} {a_2_,v} {a_3_,v} {b_1_,v} {b_2_,v} {b_3_,v} {c_1_,v} {c_2_,v} {c_3_,v} + // 0 1 2 3 4 5 6 7 8 + std::vector gp10_field_value{{"a_1_", "v"}, {"a_2_", "v"}, {"a_3_", "v"}, {"b_1_", "v"}, {"b_2_", "v"}, + {"b_3_", "v"}, {"c_1_", "v"}, {"c_2_", "v"}, {"c_3_", "v"}}; + s = db.HMSet("GP10_HSCAN_KEY", gp10_field_value); + ASSERT_TRUE(s.ok()); + ASSERT_TRUE(size_match(&db, "GP10_HSCAN_KEY", 9)); + + ASSERT_TRUE(make_expired(&db, "GP10_HSCAN_KEY")); + field_value_out.clear(); + cursor = 0, next_cursor = 0; + s = db.HScan("GP10_HSCAN_KEY", cursor, "*", 10, &field_value_out, &next_cursor); + ASSERT_TRUE(s.IsNotFound()); + ASSERT_EQ(field_value_out.size(), 0); + ASSERT_EQ(next_cursor, 0); + ASSERT_TRUE(field_value_match(field_value_out, {})); + + // ***************** Group 11 Test ***************** + // HScan Not Exist Key + field_value_out.clear(); + cursor = 0, next_cursor = 0; + s = db.HScan("GP11_HSCAN_KEY", cursor, "*", 10, &field_value_out, &next_cursor); + ASSERT_TRUE(s.IsNotFound()); + ASSERT_EQ(field_value_out.size(), 0); + ASSERT_EQ(next_cursor, 0); + ASSERT_TRUE(field_value_match(field_value_out, {})); +} + int main(int argc, char** argv) { if (!pstd::FileExists("./log")) { pstd::CreatePath("./log");