This is an automated email from the ASF dual-hosted git repository.
twice pushed a commit to branch unstable
in repository https://gitbox.apache.org/repos/asf/kvrocks.git
The following commit(s) were added to refs/heads/unstable by this push:
new 723829ff Add the support of the BF.INSERT command (#1768)
723829ff is described below
commit 723829ff2ba3a65683296e3a50954126763ec00b
Author: Leo <[email protected]>
AuthorDate: Fri Sep 22 15:58:20 2023 +0800
Add the support of the BF.INSERT command (#1768)
---
src/commands/cmd_bloom_filter.cc | 136 ++++++++++++++++++++++++++---
src/types/redis_bloom_chain.cc | 23 +++--
src/types/redis_bloom_chain.h | 20 +++--
tests/gocase/unit/type/bloom/bloom_test.go | 94 ++++++++++++++++++--
4 files changed, 242 insertions(+), 31 deletions(-)
diff --git a/src/commands/cmd_bloom_filter.cc b/src/commands/cmd_bloom_filter.cc
index ded31857..611d1b9d 100644
--- a/src/commands/cmd_bloom_filter.cc
+++ b/src/commands/cmd_bloom_filter.cc
@@ -24,6 +24,18 @@
#include "server/server.h"
#include "types/redis_bloom_chain.h"
+namespace {
+
+constexpr const char *errBadErrorRate = "Bad error rate";
+constexpr const char *errBadCapacity = "Bad capacity";
+constexpr const char *errBadExpansion = "Bad expansion";
+constexpr const char *errInvalidErrorRate = "error rate should be between 0
and 1";
+constexpr const char *errInvalidCapacity = "capacity should be larger than 0";
+constexpr const char *errInvalidExpansion = "expansion should be greater or
equal to 1";
+constexpr const char *errNonscalingButExpand = "nonscaling filters cannot
expand";
+constexpr const char *errFilterFull = "ERR nonscaling filter is full";
+} // namespace
+
namespace redis {
class CommandBFReserve : public Commander {
@@ -31,20 +43,20 @@ class CommandBFReserve : public Commander {
Status Parse(const std::vector<std::string> &args) override {
auto parse_error_rate = ParseFloat<double>(args[2]);
if (!parse_error_rate) {
- return {Status::RedisParseErr, errValueIsNotFloat};
+ return {Status::RedisParseErr, errBadErrorRate};
}
error_rate_ = *parse_error_rate;
if (error_rate_ >= 1 || error_rate_ <= 0) {
- return {Status::RedisParseErr, "error rate should be between 0 and 1"};
+ return {Status::RedisParseErr, errInvalidErrorRate};
}
auto parse_capacity = ParseInt<uint32_t>(args[3], 10);
if (!parse_capacity) {
- return {Status::RedisParseErr, errValueNotInteger};
+ return {Status::RedisParseErr, errBadCapacity};
}
capacity_ = *parse_capacity;
if (capacity_ <= 0) {
- return {Status::RedisParseErr, "capacity should be larger than 0"};
+ return {Status::RedisParseErr, errInvalidCapacity};
}
CommandParser parser(args, 4);
@@ -56,9 +68,13 @@ class CommandBFReserve : public Commander {
expansion_ = 0;
} else if (parser.EatEqICase("expansion")) {
has_expansion = true;
- expansion_ = GET_OR_RET(parser.TakeInt<uint16_t>());
+ auto parse_expansion = parser.TakeInt<uint16_t>();
+ if (!parse_expansion.IsOK()) {
+ return {Status::RedisParseErr, errBadExpansion};
+ }
+ expansion_ = parse_expansion.GetValue();
if (expansion_ < 1) {
- return {Status::RedisParseErr, "expansion should be greater or equal
to 1"};
+ return {Status::RedisParseErr, errInvalidExpansion};
}
} else {
return {Status::RedisParseErr, errInvalidSyntax};
@@ -66,7 +82,7 @@ class CommandBFReserve : public Commander {
}
if (is_nonscaling && has_expansion) {
- return {Status::RedisParseErr, "nonscaling filters cannot expand"};
+ return {Status::RedisParseErr, errNonscalingButExpand};
}
return Commander::Parse(args);
@@ -103,7 +119,7 @@ class CommandBFAdd : public Commander {
*output = redis::Integer(0);
break;
case BloomFilterAddResult::kFull:
- *output = redis::Error("ERR nonscaling filter is full");
+ *output = redis::Error(errFilterFull);
break;
}
return Status::OK();
@@ -136,7 +152,103 @@ class CommandBFMAdd : public Commander {
*output += redis::Integer(0);
break;
case BloomFilterAddResult::kFull:
- *output += redis::Error("ERR nonscaling filter is full");
+ *output += redis::Error(errFilterFull);
+ break;
+ }
+ }
+ return Status::OK();
+ }
+
+ private:
+ std::vector<std::string> items_;
+};
+
+class CommandBFInsert : public Commander {
+ public:
+ Status Parse(const std::vector<std::string> &args) override {
+ CommandParser parser(args, 2);
+ bool is_nonscaling = false;
+ bool has_expansion = false;
+ bool has_items = false;
+ while (parser.Good()) {
+ if (parser.EatEqICase("capacity")) {
+ auto parse_capacity = parser.TakeInt<uint32_t>();
+ if (!parse_capacity.IsOK()) {
+ return {Status::RedisParseErr, errBadCapacity};
+ }
+ insert_options_.capacity = parse_capacity.GetValue();
+ if (insert_options_.capacity <= 0) {
+ return {Status::RedisParseErr, errInvalidCapacity};
+ }
+ } else if (parser.EatEqICase("error")) {
+ auto parse_error_rate = parser.TakeFloat<double>();
+ if (!parse_error_rate.IsOK()) {
+ return {Status::RedisParseErr, errBadErrorRate};
+ }
+ insert_options_.error_rate = parse_error_rate.GetValue();
+ if (insert_options_.error_rate >= 1 || insert_options_.error_rate <=
0) {
+ return {Status::RedisParseErr, errInvalidErrorRate};
+ }
+ } else if (parser.EatEqICase("nocreate")) {
+ insert_options_.auto_create = false;
+ } else if (parser.EatEqICase("nonscaling")) {
+ is_nonscaling = true;
+ insert_options_.expansion = 0;
+ } else if (parser.EatEqICase("expansion")) {
+ has_expansion = true;
+ auto parse_expansion = parser.TakeInt<uint16_t>();
+ if (!parse_expansion.IsOK()) {
+ return {Status::RedisParseErr, errBadExpansion};
+ }
+ insert_options_.expansion = parse_expansion.GetValue();
+ if (insert_options_.expansion < 1) {
+ return {Status::RedisParseErr, errInvalidExpansion};
+ }
+ } else if (parser.EatEqICase("items")) {
+ has_items = true;
+ break;
+ } else {
+ return {Status::RedisParseErr, errInvalidSyntax};
+ }
+ }
+
+ if (is_nonscaling && has_expansion) {
+ return {Status::RedisParseErr, errNonscalingButExpand};
+ }
+
+ if (not has_items) {
+ return {Status::RedisParseErr, errInvalidSyntax};
+ }
+
+ while (parser.Good()) {
+ items_.emplace_back(GET_OR_RET(parser.TakeStr()));
+ }
+
+ if (items_.size() == 0) {
+ return {Status::RedisParseErr, "num of items should be greater than 0"};
+ }
+
+ return Commander::Parse(args);
+ }
+
+ Status Execute(Server *svr, Connection *conn, std::string *output) override {
+ redis::BloomChain bloom_db(svr->storage, conn->GetNamespace());
+ std::vector<BloomFilterAddResult> rets(items_.size(),
BloomFilterAddResult::kOk);
+ auto s = bloom_db.InsertCommon(args_[1], items_, insert_options_, &rets);
+ if (s.IsNotFound()) return {Status::RedisExecErr, "key is not found"};
+ if (!s.ok()) return {Status::RedisExecErr, s.ToString()};
+
+ *output = redis::MultiLen(items_.size());
+ for (size_t i = 0; i < items_.size(); ++i) {
+ switch (rets[i]) {
+ case BloomFilterAddResult::kOk:
+ *output += redis::Integer(1);
+ break;
+ case BloomFilterAddResult::kExist:
+ *output += redis::Integer(0);
+ break;
+ case BloomFilterAddResult::kFull:
+ *output += redis::Error(errFilterFull);
break;
}
}
@@ -144,7 +256,8 @@ class CommandBFMAdd : public Commander {
}
private:
- std::vector<Slice> items_;
+ std::vector<std::string> items_;
+ BloomFilterInsertOptions insert_options_;
};
class CommandBFExists : public Commander {
@@ -184,7 +297,7 @@ class CommandBFMExists : public Commander {
}
private:
- std::vector<Slice> items_;
+ std::vector<std::string> items_;
};
class CommandBFInfo : public Commander {
@@ -277,6 +390,7 @@ class CommandBFCard : public Commander {
REDIS_REGISTER_COMMANDS(MakeCmdAttr<CommandBFReserve>("bf.reserve", -4,
"write", 1, 1, 1),
MakeCmdAttr<CommandBFAdd>("bf.add", 3, "write", 1, 1,
1),
MakeCmdAttr<CommandBFMAdd>("bf.madd", -3, "write", 1,
1, 1),
+ MakeCmdAttr<CommandBFInsert>("bf.insert", -4, "write",
1, 1, 1),
MakeCmdAttr<CommandBFExists>("bf.exists", 3,
"read-only", 1, 1, 1),
MakeCmdAttr<CommandBFMExists>("bf.mexists", -3,
"read-only", 1, 1, 1),
MakeCmdAttr<CommandBFInfo>("bf.info", -2, "read-only",
1, 1, 1),
diff --git a/src/types/redis_bloom_chain.cc b/src/types/redis_bloom_chain.cc
index 592b6eec..aa5131c7 100644
--- a/src/types/redis_bloom_chain.cc
+++ b/src/types/redis_bloom_chain.cc
@@ -60,7 +60,7 @@ rocksdb::Status BloomChain::getBFDataList(const
std::vector<std::string> &bf_key
return rocksdb::Status::OK();
}
-void BloomChain::getItemHashList(const std::vector<Slice> &items,
std::vector<uint64_t> *item_hash_list) {
+void BloomChain::getItemHashList(const std::vector<std::string> &items,
std::vector<uint64_t> *item_hash_list) {
item_hash_list->reserve(items.size());
for (const auto &item : items) {
item_hash_list->push_back(BlockSplitBloomFilter::Hash(item.data(),
item.size()));
@@ -132,23 +132,31 @@ rocksdb::Status BloomChain::Reserve(const Slice
&user_key, uint32_t capacity, do
return createBloomChain(ns_key, error_rate, capacity, expansion,
&bloom_chain_metadata);
}
-rocksdb::Status BloomChain::Add(const Slice &user_key, const Slice &item,
BloomFilterAddResult *ret) {
+rocksdb::Status BloomChain::Add(const Slice &user_key, const std::string
&item, BloomFilterAddResult *ret) {
std::vector<BloomFilterAddResult> tmp{BloomFilterAddResult::kOk};
rocksdb::Status s = MAdd(user_key, {item}, &tmp);
*ret = tmp[0];
return s;
}
-rocksdb::Status BloomChain::MAdd(const Slice &user_key, const
std::vector<Slice> &items,
+rocksdb::Status BloomChain::MAdd(const Slice &user_key, const
std::vector<std::string> &items,
std::vector<BloomFilterAddResult> *rets) {
+ BloomFilterInsertOptions insert_options;
+ return InsertCommon(user_key, items, insert_options, rets);
+}
+
+rocksdb::Status BloomChain::InsertCommon(const Slice &user_key, const
std::vector<std::string> &items,
+ const BloomFilterInsertOptions
&insert_options,
+ std::vector<BloomFilterAddResult>
*rets) {
std::string ns_key = AppendNamespacePrefix(user_key);
LockGuard guard(storage_->GetLockManager(), ns_key);
BloomChainMetadata metadata;
rocksdb::Status s = getBloomChainMetadata(ns_key, &metadata);
- if (s.IsNotFound()) {
- s = createBloomChain(ns_key, kBFDefaultErrorRate, kBFDefaultInitCapacity,
kBFDefaultExpansion, &metadata);
+ if (s.IsNotFound() && insert_options.auto_create) {
+ s = createBloomChain(ns_key, insert_options.error_rate,
insert_options.capacity, insert_options.expansion,
+ &metadata);
}
if (!s.ok()) return s;
@@ -207,14 +215,15 @@ rocksdb::Status BloomChain::MAdd(const Slice &user_key,
const std::vector<Slice>
return storage_->Write(storage_->DefaultWriteOptions(),
batch->GetWriteBatch());
}
-rocksdb::Status BloomChain::Exists(const Slice &user_key, const Slice &item,
bool *exist) {
+rocksdb::Status BloomChain::Exists(const Slice &user_key, const std::string
&item, bool *exist) {
std::vector<bool> tmp{false};
rocksdb::Status s = MExists(user_key, {item}, &tmp);
*exist = tmp[0];
return s;
}
-rocksdb::Status BloomChain::MExists(const Slice &user_key, const
std::vector<Slice> &items, std::vector<bool> *exists) {
+rocksdb::Status BloomChain::MExists(const Slice &user_key, const
std::vector<std::string> &items,
+ std::vector<bool> *exists) {
std::string ns_key = AppendNamespacePrefix(user_key);
BloomChainMetadata metadata;
diff --git a/src/types/redis_bloom_chain.h b/src/types/redis_bloom_chain.h
index b2d53774..7190a6ff 100644
--- a/src/types/redis_bloom_chain.h
+++ b/src/types/redis_bloom_chain.h
@@ -45,6 +45,13 @@ enum class BloomFilterAddResult {
kFull,
};
+struct BloomFilterInsertOptions {
+ double error_rate = kBFDefaultErrorRate;
+ uint32_t capacity = kBFDefaultInitCapacity;
+ uint16_t expansion = kBFDefaultExpansion;
+ bool auto_create = true;
+};
+
struct BloomFilterInfo {
uint32_t capacity;
uint32_t bloom_bytes;
@@ -57,10 +64,13 @@ class BloomChain : public Database {
public:
BloomChain(engine::Storage *storage, const std::string &ns) :
Database(storage, ns) {}
rocksdb::Status Reserve(const Slice &user_key, uint32_t capacity, double
error_rate, uint16_t expansion);
- rocksdb::Status Add(const Slice &user_key, const Slice &item,
BloomFilterAddResult *ret);
- rocksdb::Status MAdd(const Slice &user_key, const std::vector<Slice> &items,
std::vector<BloomFilterAddResult> *rets);
- rocksdb::Status Exists(const Slice &user_key, const Slice &item, bool
*exist);
- rocksdb::Status MExists(const Slice &user_key, const std::vector<Slice>
&items, std::vector<bool> *exists);
+ rocksdb::Status Add(const Slice &user_key, const std::string &item,
BloomFilterAddResult *ret);
+ rocksdb::Status MAdd(const Slice &user_key, const std::vector<std::string>
&items,
+ std::vector<BloomFilterAddResult> *rets);
+ rocksdb::Status InsertCommon(const Slice &user_key, const
std::vector<std::string> &items,
+ const BloomFilterInsertOptions &insert_options,
std::vector<BloomFilterAddResult> *rets);
+ rocksdb::Status Exists(const Slice &user_key, const std::string &item, bool
*exist);
+ rocksdb::Status MExists(const Slice &user_key, const
std::vector<std::string> &items, std::vector<bool> *exists);
rocksdb::Status Info(const Slice &user_key, BloomFilterInfo *info);
private:
@@ -68,7 +78,7 @@ class BloomChain : public Database {
std::string getBFKey(const Slice &ns_key, const BloomChainMetadata
&metadata, uint16_t filters_index);
void getBFKeyList(const Slice &ns_key, const BloomChainMetadata &metadata,
std::vector<std::string> *bf_key_list);
rocksdb::Status getBFDataList(const std::vector<std::string> &bf_key_list,
std::vector<std::string> *bf_data_list);
- static void getItemHashList(const std::vector<Slice> &items,
std::vector<uint64_t> *item_hash_list);
+ static void getItemHashList(const std::vector<std::string> &items,
std::vector<uint64_t> *item_hash_list);
rocksdb::Status createBloomChain(const Slice &ns_key, double error_rate,
uint32_t capacity, uint16_t expansion,
BloomChainMetadata *metadata);
diff --git a/tests/gocase/unit/type/bloom/bloom_test.go
b/tests/gocase/unit/type/bloom/bloom_test.go
index 153296d2..ebfd952e 100644
--- a/tests/gocase/unit/type/bloom/bloom_test.go
+++ b/tests/gocase/unit/type/bloom/bloom_test.go
@@ -44,16 +44,16 @@ func TestBloom(t *testing.T) {
t.Run("Reserve a bloom filter with wrong error_rate", func(t
*testing.T) {
require.NoError(t, rdb.Del(ctx, key).Err())
- require.ErrorContains(t, rdb.Do(ctx, "bf.reserve", key, "abc",
"1000").Err(), "ERR value is not a valid float")
+ require.ErrorContains(t, rdb.Do(ctx, "bf.reserve", key, "abc",
"1000").Err(), "ERR Bad error rate")
require.ErrorContains(t, rdb.Do(ctx, "bf.reserve", key,
"-0.03", "1000").Err(), "ERR error rate should be between 0 and 1")
require.ErrorContains(t, rdb.Do(ctx, "bf.reserve", key, "1",
"1000").Err(), "ERR error rate should be between 0 and 1")
})
t.Run("Reserve a bloom filter with wrong capacity", func(t *testing.T) {
require.NoError(t, rdb.Del(ctx, key).Err())
- require.ErrorContains(t, rdb.Do(ctx, "bf.reserve", key, "0.01",
"qwe").Err(), "ERR value is not an integer")
+ require.ErrorContains(t, rdb.Do(ctx, "bf.reserve", key, "0.01",
"qwe").Err(), "ERR Bad capacity")
// capacity stored in uint32_t, if input is negative, the
parser will make an error.
- require.ErrorContains(t, rdb.Do(ctx, "bf.reserve", key, "0.01",
"-1000").Err(), "ERR value is not an integer or out of range")
+ require.ErrorContains(t, rdb.Do(ctx, "bf.reserve", key, "0.01",
"-1000").Err(), "ERR Bad capacity")
require.ErrorContains(t, rdb.Do(ctx, "bf.reserve", key, "0.01",
"0").Err(), "ERR capacity should be larger than 0")
})
@@ -69,12 +69,12 @@ func TestBloom(t *testing.T) {
t.Run("Reserve a bloom filter with wrong expansion", func(t *testing.T)
{
require.NoError(t, rdb.Del(ctx, key).Err())
- require.ErrorContains(t, rdb.Do(ctx, "bf.reserve", key, "0.01",
"1000", "expansion").Err(), "ERR no more item to parse")
+ require.ErrorContains(t, rdb.Do(ctx, "bf.reserve", key, "0.01",
"1000", "expansion").Err(), "Bad expansion")
require.ErrorContains(t, rdb.Do(ctx, "bf.reserve", key, "0.01",
"1000", "expansion", "0").Err(), "ERR expansion should be greater or equal to
1")
- require.ErrorContains(t, rdb.Do(ctx, "bf.reserve", key, "0.01",
"1000", "expansion", "asd").Err(), "ERR not started as an integer")
- require.ErrorContains(t, rdb.Do(ctx, "bf.reserve", key, "0.01",
"1000", "expansion", "-1").Err(), "ERR out of range of integer type")
- require.ErrorContains(t, rdb.Do(ctx, "bf.reserve", key, "0.01",
"1000", "expansion", "1.5").Err(), "ERR encounter non-integer characters")
- require.ErrorContains(t, rdb.Do(ctx, "bf.reserve", key, "0.01",
"1000", "expansion", "123asd").Err(), "ERR encounter non-integer characters")
+ require.ErrorContains(t, rdb.Do(ctx, "bf.reserve", key, "0.01",
"1000", "expansion", "asd").Err(), "ERR Bad expansion")
+ require.ErrorContains(t, rdb.Do(ctx, "bf.reserve", key, "0.01",
"1000", "expansion", "-1").Err(), "ERR Bad expansion")
+ require.ErrorContains(t, rdb.Do(ctx, "bf.reserve", key, "0.01",
"1000", "expansion", "1.5").Err(), "ERR Bad expansion")
+ require.ErrorContains(t, rdb.Do(ctx, "bf.reserve", key, "0.01",
"1000", "expansion", "123asd").Err(), "ERR Bad expansion")
})
t.Run("Reserve a bloom filter with nonscaling and expansion", func(t
*testing.T) {
@@ -227,6 +227,84 @@ func TestBloom(t *testing.T) {
require.Equal(t, []interface{}{"Capacity", int64(210), "Size",
int64(896), "Number of filters", int64(3), "Number of items inserted",
int64(91), "Expansion rate", int64(2)}, rdb.Do(ctx, "bf.info", key).Val())
})
+ t.Run("Insert but not create", func(t *testing.T) {
+ require.NoError(t, rdb.Del(ctx, key).Err())
+ require.ErrorContains(t, rdb.Do(ctx, "bf.insert", key,
"nocreate", "items", "items1").Err(), "key is not found")
+
+ })
+
+ t.Run("Insert with error_rate", func(t *testing.T) {
+ require.NoError(t, rdb.Del(ctx, key).Err())
+ require.ErrorContains(t, rdb.Do(ctx, "bf.insert", key, "error",
"abc", "items", "items1").Err(), "ERR Bad error rate")
+ require.ErrorContains(t, rdb.Do(ctx, "bf.insert", key, "error",
"-0.03", "items", "items1").Err(), "ERR error rate should be between 0 and 1")
+ require.ErrorContains(t, rdb.Do(ctx, "bf.insert", key, "error",
"1", "items", "items1").Err(), "ERR error rate should be between 0 and 1")
+
+ require.NoError(t, rdb.Do(ctx, "bf.insert", key, "error",
"0.0001", "items", "items1").Err())
+ require.Equal(t, []interface{}{"Capacity", int64(100), "Size",
int64(512), "Number of filters", int64(1), "Number of items inserted",
int64(1), "Expansion rate", int64(2)}, rdb.Do(ctx, "bf.info", key).Val())
+ })
+
+ t.Run("Insert with capacity", func(t *testing.T) {
+ require.NoError(t, rdb.Del(ctx, key).Err())
+ require.ErrorContains(t, rdb.Do(ctx, "bf.insert", key,
"capacity", "qwe", "items", "items1").Err(), "ERR Bad capacity")
+ require.ErrorContains(t, rdb.Do(ctx, "bf.insert", key,
"capacity", "-1000", "items", "items1").Err(), "ERR Bad capacity")
+ require.ErrorContains(t, rdb.Do(ctx, "bf.insert", key,
"capacity", "0", "items", "items1").Err(), "ERR capacity should be larger than
0")
+
+ require.NoError(t, rdb.Do(ctx, "bf.insert", key, "capacity",
"200", "items", "items1").Err())
+ require.Equal(t, []interface{}{"Capacity", int64(200), "Size",
int64(256), "Number of filters", int64(1), "Number of items inserted",
int64(1), "Expansion rate", int64(2)}, rdb.Do(ctx, "bf.info", key).Val())
+ })
+
+ t.Run("Insert with nonscaling", func(t *testing.T) {
+ require.NoError(t, rdb.Del(ctx, key).Err())
+ require.NoError(t, rdb.Do(ctx, "bf.insert", key, "nonscaling",
"items", "items1").Err())
+
+ require.Equal(t, redis.Nil, rdb.Do(ctx, "bf.info", key,
"expansion").Err())
+ })
+
+ t.Run("Insert with expansion", func(t *testing.T) {
+ require.NoError(t, rdb.Del(ctx, key).Err())
+ require.ErrorContains(t, rdb.Do(ctx, "bf.insert", key,
"expansion", "items", "items1").Err(), "ERR Bad expansion")
+ require.ErrorContains(t, rdb.Do(ctx, "bf.insert", key,
"expansion", "0", "items", "items1").Err(), "ERR expansion should be greater or
equal to 1")
+ require.ErrorContains(t, rdb.Do(ctx, "bf.insert", key,
"expansion", "asd", "items", "items1").Err(), "ERR Bad expansion")
+ require.ErrorContains(t, rdb.Do(ctx, "bf.insert", key,
"expansion", "-1", "items", "items1").Err(), "ERR Bad expansion")
+ require.ErrorContains(t, rdb.Do(ctx, "bf.insert", key,
"expansion", "1.5", "items", "items1").Err(), "ERR Bad expansion")
+ require.ErrorContains(t, rdb.Do(ctx, "bf.insert", key,
"expansion", "123asd", "items", "items1").Err(), "ERR Bad expansion")
+
+ require.NoError(t, rdb.Do(ctx, "bf.insert", key, "expansion",
"3", "items", "items1").Err())
+ require.Equal(t, []interface{}{"Capacity", int64(100), "Size",
int64(128), "Number of filters", int64(1), "Number of items inserted",
int64(1), "Expansion rate", int64(3)}, rdb.Do(ctx, "bf.info", key).Val())
+ })
+
+ t.Run("Insert with nonscaling and expansion", func(t *testing.T) {
+ require.NoError(t, rdb.Del(ctx, key).Err())
+ require.ErrorContains(t, rdb.Do(ctx, "bf.insert", key,
"expansion", "1", "nonscaling", "items", "items1").Err(), "ERR nonscaling
filters cannot expand")
+ require.ErrorContains(t, rdb.Do(ctx, "bf.insert", key,
"nonscaling", "expansion", "1", "items", "items1").Err(), "ERR nonscaling
filters cannot expand")
+ })
+
+ t.Run("Insert items", func(t *testing.T) {
+ require.NoError(t, rdb.Del(ctx, key).Err())
+ require.ErrorContains(t, rdb.Do(ctx, "bf.insert", key,
"capacity", "100", "items").Err(), "ERR num of items should be greater than 0")
+
+ require.Equal(t, []interface{}{int64(0), int64(0), int64(0)},
rdb.Do(ctx, "bf.mexists", key, "xxx", "yyy", "zzz").Val())
+
+ require.Equal(t, []interface{}{int64(1), int64(1)}, rdb.Do(ctx,
"bf.insert", key, "items", "xxx", "zzz").Val())
+ require.Equal(t, int64(2), rdb.Do(ctx, "bf.card", key).Val())
+ require.Equal(t, []interface{}{int64(1), int64(0), int64(1)},
rdb.Do(ctx, "bf.mexists", key, "xxx", "yyy", "zzz").Val())
+
+ // add the existed value
+ require.Equal(t, []interface{}{int64(0)}, rdb.Do(ctx,
"bf.insert", key, "items", "zzz").Val())
+ require.Equal(t, []interface{}{int64(1), int64(0), int64(1)},
rdb.Do(ctx, "bf.mexists", key, "xxx", "yyy", "zzz").Val())
+
+ // add the same value
+ require.Equal(t, []interface{}{int64(1), int64(0)}, rdb.Do(ctx,
"bf.insert", key, "items", "yyy", "yyy").Val())
+ require.Equal(t, []interface{}{int64(1), int64(1), int64(1)},
rdb.Do(ctx, "bf.mexists", key, "xxx", "yyy", "zzz").Val())
+ })
+
+ t.Run("Insert would not change existed bloom filter", func(t
*testing.T) {
+ require.NoError(t, rdb.Del(ctx, key).Err())
+ require.NoError(t, rdb.Do(ctx, "bf.reserve", key, "0.02",
"1000", "expansion", "3").Err())
+ require.NoError(t, rdb.Do(ctx, "bf.insert", key, "error",
"0.01", "capacity", "2000", "expansion", "4", "items", "xxx", "zzz").Err())
+ require.Equal(t, []interface{}{"Capacity", int64(1000), "Size",
int64(2048), "Number of filters", int64(1), "Number of items inserted",
int64(2), "Expansion rate", int64(3)}, rdb.Do(ctx, "bf.info", key).Val())
+ })
+
t.Run("MExists Basic Test", func(t *testing.T) {
require.NoError(t, rdb.Del(ctx, key).Err())
require.Equal(t, []interface{}{int64(0), int64(0), int64(0)},
rdb.Do(ctx, "bf.mexists", key, "xxx", "yyy", "zzz").Val())