blob: 1a304561749d30eef0b5188591eb8a0d5ad9d15f [file]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
#include <cluster/redis_slot.h>
#include <fmt/format.h>
#include <storage/iterator.h>
#include <types/redis_bitmap.h>
#include <types/redis_bloom_chain.h>
#include <types/redis_json.h>
#include <types/redis_list.h>
#include <types/redis_set.h>
#include <types/redis_sortedint.h>
#include <types/redis_stream.h>
#include <types/redis_zset.h>
#include "test_base.h"
#include "types/redis_string.h"
class DBIteratorTest : public TestBase {
protected:
explicit DBIteratorTest() = default;
~DBIteratorTest() override = default;
void SetUp() override {
{ // string
redis::String string(storage_.get(), "test_ns0");
string.Set(*ctx_, "a", "1");
string.Set(*ctx_, "b", "2");
string.Set(*ctx_, "c", "3");
// Make sure the key "c" is expired
auto s = string.Expire(*ctx_, "c", 1);
ASSERT_TRUE(s.ok());
string.Set(*ctx_, "d", "4");
}
{ // hash
uint64_t ret = 0;
redis::Hash hash(storage_.get(), "test_ns1");
hash.MSet(*ctx_, "hash-1", {{"f0", "v0"}, {"f1", "v1"}, {"f2", "v2"}, {"f3", "v3"}}, false, &ret);
}
{ // set
uint64_t ret = 0;
redis::Set set(storage_.get(), "test_ns2");
set.Add(*ctx_, "set-1", {"e0", "e1", "e2"}, &ret);
}
{ // sorted set
uint64_t ret = 0;
redis::ZSet zset(storage_.get(), "test_ns3");
auto mscores = std::vector<MemberScore>{{"z0", 0}, {"z1", 1}, {"z2", 2}};
zset.Add(*ctx_, "zset-1", ZAddFlags(), &mscores, &ret);
}
{ // list
uint64_t ret = 0;
redis::List list(storage_.get(), "test_ns4");
list.Push(*ctx_, "list-1", {"l0", "l1", "l2"}, false, &ret);
}
{ // stream
redis::Stream stream(storage_.get(), "test_ns5");
redis::StreamEntryID ret;
redis::StreamAddOptions options;
options.next_id_strategy = std::make_unique<redis::AutoGeneratedEntryID>();
stream.Add(*ctx_, "stream-1", options, {"x0"}, &ret);
stream.Add(*ctx_, "stream-1", options, {"x1"}, &ret);
stream.Add(*ctx_, "stream-1", options, {"x2"}, &ret);
// TODO(@git-hulk): add stream group after it's finished
}
{ // bitmap
redis::Bitmap bitmap(storage_.get(), "test_ns6");
bool ret = false;
bitmap.SetBit(*ctx_, "bitmap-1", 0, true, &ret);
bitmap.SetBit(*ctx_, "bitmap-1", 8 * 1024, true, &ret);
bitmap.SetBit(*ctx_, "bitmap-1", 2 * 8 * 1024, true, &ret);
}
{ // json
redis::Json json(storage_.get(), "test_ns7");
json.Set(*ctx_, "json-1", "$", "{\"a\": 1, \"b\": 2}");
json.Set(*ctx_, "json-2", "$", "{\"a\": 1, \"b\": 2}");
json.Set(*ctx_, "json-3", "$", "{\"a\": 1, \"b\": 2}");
json.Set(*ctx_, "json-4", "$", "{\"a\": 1, \"b\": 2}");
auto s = json.Expire(*ctx_, "json-4", 1);
ASSERT_TRUE(s.ok());
}
{
// sorted integer
redis::Sortedint sortedint(storage_.get(), "test_ns8");
uint64_t ret = 0;
sortedint.Add(*ctx_, "sortedint-1", {1, 2, 3}, &ret);
}
}
};
TEST_F(DBIteratorTest, AllKeys) {
engine::DBIterator iter(*ctx_, rocksdb::ReadOptions());
std::vector<std::string> live_keys = {"a", "b", "d", "hash-1", "set-1", "zset-1", "list-1",
"stream-1", "bitmap-1", "json-1", "json-2", "json-3", "sortedint-1"};
std::reverse(live_keys.begin(), live_keys.end());
for (iter.Seek(); iter.Valid(); iter.Next()) {
ASSERT_TRUE(!live_keys.empty());
auto [_, user_key] = iter.UserKey();
ASSERT_EQ(live_keys.back(), user_key.ToString());
live_keys.pop_back();
}
ASSERT_TRUE(live_keys.empty());
}
TEST_F(DBIteratorTest, BasicString) {
engine::DBIterator iter(*ctx_, rocksdb::ReadOptions());
std::vector<std::string> expected_keys = {"a", "b", "d"};
std::reverse(expected_keys.begin(), expected_keys.end());
auto prefix = ComposeNamespaceKey("test_ns0", "", storage_->IsSlotIdEncoded());
for (iter.Seek(prefix); iter.Valid() && iter.Key().starts_with(prefix); iter.Next()) {
if (expected_keys.empty()) {
FAIL() << "Unexpected key: " << iter.Key().ToString();
}
ASSERT_EQ(kRedisString, iter.Type());
auto [ns, key] = iter.UserKey();
ASSERT_EQ("test_ns0", ns.ToString());
ASSERT_EQ(expected_keys.back(), key.ToString());
expected_keys.pop_back();
// Make sure there is no subkey iterator
ASSERT_TRUE(!iter.GetSubKeyIterator());
}
// Make sure all keys are iterated except the expired one: "c"
ASSERT_TRUE(expected_keys.empty());
}
TEST_F(DBIteratorTest, BasicHash) {
engine::DBIterator iter(*ctx_, rocksdb::ReadOptions());
auto prefix = ComposeNamespaceKey("test_ns1", "", storage_->IsSlotIdEncoded());
for (iter.Seek(prefix); iter.Valid() && iter.Key().starts_with(prefix); iter.Next()) {
ASSERT_EQ(kRedisHash, iter.Type());
auto [ns, key] = iter.UserKey();
ASSERT_EQ("test_ns1", ns.ToString());
auto subkey_iter = iter.GetSubKeyIterator();
ASSERT_TRUE(subkey_iter);
std::vector<std::string> expected_keys = {"f0", "f1", "f2", "f3"};
std::reverse(expected_keys.begin(), expected_keys.end());
for (subkey_iter->Seek(); subkey_iter->Valid(); subkey_iter->Next()) {
if (expected_keys.empty()) {
FAIL() << "Unexpected key: " << subkey_iter->UserKey().ToString();
}
ASSERT_EQ(expected_keys.back(), subkey_iter->UserKey().ToString());
expected_keys.pop_back();
}
ASSERT_TRUE(expected_keys.empty());
}
}
TEST_F(DBIteratorTest, BasicSet) {
engine::DBIterator iter(*ctx_, rocksdb::ReadOptions());
auto prefix = ComposeNamespaceKey("test_ns2", "", storage_->IsSlotIdEncoded());
for (iter.Seek(prefix); iter.Valid() && iter.Key().starts_with(prefix); iter.Next()) {
ASSERT_EQ(kRedisSet, iter.Type());
auto [ns, key] = iter.UserKey();
ASSERT_EQ("test_ns2", ns.ToString());
auto subkey_iter = iter.GetSubKeyIterator();
ASSERT_TRUE(subkey_iter);
std::vector<std::string> expected_keys = {"e0", "e1", "e2"};
std::reverse(expected_keys.begin(), expected_keys.end());
for (subkey_iter->Seek(); subkey_iter->Valid(); subkey_iter->Next()) {
if (expected_keys.empty()) {
FAIL() << "Unexpected key: " << subkey_iter->UserKey().ToString();
}
ASSERT_EQ(expected_keys.back(), subkey_iter->UserKey().ToString());
expected_keys.pop_back();
}
ASSERT_TRUE(expected_keys.empty());
}
}
TEST_F(DBIteratorTest, BasicZSet) {
engine::DBIterator iter(*ctx_, rocksdb::ReadOptions());
auto prefix = ComposeNamespaceKey("test_ns3", "", storage_->IsSlotIdEncoded());
for (iter.Seek(prefix); iter.Valid() && iter.Key().starts_with(prefix); iter.Next()) {
ASSERT_EQ(kRedisZSet, iter.Type());
auto [ns, key] = iter.UserKey();
ASSERT_EQ("test_ns3", ns.ToString());
auto subkey_iter = iter.GetSubKeyIterator();
ASSERT_TRUE(subkey_iter);
std::vector<std::string> expected_members = {"z0", "z1", "z2"};
std::reverse(expected_members.begin(), expected_members.end());
for (subkey_iter->Seek(); subkey_iter->Valid(); subkey_iter->Next()) {
if (expected_members.empty()) {
FAIL() << "Unexpected key: " << subkey_iter->UserKey().ToString();
}
ASSERT_EQ(expected_members.back(), subkey_iter->UserKey().ToString());
expected_members.pop_back();
}
ASSERT_TRUE(expected_members.empty());
}
}
TEST_F(DBIteratorTest, BasicList) {
engine::DBIterator iter(*ctx_, rocksdb::ReadOptions());
auto prefix = ComposeNamespaceKey("test_ns4", "", storage_->IsSlotIdEncoded());
for (iter.Seek(prefix); iter.Valid() && iter.Key().starts_with(prefix); iter.Next()) {
ASSERT_EQ(kRedisList, iter.Type());
auto [ns, key] = iter.UserKey();
ASSERT_EQ("test_ns4", ns.ToString());
auto subkey_iter = iter.GetSubKeyIterator();
ASSERT_TRUE(subkey_iter);
std::vector<std::string> expected_values = {"l0", "l1", "l2"};
std::reverse(expected_values.begin(), expected_values.end());
for (subkey_iter->Seek(); subkey_iter->Valid(); subkey_iter->Next()) {
if (expected_values.empty()) {
FAIL() << "Unexpected value: " << subkey_iter->Value().ToString();
}
ASSERT_EQ(expected_values.back(), subkey_iter->Value().ToString());
expected_values.pop_back();
}
ASSERT_TRUE(expected_values.empty());
}
}
TEST_F(DBIteratorTest, BasicStream) {
engine::DBIterator iter(*ctx_, rocksdb::ReadOptions());
auto prefix = ComposeNamespaceKey("test_ns5", "", storage_->IsSlotIdEncoded());
for (iter.Seek(prefix); iter.Valid() && iter.Key().starts_with(prefix); iter.Next()) {
ASSERT_EQ(kRedisStream, iter.Type());
auto [ns, key] = iter.UserKey();
ASSERT_EQ("test_ns5", ns.ToString());
auto subkey_iter = iter.GetSubKeyIterator();
ASSERT_TRUE(subkey_iter);
std::vector<std::string> expected_values = {"x0", "x1", "x2"};
std::reverse(expected_values.begin(), expected_values.end());
for (subkey_iter->Seek(); subkey_iter->Valid(); subkey_iter->Next()) {
if (expected_values.empty()) {
FAIL() << "Unexpected value: " << subkey_iter->Value().ToString();
}
std::vector<std::string> elems;
auto s = redis::DecodeRawStreamEntryValue(subkey_iter->Value().ToString(), &elems);
ASSERT_TRUE(s.IsOK() && !elems.empty());
ASSERT_EQ(expected_values.back(), elems[0]);
expected_values.pop_back();
}
ASSERT_TRUE(expected_values.empty());
}
}
TEST_F(DBIteratorTest, BasicBitmap) {
engine::DBIterator iter(*ctx_, rocksdb::ReadOptions());
auto prefix = ComposeNamespaceKey("test_ns6", "", storage_->IsSlotIdEncoded());
for (iter.Seek(prefix); iter.Valid() && iter.Key().starts_with(prefix); iter.Next()) {
ASSERT_EQ(kRedisBitmap, iter.Type());
auto [ns, key] = iter.UserKey();
ASSERT_EQ("test_ns6", ns.ToString());
auto subkey_iter = iter.GetSubKeyIterator();
ASSERT_TRUE(subkey_iter);
std::vector<std::string> expected_values = {"\x1", "\x1", "\x1"};
for (subkey_iter->Seek(); subkey_iter->Valid(); subkey_iter->Next()) {
if (expected_values.empty()) {
FAIL() << "Unexpected value: " << subkey_iter->Value().ToString();
}
ASSERT_EQ(expected_values.back(), subkey_iter->Value().ToString());
expected_values.pop_back();
}
ASSERT_TRUE(expected_values.empty());
}
}
TEST_F(DBIteratorTest, BasicJSON) {
engine::DBIterator iter(*ctx_, rocksdb::ReadOptions());
std::vector<std::string> expected_keys = {"json-1", "json-2", "json-3"};
std::reverse(expected_keys.begin(), expected_keys.end());
auto prefix = ComposeNamespaceKey("test_ns7", "", storage_->IsSlotIdEncoded());
for (iter.Seek(prefix); iter.Valid() && iter.Key().starts_with(prefix); iter.Next()) {
if (expected_keys.empty()) {
FAIL() << "Unexpected key: " << iter.Key().ToString();
}
ASSERT_EQ(kRedisJson, iter.Type());
auto [ns, key] = iter.UserKey();
ASSERT_EQ("test_ns7", ns.ToString());
ASSERT_EQ(expected_keys.back(), key.ToString());
expected_keys.pop_back();
// Make sure there is no subkey iterator
ASSERT_TRUE(!iter.GetSubKeyIterator());
}
// Make sure all keys are iterated except the expired one: "json-4"
ASSERT_TRUE(expected_keys.empty());
}
TEST_F(DBIteratorTest, BasicSortedInt) {
engine::DBIterator iter(*ctx_, rocksdb::ReadOptions());
auto prefix = ComposeNamespaceKey("test_ns8", "", storage_->IsSlotIdEncoded());
for (iter.Seek(prefix); iter.Valid() && iter.Key().starts_with(prefix); iter.Next()) {
ASSERT_EQ(kRedisSortedint, iter.Type());
auto [ns, key] = iter.UserKey();
ASSERT_EQ("test_ns8", ns.ToString());
auto subkey_iter = iter.GetSubKeyIterator();
ASSERT_TRUE(subkey_iter);
std::vector<uint64_t> expected_keys = {1, 2, 3};
std::reverse(expected_keys.begin(), expected_keys.end());
for (subkey_iter->Seek(); subkey_iter->Valid(); subkey_iter->Next()) {
auto value = DecodeFixed64(subkey_iter->UserKey().data());
if (expected_keys.empty()) {
FAIL() << "Unexpected value: " << value;
}
ASSERT_EQ(expected_keys.back(), value);
expected_keys.pop_back();
}
}
}
class SlotIteratorTest : public TestBase {
protected:
explicit SlotIteratorTest() = default;
~SlotIteratorTest() override = default;
void SetUp() override { storage_->GetConfig()->slot_id_encoded = true; }
};
TEST_F(SlotIteratorTest, LiveKeys) {
redis::String string(storage_.get(), kDefaultNamespace);
auto start_seq = storage_->GetDB()->GetLatestSequenceNumber();
std::vector<std::string> keys = {"{x}a", "{x}b", "{y}c", "{y}d", "{x}e"};
for (const auto &key : keys) {
string.Set(*ctx_, key, "1");
}
std::set<std::string> same_slot_keys;
auto slot_id = GetSlotIdFromKey(keys[0]);
for (const auto &key : keys) {
if (GetSlotIdFromKey(key) == slot_id) {
same_slot_keys.insert(key);
}
}
engine::DBIterator iter(*ctx_, rocksdb::ReadOptions(), slot_id);
int count = 0;
for (iter.Seek(); iter.Valid(); iter.Next()) {
auto [_, user_key] = iter.UserKey();
ASSERT_EQ(slot_id, GetSlotIdFromKey(user_key.ToString()));
count++;
}
ASSERT_EQ(count, same_slot_keys.size());
engine::WALIterator wal_iter(storage_.get(), slot_id);
count = 0;
for (wal_iter.Seek(start_seq + 1); wal_iter.Valid(); wal_iter.Next()) {
auto item = wal_iter.Item();
if (item.type == engine::WALItem::Type::kTypePut) {
auto [_, user_key] = ExtractNamespaceKey(item.key, storage_->IsSlotIdEncoded());
ASSERT_EQ(slot_id, GetSlotIdFromKey(user_key.ToString())) << user_key.ToString();
count++;
}
}
ASSERT_EQ(count, same_slot_keys.size());
}
class WALIteratorTest : public TestBase {
protected:
explicit WALIteratorTest() = default;
~WALIteratorTest() override = default;
void SetUp() override {}
};
TEST_F(WALIteratorTest, BasicString) {
auto start_seq = storage_->GetDB()->GetLatestSequenceNumber();
redis::String string(storage_.get(), "test_ns0");
string.Set(*ctx_, "a", "1");
string.MSet(*ctx_, {{"b", "2"}, {"c", "3"}});
ASSERT_TRUE(string.Del(*ctx_, "b").ok());
std::vector<std::string> put_keys, delete_keys;
auto expected_put_keys = {"a", "b", "c"};
auto expected_delete_keys = {"b"};
engine::WALIterator iter(storage_.get());
for (iter.Seek(start_seq + 1); iter.Valid(); iter.Next()) {
auto item = iter.Item();
switch (item.type) {
case engine::WALItem::Type::kTypePut: {
auto [ns, key] = ExtractNamespaceKey(item.key, storage_->IsSlotIdEncoded());
ASSERT_EQ(ns.ToString(), "test_ns0");
put_keys.emplace_back(key.ToString());
break;
}
case engine::WALItem::Type::kTypeLogData: {
redis::WriteBatchLogData log_data;
ASSERT_TRUE(log_data.Decode(item.key).IsOK());
ASSERT_EQ(log_data.GetRedisType(), kRedisString);
break;
}
case engine::WALItem::Type::kTypeDelete: {
auto [ns, key] = ExtractNamespaceKey(item.key, storage_->IsSlotIdEncoded());
ASSERT_EQ(ns.ToString(), "test_ns0");
delete_keys.emplace_back(key.ToString());
break;
}
default:
FAIL() << "Unexpected wal item type" << uint8_t(item.type);
}
}
ASSERT_EQ(expected_put_keys.size(), put_keys.size());
ASSERT_EQ(expected_delete_keys.size(), delete_keys.size());
ASSERT_TRUE(std::equal(expected_put_keys.begin(), expected_put_keys.end(), put_keys.begin()));
ASSERT_TRUE(std::equal(expected_delete_keys.begin(), expected_delete_keys.end(), delete_keys.begin()));
}
TEST_F(WALIteratorTest, BasicHash) {
auto start_seq = storage_->GetDB()->GetLatestSequenceNumber();
redis::Hash hash(storage_.get(), "test_ns1");
uint64_t ret = 0;
hash.MSet(*ctx_, "hash-1", {{"f0", "v0"}, {"f1", "v1"}, {"f2", "v2"}, {"f3", "v3"}}, false, &ret);
uint64_t deleted_cnt = 0;
hash.Delete(*ctx_, "hash-1", {"f0"}, &deleted_cnt);
// Delete will put meta key again
auto expected_put_keys = {"hash-1", "hash-1"};
// Sub key will be putted in reverse order
auto expected_put_fields = {"f3", "f2", "f1", "f0"};
auto expected_delete_fields = {"f0"};
std::vector<std::string> put_keys, put_fields, delete_fields;
engine::WALIterator iter(storage_.get());
for (iter.Seek(start_seq + 1); iter.Valid(); iter.Next()) {
auto item = iter.Item();
switch (item.type) {
case engine::WALItem::Type::kTypePut: {
if (item.column_family_id == static_cast<uint32_t>(ColumnFamilyID::PrimarySubkey)) {
InternalKey internal_key(item.key, storage_->IsSlotIdEncoded());
put_fields.emplace_back(internal_key.GetSubKey().ToString());
} else if (item.column_family_id == static_cast<uint32_t>(ColumnFamilyID::Metadata)) {
auto [ns, key] = ExtractNamespaceKey(item.key, storage_->IsSlotIdEncoded());
ASSERT_EQ(ns.ToString(), "test_ns1");
put_keys.emplace_back(key.ToString());
}
break;
}
case engine::WALItem::Type::kTypeLogData: {
redis::WriteBatchLogData log_data;
ASSERT_TRUE(log_data.Decode(item.key).IsOK());
ASSERT_EQ(log_data.GetRedisType(), kRedisHash);
break;
}
case engine::WALItem::Type::kTypeDelete: {
InternalKey internal_key(item.key, storage_->IsSlotIdEncoded());
delete_fields.emplace_back(internal_key.GetSubKey().ToString());
break;
}
default:
FAIL() << "Unexpected wal item type" << uint8_t(item.type);
}
}
ASSERT_EQ(expected_put_keys.size(), put_keys.size());
ASSERT_EQ(expected_put_fields.size(), put_fields.size());
ASSERT_EQ(expected_delete_fields.size(), delete_fields.size());
ASSERT_TRUE(std::equal(expected_put_keys.begin(), expected_put_keys.end(), put_keys.begin()));
ASSERT_TRUE(std::equal(expected_put_fields.begin(), expected_put_fields.end(), put_fields.begin()));
ASSERT_TRUE(std::equal(expected_delete_fields.begin(), expected_delete_fields.end(), delete_fields.begin()));
}
TEST_F(WALIteratorTest, BasicSet) {
auto start_seq = storage_->GetDB()->GetLatestSequenceNumber();
uint64_t ret = 0;
redis::Set set(storage_.get(), "test_ns2");
set.Add(*ctx_, "set-1", {"e0", "e1", "e2"}, &ret);
uint64_t removed_cnt = 0;
set.Remove(*ctx_, "set-1", {"e0", "e1"}, &removed_cnt);
auto expected_put_keys = {"set-1", "set-1"};
auto expected_put_members = {"e0", "e1", "e2"};
auto expected_delete_members = {"e0", "e1"};
std::vector<std::string> put_keys, put_members, delete_members;
engine::WALIterator iter(storage_.get());
for (iter.Seek(start_seq + 1); iter.Valid(); iter.Next()) {
auto item = iter.Item();
switch (item.type) {
case engine::WALItem::Type::kTypePut: {
if (item.column_family_id == static_cast<uint32_t>(ColumnFamilyID::PrimarySubkey)) {
InternalKey internal_key(item.key, storage_->IsSlotIdEncoded());
put_members.emplace_back(internal_key.GetSubKey().ToString());
} else if (item.column_family_id == static_cast<uint32_t>(ColumnFamilyID::Metadata)) {
auto [ns, key] = ExtractNamespaceKey(item.key, storage_->IsSlotIdEncoded());
ASSERT_EQ(ns.ToString(), "test_ns2");
put_keys.emplace_back(key.ToString());
}
break;
}
case engine::WALItem::Type::kTypeLogData: {
redis::WriteBatchLogData log_data;
ASSERT_TRUE(log_data.Decode(item.key).IsOK());
ASSERT_EQ(log_data.GetRedisType(), kRedisSet);
break;
}
case engine::WALItem::Type::kTypeDelete: {
InternalKey internal_key(item.key, storage_->IsSlotIdEncoded());
delete_members.emplace_back(internal_key.GetSubKey().ToString());
break;
}
default:
FAIL() << "Unexpected wal item type" << uint8_t(item.type);
}
}
ASSERT_EQ(expected_put_keys.size(), put_keys.size());
ASSERT_EQ(expected_put_members.size(), put_members.size());
ASSERT_EQ(expected_delete_members.size(), delete_members.size());
ASSERT_TRUE(std::equal(expected_put_keys.begin(), expected_put_keys.end(), put_keys.begin()));
ASSERT_TRUE(std::equal(expected_put_members.begin(), expected_put_members.end(), put_members.begin()));
ASSERT_TRUE(std::equal(expected_delete_members.begin(), expected_delete_members.end(), delete_members.begin()));
}
TEST_F(WALIteratorTest, BasicZSet) {
auto start_seq = storage_->GetDB()->GetLatestSequenceNumber();
uint64_t ret = 0;
redis::ZSet zset(storage_.get(), "test_ns3");
auto mscores = std::vector<MemberScore>{{"z0", 0}, {"z1", 1}, {"z2", 2}};
zset.Add(*ctx_, "zset-1", ZAddFlags(), &mscores, &ret);
uint64_t removed_cnt = 0;
zset.Remove(*ctx_, "zset-1", {"z0"}, &removed_cnt);
auto expected_put_keys = {"zset-1", "zset-1"};
auto expected_put_members = {"z2", "z1", "z0"};
// member and score
int expected_delete_count = 2, delete_count = 0;
std::vector<std::string> put_keys, put_members;
engine::WALIterator iter(storage_.get());
for (iter.Seek(start_seq + 1); iter.Valid(); iter.Next()) {
auto item = iter.Item();
switch (item.type) {
case engine::WALItem::Type::kTypePut: {
if (item.column_family_id == static_cast<uint32_t>(ColumnFamilyID::PrimarySubkey)) {
InternalKey internal_key(item.key, storage_->IsSlotIdEncoded());
put_members.emplace_back(internal_key.GetSubKey().ToString());
} else if (item.column_family_id == static_cast<uint32_t>(ColumnFamilyID::Metadata)) {
auto [ns, key] = ExtractNamespaceKey(item.key, storage_->IsSlotIdEncoded());
ASSERT_EQ(ns.ToString(), "test_ns3");
put_keys.emplace_back(key.ToString());
}
break;
}
case engine::WALItem::Type::kTypeLogData: {
redis::WriteBatchLogData log_data;
ASSERT_TRUE(log_data.Decode(item.key).IsOK());
ASSERT_EQ(log_data.GetRedisType(), kRedisZSet);
break;
}
case engine::WALItem::Type::kTypeDelete: {
delete_count++;
break;
}
default:
FAIL() << "Unexpected wal item type" << uint8_t(item.type);
}
}
ASSERT_EQ(expected_put_keys.size(), put_keys.size());
ASSERT_EQ(expected_put_members.size(), put_members.size());
ASSERT_EQ(expected_delete_count, delete_count);
ASSERT_TRUE(std::equal(expected_put_keys.begin(), expected_put_keys.end(), put_keys.begin()));
ASSERT_TRUE(std::equal(expected_put_members.begin(), expected_put_members.end(), put_members.begin()));
}
TEST_F(WALIteratorTest, BasicList) {
auto start_seq = storage_->GetDB()->GetLatestSequenceNumber();
uint64_t ret = 0;
redis::List list(storage_.get(), "test_ns4");
list.Push(*ctx_, "list-1", {"l0", "l1", "l2", "l3", "l4"}, false, &ret);
ASSERT_TRUE(list.Trim(*ctx_, "list-1", 2, 4).ok());
auto expected_put_keys = {"list-1", "list-1"};
auto expected_put_values = {"l0", "l1", "l2", "l3", "l4"};
auto expected_delete_count = 2, delete_count = 0;
std::vector<std::string> put_keys, put_values;
engine::WALIterator iter(storage_.get());
for (iter.Seek(start_seq + 1); iter.Valid(); iter.Next()) {
auto item = iter.Item();
switch (item.type) {
case engine::WALItem::Type::kTypePut: {
if (item.column_family_id == static_cast<uint32_t>(ColumnFamilyID::PrimarySubkey)) {
put_values.emplace_back(item.value);
} else if (item.column_family_id == static_cast<uint32_t>(ColumnFamilyID::Metadata)) {
auto [ns, key] = ExtractNamespaceKey(item.key, storage_->IsSlotIdEncoded());
ASSERT_EQ(ns.ToString(), "test_ns4");
put_keys.emplace_back(key.ToString());
}
break;
}
case engine::WALItem::Type::kTypeLogData: {
redis::WriteBatchLogData log_data;
ASSERT_TRUE(log_data.Decode(item.key).IsOK());
ASSERT_EQ(log_data.GetRedisType(), kRedisList);
break;
}
case engine::WALItem::Type::kTypeDelete: {
delete_count++;
break;
}
default:
FAIL() << "Unexpected wal item type" << uint8_t(item.type);
}
}
ASSERT_EQ(expected_put_keys.size(), put_keys.size());
ASSERT_EQ(expected_put_values.size(), put_values.size());
ASSERT_EQ(expected_delete_count, delete_count);
ASSERT_TRUE(std::equal(expected_put_keys.begin(), expected_put_keys.end(), put_keys.begin()));
ASSERT_TRUE(std::equal(expected_put_values.begin(), expected_put_values.end(), put_values.begin()));
}
TEST_F(WALIteratorTest, BasicStream) {
auto start_seq = storage_->GetDB()->GetLatestSequenceNumber();
redis::Stream stream(storage_.get(), "test_ns5");
redis::StreamEntryID ret;
redis::StreamAddOptions options;
options.next_id_strategy = std::make_unique<redis::AutoGeneratedEntryID>();
stream.Add(*ctx_, "stream-1", options, {"x0"}, &ret);
stream.Add(*ctx_, "stream-1", options, {"x1"}, &ret);
stream.Add(*ctx_, "stream-1", options, {"x2"}, &ret);
uint64_t deleted = 0;
ASSERT_TRUE(stream.DeleteEntries(*ctx_, "stream-1", {ret}, &deleted).ok());
auto expected_put_keys = {"stream-1", "stream-1", "stream-1", "stream-1"};
auto expected_put_values = {"x0", "x1", "x2"};
int delete_count = 0;
std::vector<std::string> put_keys, put_values;
engine::WALIterator iter(storage_.get());
for (iter.Seek(start_seq + 1); iter.Valid(); iter.Next()) {
auto item = iter.Item();
switch (item.type) {
case engine::WALItem::Type::kTypePut: {
if (item.column_family_id == static_cast<uint32_t>(ColumnFamilyID::Stream)) {
std::vector<std::string> elems;
auto s = redis::DecodeRawStreamEntryValue(item.value, &elems);
ASSERT_TRUE(s.IsOK() && !elems.empty());
put_values.emplace_back(elems[0]);
} else if (item.column_family_id == static_cast<uint32_t>(ColumnFamilyID::Metadata)) {
auto [ns, key] = ExtractNamespaceKey(item.key, storage_->IsSlotIdEncoded());
ASSERT_EQ(ns.ToString(), "test_ns5");
put_keys.emplace_back(key.ToString());
}
break;
}
case engine::WALItem::Type::kTypeLogData: {
redis::WriteBatchLogData log_data;
ASSERT_TRUE(log_data.Decode(item.key).IsOK());
ASSERT_EQ(log_data.GetRedisType(), kRedisStream);
break;
}
case engine::WALItem::Type::kTypeDelete: {
delete_count++;
break;
}
default:
FAIL() << "Unexpected wal item type" << uint8_t(item.type);
}
}
ASSERT_EQ(expected_put_keys.size(), put_keys.size());
ASSERT_EQ(expected_put_values.size(), put_values.size());
ASSERT_EQ(deleted, delete_count);
ASSERT_TRUE(std::equal(expected_put_keys.begin(), expected_put_keys.end(), put_keys.begin()));
ASSERT_TRUE(std::equal(expected_put_values.begin(), expected_put_values.end(), put_values.begin()));
}
TEST_F(WALIteratorTest, BasicBitmap) {
auto start_seq = storage_->GetDB()->GetLatestSequenceNumber();
redis::Bitmap bitmap(storage_.get(), "test_ns6");
bool ret = false;
bitmap.SetBit(*ctx_, "bitmap-1", 0, true, &ret);
bitmap.SetBit(*ctx_, "bitmap-1", 8 * 1024, true, &ret);
bitmap.SetBit(*ctx_, "bitmap-1", 2 * 8 * 1024, true, &ret);
auto expected_put_values = {"\x1", "\x1", "\x1"};
std::vector<std::string> put_values;
engine::WALIterator iter(storage_.get());
for (iter.Seek(start_seq + 1); iter.Valid(); iter.Next()) {
auto item = iter.Item();
switch (item.type) {
case engine::WALItem::Type::kTypePut: {
if (item.column_family_id == static_cast<uint32_t>(ColumnFamilyID::PrimarySubkey)) {
put_values.emplace_back(item.value);
}
break;
}
case engine::WALItem::Type::kTypeLogData: {
redis::WriteBatchLogData log_data;
ASSERT_TRUE(log_data.Decode(item.key).IsOK());
ASSERT_EQ(log_data.GetRedisType(), kRedisBitmap);
break;
}
default:
FAIL() << "Unexpected wal item type" << uint8_t(item.type);
}
}
ASSERT_EQ(expected_put_values.size(), put_values.size());
ASSERT_TRUE(std::equal(expected_put_values.begin(), expected_put_values.end(), put_values.begin()));
}
TEST_F(WALIteratorTest, BasicJSON) {
auto start_seq = storage_->GetDB()->GetLatestSequenceNumber();
redis::Json json(storage_.get(), "test_ns7");
json.Set(*ctx_, "json-1", "$", "{\"a\": 1, \"b\": 2}");
json.Set(*ctx_, "json-2", "$", "{\"a\": 1, \"b\": 2}");
json.Set(*ctx_, "json-3", "$", "{\"a\": 1, \"b\": 2}");
size_t result = 0;
ASSERT_TRUE(json.Del(*ctx_, "json-3", "$", &result).ok());
auto expected_put_keys = {"json-1", "json-2", "json-3"};
auto expected_delete_keys = {"json-3"};
std::vector<std::string> put_keys, delete_keys;
engine::WALIterator iter(storage_.get());
for (iter.Seek(start_seq + 1); iter.Valid(); iter.Next()) {
auto item = iter.Item();
switch (item.type) {
case engine::WALItem::Type::kTypePut: {
ASSERT_EQ(item.column_family_id, static_cast<uint32_t>(ColumnFamilyID::Metadata));
auto [ns, key] = ExtractNamespaceKey(item.key, storage_->IsSlotIdEncoded());
ASSERT_EQ(ns.ToString(), "test_ns7");
put_keys.emplace_back(key.ToString());
break;
}
case engine::WALItem::Type::kTypeLogData: {
redis::WriteBatchLogData log_data;
ASSERT_TRUE(log_data.Decode(item.key).IsOK());
ASSERT_EQ(log_data.GetRedisType(), kRedisJson);
break;
}
case engine::WALItem::Type::kTypeDelete: {
ASSERT_EQ(item.column_family_id, static_cast<uint32_t>(ColumnFamilyID::Metadata));
auto [ns, key] = ExtractNamespaceKey(item.key, storage_->IsSlotIdEncoded());
ASSERT_EQ(ns.ToString(), "test_ns7");
delete_keys.emplace_back(key.ToString());
break;
}
default:
FAIL() << "Unexpected wal item type" << uint8_t(item.type);
}
}
ASSERT_EQ(expected_put_keys.size(), put_keys.size());
ASSERT_EQ(expected_delete_keys.size(), delete_keys.size());
ASSERT_TRUE(std::equal(expected_put_keys.begin(), expected_put_keys.end(), put_keys.begin()));
ASSERT_TRUE(std::equal(expected_delete_keys.begin(), expected_delete_keys.end(), delete_keys.begin()));
}
TEST_F(WALIteratorTest, BasicSortedInt) {
auto start_seq = storage_->GetDB()->GetLatestSequenceNumber();
redis::Sortedint sortedint(storage_.get(), "test_ns8");
uint64_t ret = 0;
sortedint.Add(*ctx_, "sortedint-1", {1, 2, 3}, &ret);
uint64_t removed_cnt = 0;
sortedint.Remove(*ctx_, "sortedint-1", {2}, &removed_cnt);
std::vector<uint64_t> expected_values = {1, 2, 3}, put_values;
std::vector<uint64_t> expected_delete_values = {2}, delete_values;
engine::WALIterator iter(storage_.get());
for (iter.Seek(start_seq + 1); iter.Valid(); iter.Next()) {
auto item = iter.Item();
switch (item.type) {
case engine::WALItem::Type::kTypePut: {
if (item.column_family_id == static_cast<uint32_t>(ColumnFamilyID::PrimarySubkey)) {
const InternalKey internal_key(item.key, storage_->IsSlotIdEncoded());
auto value = DecodeFixed64(internal_key.GetSubKey().data());
put_values.emplace_back(value);
}
break;
}
case engine::WALItem::Type::kTypeLogData: {
redis::WriteBatchLogData log_data;
ASSERT_TRUE(log_data.Decode(item.key).IsOK());
ASSERT_EQ(log_data.GetRedisType(), kRedisSortedint);
break;
}
case engine::WALItem::Type::kTypeDelete: {
const InternalKey internal_key(item.key, storage_->IsSlotIdEncoded());
auto value = DecodeFixed64(internal_key.GetSubKey().data());
delete_values.emplace_back(value);
break;
}
default:
FAIL() << "Unexpected wal item type" << uint8_t(item.type);
}
}
ASSERT_EQ(expected_values.size(), put_values.size());
ASSERT_EQ(expected_delete_values.size(), delete_values.size());
ASSERT_TRUE(std::equal(expected_values.begin(), expected_values.end(), put_values.begin()));
ASSERT_TRUE(std::equal(expected_delete_values.begin(), expected_delete_values.end(), delete_values.begin()));
}
TEST_F(WALIteratorTest, NextSequence) {
std::vector<rocksdb::SequenceNumber> expected_next_sequences;
std::set<rocksdb::SequenceNumber> next_sequences_set;
auto start_seq = storage_->GetDB()->GetLatestSequenceNumber();
uint64_t ret = 0;
redis::List list(storage_.get(), "test_ns2");
list.Push(*ctx_, "list-1", {"l0", "l1", "l2", "l3", "l4"}, false, &ret);
expected_next_sequences.emplace_back(storage_->GetDB()->GetLatestSequenceNumber() + 1);
list.Push(*ctx_, "list-2", {"l0", "l1", "l2"}, false, &ret);
expected_next_sequences.emplace_back(storage_->GetDB()->GetLatestSequenceNumber() + 1);
ASSERT_TRUE(list.Trim(*ctx_, "list-1", 2, 4).ok());
expected_next_sequences.emplace_back(storage_->GetDB()->GetLatestSequenceNumber() + 1);
engine::WALIterator iter(storage_.get());
ASSERT_EQ(iter.NextSequenceNumber(), 0);
for (iter.Seek(start_seq + 1); iter.Valid(); iter.Next()) {
next_sequences_set.emplace(iter.NextSequenceNumber());
}
std::vector<rocksdb::SequenceNumber> next_sequences(next_sequences_set.begin(), next_sequences_set.end());
ASSERT_EQ(expected_next_sequences.size(), next_sequences.size());
ASSERT_TRUE(std::equal(expected_next_sequences.begin(), expected_next_sequences.end(), next_sequences.begin()));
}