From cd5e466f9041699579e1904a5dff44d54b7fd430 Mon Sep 17 00:00:00 2001 From: Zhelong Zhao <50295684+zztaki@users.noreply.github.com> Date: Mon, 20 May 2024 11:15:23 +0800 Subject: [PATCH] feat: add rename and renamenx (#299) * feat: add rename and renamenx --------- Signed-off-by: zztaki --- src/base_cmd.h | 2 + src/cmd_keys.cc | 41 ++++++ src/cmd_keys.h | 22 +++ src/cmd_table_manager.cc | 2 + src/storage/include/storage/storage.h | 11 ++ src/storage/src/redis.h | 12 ++ src/storage/src/redis_hashes.cc | 71 ++++++++++ src/storage/src/redis_lists.cc | 69 +++++++++ src/storage/src/redis_sets.cc | 70 +++++++++ src/storage/src/redis_strings.cc | 48 +++++++ src/storage/src/redis_zsets.cc | 69 +++++++++ src/storage/src/storage.cc | 96 +++++++++++++ tests/key_test.go | 36 +++++ tests/unit/basic.tcl | 197 ++++++++++++++------------ 14 files changed, 656 insertions(+), 90 deletions(-) diff --git a/src/base_cmd.h b/src/base_cmd.h index 0af27012b..e25dc9a79 100644 --- a/src/base_cmd.h +++ b/src/base_cmd.h @@ -36,6 +36,8 @@ const std::string kCmdNameExpireat = "expireat"; const std::string kCmdNamePExpireat = "pexpireat"; const std::string kCmdNamePersist = "persist"; const std::string kCmdNameKeys = "keys"; +const std::string kCmdNameRename = "rename"; +const std::string kCmdNameRenameNX = "renamenx"; // raft cmd const std::string kCmdNameRaftCluster = "raft.cluster"; diff --git a/src/cmd_keys.cc b/src/cmd_keys.cc index 40b59544b..d1e40e3ca 100644 --- a/src/cmd_keys.cc +++ b/src/cmd_keys.cc @@ -295,4 +295,45 @@ void PttlCmd::DoCmd(PClient* client) { } } +RenameCmd::RenameCmd(const std::string& name, int16_t arity) + : BaseCmd(name, arity, kCmdFlagsWrite, kAclCategoryWrite | kAclCategoryKeyspace) {} + +bool RenameCmd::DoInitial(PClient* client) { + client->SetKey(client->argv_[1]); + return true; +} + +void RenameCmd::DoCmd(PClient* client) { + storage::Status s = PSTORE.GetBackend(client->GetCurrentDB())->GetStorage()->Rename(client->Key(), client->argv_[2]); + if (s.ok()) { + client->SetRes(CmdRes::kOK); + } else if (s.IsNotFound()) { + client->SetRes(CmdRes::kNotFound, s.ToString()); + } else { + client->SetRes(CmdRes::kErrOther, s.ToString()); + } +} + +RenameNXCmd::RenameNXCmd(const std::string& name, int16_t arity) + : BaseCmd(name, arity, kCmdFlagsWrite, kAclCategoryWrite | kAclCategoryKeyspace) {} + +bool RenameNXCmd::DoInitial(PClient* client) { + client->SetKey(client->argv_[1]); + return true; +} + +void RenameNXCmd::DoCmd(PClient* client) { + storage::Status s = + PSTORE.GetBackend(client->GetCurrentDB())->GetStorage()->Renamenx(client->Key(), client->argv_[2]); + if (s.ok()) { + client->SetRes(CmdRes::kOK); + } else if (s.IsNotFound()) { + client->SetRes(CmdRes::kNotFound, s.ToString()); + } else if (s.IsCorruption()) { + client->AppendInteger(0); // newkey already exists + } else { + client->SetRes(CmdRes::kErrOther, s.ToString()); + } +} + } // namespace pikiwidb diff --git a/src/cmd_keys.h b/src/cmd_keys.h index fbe3048ad..f9d1bb793 100644 --- a/src/cmd_keys.h +++ b/src/cmd_keys.h @@ -132,4 +132,26 @@ class PttlCmd : public BaseCmd { void DoCmd(PClient* client) override; }; +class RenameCmd : public BaseCmd { + public: + RenameCmd(const std::string& name, int16_t arity); + + protected: + bool DoInitial(PClient* client) override; + + private: + void DoCmd(PClient* client) override; +}; + +class RenameNXCmd : public BaseCmd { + public: + RenameNXCmd(const std::string& name, int16_t arity); + + protected: + bool DoInitial(PClient* client) override; + + private: + void DoCmd(PClient* client) override; +}; + } // namespace pikiwidb diff --git a/src/cmd_table_manager.cc b/src/cmd_table_manager.cc index 26f910854..236b3b37e 100644 --- a/src/cmd_table_manager.cc +++ b/src/cmd_table_manager.cc @@ -66,6 +66,8 @@ void CmdTableManager::InitCmdTable() { ADD_COMMAND(Pttl, 2); ADD_COMMAND(Persist, 2); ADD_COMMAND(Keys, 2); + ADD_COMMAND(Rename, 3); + ADD_COMMAND(RenameNX, 3); // kv ADD_COMMAND(Get, 2); diff --git a/src/storage/include/storage/storage.h b/src/storage/include/storage/storage.h index 4536a7e41..72ad8ea94 100644 --- a/src/storage/include/storage/storage.h +++ b/src/storage/include/storage/storage.h @@ -1057,6 +1057,17 @@ class Storage { Status Keys(const DataType& data_type, const std::string& pattern, std::vector* keys); + // Return OK if Rename successfully, + // NotFound if key doesn't exist, + // otherwise abort. + Status Rename(const std::string& key, const std::string& newkey); + + // Return OK if Renamenx successfully, + // NotFound if key doesn't exist, + // Corruption if newkey already exists, + // otherwise abort. + Status Renamenx(const std::string& key, const std::string& newkey); + // Dynamic switch WAL void DisableWal(const bool is_wal_disable); diff --git a/src/storage/src/redis.h b/src/storage/src/redis.h index b60878c29..24d16dd18 100644 --- a/src/storage/src/redis.h +++ b/src/storage/src/redis.h @@ -155,6 +155,18 @@ class Redis { virtual Status ZsetsTTL(const Slice& key, uint64_t* timestamp); virtual Status SetsTTL(const Slice& key, uint64_t* timestamp); + virtual Status StringsRename(const Slice& key, Redis* new_inst, const Slice& newkey); + virtual Status HashesRename(const Slice& key, Redis* new_inst, const Slice& newkey); + virtual Status ListsRename(const Slice& key, Redis* new_inst, const Slice& newkey); + virtual Status ZsetsRename(const Slice& key, Redis* new_inst, const Slice& newkey); + virtual Status SetsRename(const Slice& key, Redis* new_inst, const Slice& newkey); + + virtual Status StringsRenamenx(const Slice& key, Redis* new_inst, const Slice& newkey); + virtual Status HashesRenamenx(const Slice& key, Redis* new_inst, const Slice& newkey); + virtual Status ListsRenamenx(const Slice& key, Redis* new_inst, const Slice& newkey); + virtual Status ZsetsRenamenx(const Slice& key, Redis* new_inst, const Slice& newkey); + virtual Status SetsRenamenx(const Slice& key, Redis* new_inst, const Slice& newkey); + // Strings Commands Status Append(const Slice& key, const Slice& value, int32_t* ret); Status BitCount(const Slice& key, int64_t start_offset, int64_t end_offset, int32_t* ret, bool have_range); diff --git a/src/storage/src/redis_hashes.cc b/src/storage/src/redis_hashes.cc index e479e0c26..039a0b356 100644 --- a/src/storage/src/redis_hashes.cc +++ b/src/storage/src/redis_hashes.cc @@ -1225,6 +1225,77 @@ Status Redis::HashesTTL(const Slice& key, uint64_t* timestamp) { return s; } +Status Redis::HashesRename(const Slice& key, Redis* new_inst, const Slice& newkey) { + std::string meta_value; + Status s; + uint64_t statistic = 0; + const std::vector keys = {key.ToString(), newkey.ToString()}; + MultiScopeRecordLock ml(lock_mgr_, keys); + + BaseMetaKey base_meta_key(key); + BaseMetaKey base_meta_newkey(newkey); + s = db_->Get(default_read_options_, handles_[kHashesMetaCF], base_meta_key.Encode(), &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(); + } + // copy a new hash with newkey + statistic = parsed_hashes_meta_value.Count(); + s = new_inst->GetDB()->Put(default_write_options_, handles_[kHashesMetaCF], base_meta_newkey.Encode(), meta_value); + new_inst->UpdateSpecificKeyStatistics(DataType::kHashes, newkey.ToString(), statistic); + + // HashesDel key + parsed_hashes_meta_value.InitialMetaValue(); + s = db_->Put(default_write_options_, handles_[kHashesMetaCF], base_meta_key.Encode(), meta_value); + UpdateSpecificKeyStatistics(DataType::kHashes, key.ToString(), statistic); + } + return s; +} + +Status Redis::HashesRenamenx(const Slice& key, Redis* new_inst, const Slice& newkey) { + std::string meta_value; + Status s; + uint64_t statistic = 0; + const std::vector keys = {key.ToString(), newkey.ToString()}; + MultiScopeRecordLock ml(lock_mgr_, keys); + + BaseMetaKey base_meta_key(key); + BaseMetaKey base_meta_newkey(newkey); + s = db_->Get(default_read_options_, handles_[kHashesMetaCF], base_meta_key.Encode(), &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(); + } + // check if newkey exists. + std::string new_meta_value; + s = new_inst->GetDB()->Get(default_read_options_, handles_[kHashesMetaCF], base_meta_newkey.Encode(), + &new_meta_value); + if (s.ok()) { + ParsedHashesMetaValue parsed_hashes_new_meta_value(&new_meta_value); + if (!parsed_hashes_new_meta_value.IsStale() && parsed_hashes_new_meta_value.Count() != 0) { + return Status::Corruption(); // newkey already exists. + } + } + + // copy a new hash with newkey + statistic = parsed_hashes_meta_value.Count(); + s = new_inst->GetDB()->Put(default_write_options_, handles_[kHashesMetaCF], base_meta_newkey.Encode(), meta_value); + new_inst->UpdateSpecificKeyStatistics(DataType::kHashes, newkey.ToString(), statistic); + + // HashesDel key + parsed_hashes_meta_value.InitialMetaValue(); + s = db_->Put(default_write_options_, handles_[kHashesMetaCF], base_meta_key.Encode(), meta_value); + UpdateSpecificKeyStatistics(DataType::kHashes, key.ToString(), statistic); + } + return s; +} + void Redis::ScanHashes() { rocksdb::ReadOptions iterator_options; const rocksdb::Snapshot* snapshot; diff --git a/src/storage/src/redis_lists.cc b/src/storage/src/redis_lists.cc index cdb98bfe8..db2b15b05 100644 --- a/src/storage/src/redis_lists.cc +++ b/src/storage/src/redis_lists.cc @@ -1085,6 +1085,75 @@ Status Redis::ListsTTL(const Slice& key, uint64_t* timestamp) { return s; } +Status Redis::ListsRename(const Slice& key, Redis* new_inst, const Slice& newkey) { + std::string meta_value; + uint32_t statistic = 0; + const std::vector keys = {key.ToString(), newkey.ToString()}; + MultiScopeRecordLock ml(lock_mgr_, keys); + + BaseMetaKey base_meta_key(key); + BaseMetaKey base_meta_newkey(newkey); + Status s = db_->Get(default_read_options_, handles_[kListsMetaCF], base_meta_key.Encode(), &meta_value); + if (s.ok()) { + ParsedListsMetaValue parsed_lists_meta_value(&meta_value); + if (parsed_lists_meta_value.IsStale()) { + return Status::NotFound("Stale"); + } else if (parsed_lists_meta_value.Count() == 0) { + return Status::NotFound(); + } + // copy a new list with newkey + statistic = parsed_lists_meta_value.Count(); + s = new_inst->GetDB()->Put(default_write_options_, handles_[kListsMetaCF], base_meta_newkey.Encode(), meta_value); + new_inst->UpdateSpecificKeyStatistics(DataType::kLists, newkey.ToString(), statistic); + + // ListsDel key + parsed_lists_meta_value.InitialMetaValue(); + s = db_->Put(default_write_options_, handles_[kListsMetaCF], base_meta_key.Encode(), meta_value); + UpdateSpecificKeyStatistics(DataType::kLists, key.ToString(), statistic); + } + return s; +} + +Status Redis::ListsRenamenx(const Slice& key, Redis* new_inst, const Slice& newkey) { + std::string meta_value; + uint32_t statistic = 0; + const std::vector keys = {key.ToString(), newkey.ToString()}; + MultiScopeRecordLock ml(lock_mgr_, keys); + + BaseMetaKey base_meta_key(key); + BaseMetaKey base_meta_newkey(newkey); + Status s = db_->Get(default_read_options_, handles_[kListsMetaCF], base_meta_key.Encode(), &meta_value); + if (s.ok()) { + ParsedListsMetaValue parsed_lists_meta_value(&meta_value); + if (parsed_lists_meta_value.IsStale()) { + return Status::NotFound("Stale"); + } else if (parsed_lists_meta_value.Count() == 0) { + return Status::NotFound(); + } + // check if newkey exists. + std::string new_meta_value; + s = new_inst->GetDB()->Get(default_read_options_, handles_[kListsMetaCF], base_meta_newkey.Encode(), + &new_meta_value); + if (s.ok()) { + ParsedSetsMetaValue parsed_lists_new_meta_value(&new_meta_value); + if (!parsed_lists_new_meta_value.IsStale() && parsed_lists_new_meta_value.Count() != 0) { + return Status::Corruption(); // newkey already exists. + } + } + + // copy a new list with newkey + statistic = parsed_lists_meta_value.Count(); + s = new_inst->GetDB()->Put(default_write_options_, handles_[kListsMetaCF], base_meta_newkey.Encode(), meta_value); + new_inst->UpdateSpecificKeyStatistics(DataType::kLists, newkey.ToString(), statistic); + + // ListsDel key + parsed_lists_meta_value.InitialMetaValue(); + s = db_->Put(default_write_options_, handles_[kListsMetaCF], base_meta_key.Encode(), meta_value); + UpdateSpecificKeyStatistics(DataType::kLists, key.ToString(), statistic); + } + return s; +} + void Redis::ScanLists() { rocksdb::ReadOptions iterator_options; const rocksdb::Snapshot* snapshot; diff --git a/src/storage/src/redis_sets.cc b/src/storage/src/redis_sets.cc index c7541fcb6..a97e6c959 100644 --- a/src/storage/src/redis_sets.cc +++ b/src/storage/src/redis_sets.cc @@ -10,6 +10,7 @@ #include #include #include +#include #include @@ -1316,6 +1317,75 @@ rocksdb::Status Redis::SetsTTL(const Slice& key, uint64_t* timestamp) { return s; } +Status Redis::SetsRename(const Slice& key, Redis* new_inst, const Slice& newkey) { + std::string meta_value; + uint32_t statistic = 0; + const std::vector keys = {key.ToString(), newkey.ToString()}; + MultiScopeRecordLock ml(lock_mgr_, keys); + + BaseMetaKey base_meta_key(key); + BaseMetaKey base_meta_newkey(newkey); + rocksdb::Status s = db_->Get(default_read_options_, handles_[kSetsMetaCF], base_meta_key.Encode(), &meta_value); + if (s.ok()) { + ParsedSetsMetaValue parsed_sets_meta_value(&meta_value); + if (parsed_sets_meta_value.IsStale()) { + return rocksdb::Status::NotFound("Stale"); + } else if (parsed_sets_meta_value.Count() == 0) { + return rocksdb::Status::NotFound(); + } + // copy a new set with newkey + statistic = parsed_sets_meta_value.Count(); + s = new_inst->GetDB()->Put(default_write_options_, handles_[kSetsMetaCF], base_meta_newkey.Encode(), meta_value); + new_inst->UpdateSpecificKeyStatistics(DataType::kSets, newkey.ToString(), statistic); + + // SetsDel key + parsed_sets_meta_value.InitialMetaValue(); + s = db_->Put(default_write_options_, handles_[kSetsMetaCF], base_meta_key.Encode(), meta_value); + UpdateSpecificKeyStatistics(DataType::kSets, key.ToString(), statistic); + } + return s; +} + +Status Redis::SetsRenamenx(const Slice& key, Redis* new_inst, const Slice& newkey) { + std::string meta_value; + uint32_t statistic = 0; + const std::vector keys = {key.ToString(), newkey.ToString()}; + MultiScopeRecordLock ml(lock_mgr_, keys); + + BaseMetaKey base_meta_key(key); + BaseMetaKey base_meta_newkey(newkey); + rocksdb::Status s = db_->Get(default_read_options_, handles_[kSetsMetaCF], base_meta_key.Encode(), &meta_value); + if (s.ok()) { + ParsedSetsMetaValue parsed_sets_meta_value(&meta_value); + if (parsed_sets_meta_value.IsStale()) { + return rocksdb::Status::NotFound("Stale"); + } else if (parsed_sets_meta_value.Count() == 0) { + return rocksdb::Status::NotFound(); + } + // check if newkey exists. + std::string new_meta_value; + s = new_inst->GetDB()->Get(default_read_options_, handles_[kSetsMetaCF], base_meta_newkey.Encode(), + &new_meta_value); + if (s.ok()) { + ParsedSetsMetaValue parsed_sets_new_meta_value(&new_meta_value); + if (!parsed_sets_new_meta_value.IsStale() && parsed_sets_new_meta_value.Count() != 0) { + return Status::Corruption(); // newkey already exists. + } + } + + // copy a new set with newkey + statistic = parsed_sets_meta_value.Count(); + s = new_inst->GetDB()->Put(default_write_options_, handles_[kSetsMetaCF], base_meta_newkey.Encode(), meta_value); + new_inst->UpdateSpecificKeyStatistics(DataType::kSets, newkey.ToString(), statistic); + + // SetsDel key + parsed_sets_meta_value.InitialMetaValue(); + s = db_->Put(default_write_options_, handles_[kSetsMetaCF], base_meta_key.Encode(), meta_value); + UpdateSpecificKeyStatistics(DataType::kSets, key.ToString(), statistic); + } + return s; +} + void Redis::ScanSets() { rocksdb::ReadOptions iterator_options; const rocksdb::Snapshot* snapshot; diff --git a/src/storage/src/redis_strings.cc b/src/storage/src/redis_strings.cc index 24a677921..aac17b200 100644 --- a/src/storage/src/redis_strings.cc +++ b/src/storage/src/redis_strings.cc @@ -1194,6 +1194,54 @@ Status Redis::StringsTTL(const Slice& key, uint64_t* timestamp) { return s; } +Status Redis::StringsRename(const Slice& key, Redis* new_inst, const Slice& newkey) { + std::string value; + Status s; + const std::vector keys = {key.ToString(), newkey.ToString()}; + MultiScopeRecordLock ml(lock_mgr_, keys); + + BaseKey base_key(key); + BaseKey base_newkey(newkey); + s = db_->Get(default_read_options_, base_key.Encode(), &value); + if (s.ok()) { + ParsedStringsValue parsed_strings_value(&value); + if (parsed_strings_value.IsStale()) { + return Status::NotFound("Stale"); + } + db_->Delete(default_write_options_, base_key.Encode()); + s = new_inst->GetDB()->Put(default_write_options_, base_newkey.Encode(), value); + } + return s; +} + +Status Redis::StringsRenamenx(const Slice& key, Redis* new_inst, const Slice& newkey) { + std::string value; + Status s; + const std::vector keys = {key.ToString(), newkey.ToString()}; + MultiScopeRecordLock ml(lock_mgr_, keys); + + BaseKey base_key(key); + BaseKey base_newkey(newkey); + s = db_->Get(default_read_options_, base_key.Encode(), &value); + if (s.ok()) { + ParsedStringsValue parsed_strings_value(&value); + if (parsed_strings_value.IsStale()) { + return Status::NotFound("Stale"); + } + // check if newkey exists. + s = new_inst->GetDB()->Get(default_read_options_, base_newkey.Encode(), &value); + if (s.ok()) { + ParsedStringsValue parsed_strings_value(&value); + if (!parsed_strings_value.IsStale()) { + return Status::Corruption(); // newkey already exists. + } + } + db_->Delete(default_write_options_, base_key.Encode()); + s = new_inst->GetDB()->Put(default_write_options_, base_newkey.Encode(), value); + } + return s; +} + void Redis::ScanStrings() { rocksdb::ReadOptions iterator_options; const rocksdb::Snapshot* snapshot; diff --git a/src/storage/src/redis_zsets.cc b/src/storage/src/redis_zsets.cc index 3532b4cca..44e9afc8e 100644 --- a/src/storage/src/redis_zsets.cc +++ b/src/storage/src/redis_zsets.cc @@ -1658,6 +1658,75 @@ Status Redis::ZsetsTTL(const Slice& key, uint64_t* timestamp) { return s; } +Status Redis::ZsetsRename(const Slice& key, Redis* new_inst, const Slice& newkey) { + std::string meta_value; + uint32_t statistic = 0; + const std::vector keys = {key.ToString(), newkey.ToString()}; + MultiScopeRecordLock ml(lock_mgr_, keys); + + BaseMetaKey base_meta_key(key); + BaseMetaKey base_meta_newkey(newkey); + Status s = db_->Get(default_read_options_, handles_[kZsetsMetaCF], base_meta_key.Encode(), &meta_value); + if (s.ok()) { + ParsedZSetsMetaValue parsed_zsets_meta_value(&meta_value); + if (parsed_zsets_meta_value.IsStale()) { + return Status::NotFound("Stale"); + } else if (parsed_zsets_meta_value.Count() == 0) { + return Status::NotFound(); + } + // copy a new zset with newkey + statistic = parsed_zsets_meta_value.Count(); + s = new_inst->GetDB()->Put(default_write_options_, handles_[kZsetsMetaCF], base_meta_newkey.Encode(), meta_value); + new_inst->UpdateSpecificKeyStatistics(DataType::kZSets, newkey.ToString(), statistic); + + // ZsetsDel key + parsed_zsets_meta_value.InitialMetaValue(); + s = db_->Put(default_write_options_, handles_[kZsetsMetaCF], base_meta_key.Encode(), meta_value); + UpdateSpecificKeyStatistics(DataType::kZSets, key.ToString(), statistic); + } + return s; +} + +Status Redis::ZsetsRenamenx(const Slice& key, Redis* new_inst, const Slice& newkey) { + std::string meta_value; + uint32_t statistic = 0; + const std::vector keys = {key.ToString(), newkey.ToString()}; + MultiScopeRecordLock ml(lock_mgr_, keys); + + BaseMetaKey base_meta_key(key); + BaseMetaKey base_meta_newkey(newkey); + Status s = db_->Get(default_read_options_, handles_[kZsetsMetaCF], base_meta_key.Encode(), &meta_value); + if (s.ok()) { + ParsedZSetsMetaValue parsed_zsets_meta_value(&meta_value); + if (parsed_zsets_meta_value.IsStale()) { + return Status::NotFound("Stale"); + } else if (parsed_zsets_meta_value.Count() == 0) { + return Status::NotFound(); + } + // check if newkey exist. + std::string new_meta_value; + s = new_inst->GetDB()->Get(default_read_options_, handles_[kZsetsMetaCF], base_meta_newkey.Encode(), + &new_meta_value); + if (s.ok()) { + ParsedSetsMetaValue parsed_zsets_new_meta_value(&new_meta_value); + if (!parsed_zsets_new_meta_value.IsStale() && parsed_zsets_new_meta_value.Count() != 0) { + return Status::Corruption(); // newkey already exists. + } + } + + // copy a new zset with newkey + statistic = parsed_zsets_meta_value.Count(); + s = new_inst->GetDB()->Put(default_write_options_, handles_[kZsetsMetaCF], base_meta_newkey.Encode(), meta_value); + new_inst->UpdateSpecificKeyStatistics(DataType::kZSets, newkey.ToString(), statistic); + + // ZsetsDel key + parsed_zsets_meta_value.InitialMetaValue(); + s = db_->Put(default_write_options_, handles_[kZsetsMetaCF], base_meta_key.Encode(), meta_value); + UpdateSpecificKeyStatistics(DataType::kZSets, key.ToString(), statistic); + } + return s; +} + void Redis::ScanZsets() { rocksdb::ReadOptions iterator_options; const rocksdb::Snapshot* snapshot; diff --git a/src/storage/src/storage.cc b/src/storage/src/storage.cc index 342f7aeb7..776ca09a7 100644 --- a/src/storage/src/storage.cc +++ b/src/storage/src/storage.cc @@ -1918,6 +1918,102 @@ Status Storage::Keys(const DataType& data_type, const std::string& pattern, std: return Status::OK(); } +Status Storage::Rename(const std::string& key, const std::string& newkey) { + Status ret = Status::NotFound(); + auto& inst = GetDBInstance(key); + auto& new_inst = GetDBInstance(newkey); + + // Strings + Status s = inst->StringsRename(key, new_inst.get(), newkey); + if (s.ok()) { + ret = Status::OK(); + } else if (!s.IsNotFound()) { + return s; + } + + // Hashes + s = inst->HashesRename(key, new_inst.get(), newkey); + if (s.ok()) { + ret = Status::OK(); + } else if (!s.IsNotFound()) { + return s; + } + + // Sets + s = inst->SetsRename(key, new_inst.get(), newkey); + if (s.ok()) { + ret = Status::OK(); + } else if (!s.IsNotFound()) { + return s; + } + + // Lists + s = inst->ListsRename(key, new_inst.get(), newkey); + if (s.ok()) { + ret = Status::OK(); + } else if (!s.IsNotFound()) { + return s; + } + + // ZSets + s = inst->ZsetsRename(key, new_inst.get(), newkey); + if (s.ok()) { + ret = Status::OK(); + } else if (!s.IsNotFound()) { + return s; + } + + return ret; +} + +Status Storage::Renamenx(const std::string& key, const std::string& newkey) { + Status ret = Status::NotFound(); + auto& inst = GetDBInstance(key); + auto& new_inst = GetDBInstance(newkey); + + // Strings + Status s = inst->StringsRenamenx(key, new_inst.get(), newkey); + if (s.ok()) { + ret = Status::OK(); + } else if (!s.IsNotFound()) { + return s; + } + + // Hashes + s = inst->HashesRenamenx(key, new_inst.get(), newkey); + if (s.ok()) { + ret = Status::OK(); + } else if (!s.IsNotFound()) { + return s; + } + + // Sets + s = inst->SetsRenamenx(key, new_inst.get(), newkey); + if (s.ok()) { + ret = Status::OK(); + } else if (!s.IsNotFound()) { + return s; + } + + // Lists + s = inst->ListsRenamenx(key, new_inst.get(), newkey); + if (s.ok()) { + ret = Status::OK(); + } else if (!s.IsNotFound()) { + return s; + } + + // ZSets + s = inst->ZsetsRenamenx(key, new_inst.get(), newkey); + if (s.ok()) { + ret = Status::OK(); + } else if (!s.IsNotFound()) { + return s; + } + + return ret; +} + void Storage::ScanDatabase(const DataType& type) { for (const auto& inst : insts_) { switch (type) { diff --git a/tests/key_test.go b/tests/key_test.go index 91a4a5db5..ff457ffc0 100644 --- a/tests/key_test.go +++ b/tests/key_test.go @@ -317,4 +317,40 @@ var _ = Describe("Keyspace", Ordered, func() { Expect(client.Do(ctx, "pexpire", DefaultKey, "err").Err()).To(MatchError("ERR value is not an integer or out of range")) }) + It("should Rename", func () { + client.Set(ctx, "mykey", "hello", 0) + client.Rename(ctx, "mykey", "mykey1") + client.Rename(ctx, "mykey1", "mykey2") + Expect(client.Get(ctx, "mykey2").Val()).To(Equal("hello")) + + Expect(client.Exists(ctx, "mykey").Val()).To(Equal(int64(0))) + + client.Set(ctx, "mykey", "foo", 0) + Expect(client.Rename(ctx, "mykey", "mykey").Val()).To(Equal(OK)) + + client.Del(ctx, "mykey", "mykey2") + client.Set(ctx, "mykey", "foo", 0) + client.Set(ctx, "mykey2", "bar", 0) + client.Expire(ctx, "mykey2", 100 * time.Second) + Expect(client.TTL(ctx, "mykey").Val()).To(Equal(-1 * time.Nanosecond)) + Expect(client.TTL(ctx, "mykey2").Val()).NotTo(Equal(-1 * time.Nanosecond)) + client.Rename(ctx, "mykey", "mykey2") + Expect(client.TTL(ctx, "mykey2").Val()).To(Equal(-1 * time.Nanosecond)) + }) + + It("should RenameNX", func () { + client.Del(ctx, "mykey", "mykey1", "mykey2") + client.Set(ctx, "mykey", "hello", 0) + client.RenameNX(ctx, "mykey", "mykey1") + client.RenameNX(ctx, "mykey1", "mykey2") + Expect(client.Get(ctx, "mykey2").Val()).To(Equal("hello")) + + client.Set(ctx, "mykey", "foo", 0) + client.Set(ctx, "mykey2", "bar", 0) + Expect(client.RenameNX(ctx, "mykey", "mykey2").Val()).To(Equal(false)) + + client.Set(ctx, "mykey", "foo", 0) + Expect(client.RenameNX(ctx, "mykey", "mykey").Val()).To(Equal(false)) + }) + }) diff --git a/tests/unit/basic.tcl b/tests/unit/basic.tcl index 6988e46a2..f9f29d22d 100644 --- a/tests/unit/basic.tcl +++ b/tests/unit/basic.tcl @@ -1,8 +1,9 @@ start_server {tags {"basic"}} { - test {DEL all keys to start with a clean DB} { - foreach key [r keys *] {r del $key} - r dbsize - } {0} + +# test {DEL all keys to start with a clean DB} { +# foreach key [r keys *] {r del $key} +# r dbsize +# } {0} test {SET and GET an item} { r set x foobar @@ -37,18 +38,18 @@ start_server {tags {"basic"}} { lsort [r keys *] } {foo_a foo_b foo_c key_x key_y key_z} - test {DBSIZE} { - r info keyspace 1 - after 1000 - r dbsize - } {6} - - test {DEL all keys} { - foreach key [r keys *] {r del $key} - r info keyspace 1 - after 1000 - r dbsize - } {0} +# test {DBSIZE} { +# r info keyspace 1 +# after 1000 +# r dbsize +# } {6} + +# test {DEL all keys} { +# foreach key [r keys *] {r del $key} +# r info keyspace 1 +# after 1000 +# r dbsize +# } {0} # test {Very big payload in GET/SET} { # set buf [string repeat "abcd" 1000000] @@ -291,89 +292,105 @@ start_server {tags {"basic"}} { append res [r exists emptykey] } {10} - test {Commands pipelining} { - set fd [r channel] - puts -nonewline $fd "SET k1 xyzk\r\nGET k1\r\nPING\r\n" - flush $fd - set res {} - append res [string match OK* [r read]] - append res [r read] - append res [string match PONG* [r read]] - format $res - } {1xyzk1} +# test {Commands pipelining} { +# set fd [r channel] +# puts -nonewline $fd "SET k1 xyzk\r\nGET k1\r\nPING\r\n" +# flush $fd +# set res {} +# append res [string match OK* [r read]] +# append res [r read] +# append res [string match PONG* [r read]] +# format $res +# } {1xyzk1} test {Non existing command} { catch {r foobaredcommand} err string match ERR* $err } {1} + + test {RENAME basic usage} { + r set mykey hello + r rename mykey mykey1 + r rename mykey1 mykey2 + r get mykey2 + } {hello} + + test {RENAME source key should no longer exist} { + r exists mykey + } {0} -# test {RENAME basic usage} { -# r set mykey hello -# r rename mykey mykey1 -# r rename mykey1 mykey2 -# r get mykey2 -# } {hello} + test {RENAME against already existing key} { + r set mykey a + r set mykey2 b + r rename mykey2 mykey + set res [r get mykey] + append res [r exists mykey2] + } {b0} -# test {RENAME source key should no longer exist} { -# r exists mykey -# } {0} + test {RENAMENX basic usage} { + r del mykey + r del mykey2 + r set mykey foobar + r renamenx mykey mykey2 + set res [r get mykey2] + append res [r exists mykey] + } {foobar0} + + test {RENAMENX against already existing key} { + r set mykey foo + r set mykey2 bar + r renamenx mykey mykey2 + } {0} -# test {RENAME against already existing key} { -# r set mykey a -# r set mykey2 b -# r rename mykey2 mykey -# set res [r get mykey] -# append res [r exists mykey2] -# } {b0} + test {RENAMENX against already existing key (2)} { + set res [r get mykey] + append res [r get mykey2] + } {foobar} -# test {RENAMENX basic usage} { -# r del mykey -# r del mykey2 -# r set mykey foobar -# r renamenx mykey mykey2 -# set res [r get mykey2] -# append res [r exists mykey] -# } {foobar0} -# -# test {RENAMENX against already existing key} { -# r set mykey foo -# r set mykey2 bar -# r renamenx mykey mykey2 -# } {0} -# -# test {RENAMENX against already existing key (2)} { -# set res [r get mykey] -# append res [r get mykey2] -# } {foobar} -# -# test {RENAME against non existing source key} { -# catch {r rename nokey foobar} err -# format $err -# } {ERR*} -# -# test {RENAME where source and dest key is the same} { -# catch {r rename mykey mykey} err -# format $err -# } {ERR*} -# -# test {RENAME with volatile key, should move the TTL as well} { -# r del mykey mykey2 -# r set mykey foo -# r expire mykey 100 -# assert {[r ttl mykey] > 95 && [r ttl mykey] <= 100} -# r rename mykey mykey2 -# assert {[r ttl mykey2] > 95 && [r ttl mykey2] <= 100} -# } -# -# test {RENAME with volatile key, should not inherit TTL of target key} { -# r del mykey mykey2 -# r set mykey foo -# r set mykey2 bar -# r expire mykey2 100 -# assert {[r ttl mykey] == -1 && [r ttl mykey2] > 0} -# r rename mykey mykey2 -# r ttl mykey2 -# } {-1} + test {RENAME against non existing source key} { + catch {r rename nokey foobar} err + format $err + } {ERR*} + + test {RENAMENX against non existing source key} { + catch {r renamenx nokey foobar} err + format $err + } {ERR*} + + test {RENAME where source and dest key are the same (existing)} { + r set mykey foo + r rename mykey mykey + } {OK} + + test {RENAMENX where source and dest key are the same (existing)} { + r set mykey foo + r renamenx mykey mykey + } {0} + + test {RENAME where source and dest key are the same (non existing)} { + r del mykey + catch {r rename mykey mykey} err + format $err + } {ERR*} + + test {RENAME with volatile key, should move the TTL as well} { + r del mykey mykey2 + r set mykey foo + r expire mykey 100 + assert {[r ttl mykey] > 95 && [r ttl mykey] <= 100} + r rename mykey mykey2 + assert {[r ttl mykey2] > 95 && [r ttl mykey2] <= 100} + } + + test {RENAME with volatile key, should not inherit TTL of target key} { + r del mykey mykey2 + r set mykey foo + r set mykey2 bar + r expire mykey2 100 + assert {[r ttl mykey] == -1 && [r ttl mykey2] > 0} + r rename mykey mykey2 + r ttl mykey2 + } {-1} # test {DEL all keys again (DB 0)} { # foreach key [r keys *] {