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())

Reply via email to