This is an automated email from the ASF dual-hosted git repository.

hulk 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 1083142b  support for the JSON.TOGGLE command (#1875)
1083142b is described below

commit 1083142b97a1e7a7c842f68ed633e9a0566383c6
Author: HashTagInclude <[email protected]>
AuthorDate: Thu Nov 9 12:16:04 2023 +0530

     support for the JSON.TOGGLE command (#1875)
    
    Co-authored-by: Twice <[email protected]>
    Co-authored-by: hulk <[email protected]>
---
 src/commands/cmd_json.cc                 | 31 ++++++++++++++++-
 src/types/json.h                         | 17 ++++++++++
 src/types/redis_json.cc                  | 18 ++++++++++
 src/types/redis_json.h                   |  2 ++
 tests/cppunit/types/json_test.cc         | 58 ++++++++++++++++++++++++++++++++
 tests/gocase/unit/type/json/json_test.go | 30 +++++++++++++++++
 6 files changed, 155 insertions(+), 1 deletion(-)

diff --git a/src/commands/cmd_json.cc b/src/commands/cmd_json.cc
index 86017fb5..08bcf7ab 100644
--- a/src/commands/cmd_json.cc
+++ b/src/commands/cmd_json.cc
@@ -219,6 +219,35 @@ class CommandJsonClear : public Commander {
   }
 };
 
+class CommandJsonToggle : public Commander {
+ public:
+  Status Execute(Server *svr, Connection *conn, std::string *output) override {
+    redis::Json json(svr->storage, conn->GetNamespace());
+
+    std::string path = (args_.size() > 2) ? args_[2] : "$";
+    std::vector<std::optional<bool>> results;
+    auto s = json.Toggle(args_[1], path, results);
+
+    if (s.IsNotFound()) {
+      *output = redis::NilString();
+      return Status::OK();
+    }
+
+    *output = redis::MultiLen(results.size());
+    for (auto it = results.rbegin(); it != results.rend(); ++it) {
+      if (it->has_value()) {
+        *output += redis::Integer(it->value());
+      } else {
+        *output += redis::NilString();
+      }
+    }
+
+    if (!s.ok()) return {Status::RedisExecErr, s.ToString()};
+
+    return Status::OK();
+  }
+};
+
 class CommandJsonArrLen : public Commander {
  public:
   Status Execute(Server *svr, Connection *conn, std::string *output) override {
@@ -297,8 +326,8 @@ 
REDIS_REGISTER_COMMANDS(MakeCmdAttr<CommandJsonSet>("json.set", 4, "write", 1, 1
                         MakeCmdAttr<CommandJsonType>("json.type", -2, 
"read-only", 1, 1, 1),
                         MakeCmdAttr<CommandJsonArrAppend>("json.arrappend", 
-4, "write", 1, 1, 1),
                         MakeCmdAttr<CommandJsonClear>("json.clear", -2, 
"write", 1, 1, 1),
+                        MakeCmdAttr<CommandJsonToggle>("json.toggle", -2, 
"write", 1, 1, 1),
                         MakeCmdAttr<CommandJsonArrLen>("json.arrlen", -2, 
"read-only", 1, 1, 1),
                         MakeCmdAttr<CommandJsonObjkeys>("json.objkeys", -2, 
"read-only", 1, 1, 1),
                         MakeCmdAttr<CommandJsonArrPop>("json.arrpop", -2, 
"write", 1, 1, 1), );
-
 }  // namespace redis
diff --git a/src/types/json.h b/src/types/json.h
index 442bef76..1b78ec06 100644
--- a/src/types/json.h
+++ b/src/types/json.h
@@ -212,6 +212,23 @@ struct JsonValue {
     return types;
   }
 
+  StatusOr<std::vector<std::optional<bool>>> Toggle(std::string_view path) {
+    std::vector<std::optional<bool>> result;
+    try {
+      jsoncons::jsonpath::json_replace(value, path, [&result](const 
std::string & /*path*/, jsoncons::json &val) {
+        if (val.is_bool()) {
+          val = !val.as_bool();
+          result.emplace_back(val.as_bool());
+        } else {
+          result.emplace_back(std::nullopt);
+        }
+      });
+    } catch (const jsoncons::jsonpath::jsonpath_error &e) {
+      return {Status::NotOK, e.what()};
+    }
+    return result;
+  }
+
   StatusOr<size_t> Clear(std::string_view path) {
     size_t count = 0;
     try {
diff --git a/src/types/redis_json.cc b/src/types/redis_json.cc
index 44da953d..94536605 100644
--- a/src/types/redis_json.cc
+++ b/src/types/redis_json.cc
@@ -231,6 +231,24 @@ rocksdb::Status Json::ArrLen(const std::string &user_key, 
const std::string &pat
   return rocksdb::Status::OK();
 }
 
+rocksdb::Status Json::Toggle(const std::string &user_key, const std::string 
&path,
+                             std::vector<std::optional<bool>> &result) {
+  auto ns_key = AppendNamespacePrefix(user_key);
+
+  LockGuard guard(storage_->GetLockManager(), ns_key);
+
+  JsonMetadata metadata;
+  JsonValue origin;
+  auto s = read(ns_key, &metadata, &origin);
+  if (!s.ok()) return s;
+
+  auto toggle_res = origin.Toggle(path);
+  if (!toggle_res) return rocksdb::Status::InvalidArgument(toggle_res.Msg());
+  result = *toggle_res;
+
+  return write(ns_key, &metadata, origin);
+}
+
 rocksdb::Status Json::ArrPop(const std::string &user_key, const std::string 
&path, int64_t index,
                              std::vector<std::optional<JsonValue>> *results) {
   auto ns_key = AppendNamespacePrefix(user_key);
diff --git a/src/types/redis_json.h b/src/types/redis_json.h
index 708199af..e1f5cde9 100644
--- a/src/types/redis_json.h
+++ b/src/types/redis_json.h
@@ -42,6 +42,8 @@ class Json : public Database {
   rocksdb::Status Clear(const std::string &user_key, const std::string &path, 
size_t *result);
   rocksdb::Status ArrLen(const std::string &user_key, const std::string &path,
                          std::vector<std::optional<uint64_t>> &arr_lens);
+  rocksdb::Status Toggle(const std::string &user_key, const std::string &path,
+                         std::vector<std::optional<bool>> &result);
   rocksdb::Status ObjKeys(const std::string &user_key, const std::string &path,
                           std::vector<std::optional<std::vector<std::string>>> 
&keys);
   rocksdb::Status ArrPop(const std::string &user_key, const std::string &path, 
int64_t index,
diff --git a/tests/cppunit/types/json_test.cc b/tests/cppunit/types/json_test.cc
index 9e023c3e..c1c10e8d 100644
--- a/tests/cppunit/types/json_test.cc
+++ b/tests/cppunit/types/json_test.cc
@@ -273,6 +273,64 @@ TEST_F(RedisJsonTest, ArrLen) {
   ASSERT_TRUE(res.empty());
 }
 
+TEST_F(RedisJsonTest, Toggle) {
+  std::vector<std::optional<bool>> res;
+  ASSERT_TRUE(json_->Set(key_, "$", "true").ok());
+  ASSERT_TRUE(json_->Toggle(key_, "$", res).ok());
+  ASSERT_TRUE(json_->Get(key_, {}, &json_val_).ok());
+  ASSERT_EQ(json_val_.Dump().GetValue(), "false");
+  ASSERT_EQ(res.size(), 1);
+  ASSERT_THAT(res, testing::ElementsAre(false));
+  res.clear();
+
+  ASSERT_TRUE(json_->Set(key_, "$", R"({"bool":true})").ok());
+  ASSERT_TRUE(json_->Toggle(key_, "$.bool", res).ok());
+  ASSERT_TRUE(json_->Get(key_, {}, &json_val_).ok());
+  ASSERT_EQ(json_val_.Dump().GetValue(), R"({"bool":false})");
+  ASSERT_EQ(res.size(), 1);
+  ASSERT_THAT(res, testing::ElementsAre(false));
+  res.clear();
+
+  ASSERT_TRUE(json_->Set(key_, "$", 
R"({"bool":true,"bools":{"bool":true}})").ok());
+  ASSERT_TRUE(json_->Toggle(key_, "$.bool", res).ok());
+  ASSERT_TRUE(json_->Get(key_, {}, &json_val_).ok());
+  ASSERT_EQ(json_val_.Dump().GetValue(), 
R"({"bool":false,"bools":{"bool":true}})");
+  ASSERT_EQ(res.size(), 1);
+  ASSERT_THAT(res, testing::ElementsAre(false));
+  res.clear();
+
+  ASSERT_TRUE(json_->Set(key_, "$", 
R"({"bool":true,"bools":{"bool":true}})").ok());
+  ASSERT_TRUE(json_->Toggle(key_, "$..bool", res).ok());
+  ASSERT_TRUE(json_->Get(key_, {}, &json_val_).ok());
+  ASSERT_EQ(json_val_.Dump().GetValue(), 
R"({"bool":false,"bools":{"bool":false}})");
+  ASSERT_EQ(res.size(), 2);
+  ASSERT_THAT(res, testing::ElementsAre(false, false));
+  res.clear();
+
+  ASSERT_TRUE(json_->Set(key_, "$", 
R"({"bool":false,"bools":{"bool":true}})").ok());
+  ASSERT_TRUE(json_->Toggle(key_, "$..bool", res).ok());
+  ASSERT_TRUE(json_->Get(key_, {}, &json_val_).ok());
+  ASSERT_EQ(json_val_.Dump().GetValue(), 
R"({"bool":true,"bools":{"bool":false}})");
+  ASSERT_EQ(res.size(), 2);
+  ASSERT_THAT(res, testing::ElementsAre(false, true));
+  res.clear();
+
+  ASSERT_TRUE(json_->Set(key_, "$", 
R"({"bool":false,"bools":{"bool":true},"incorrectbool":{"bool":88}})").ok());
+  ASSERT_TRUE(json_->Toggle(key_, "$..bool", res).ok());
+  ASSERT_TRUE(json_->Get(key_, {}, &json_val_).ok());
+  ASSERT_EQ(json_val_.Dump().GetValue(), 
R"({"bool":true,"bools":{"bool":false},"incorrectbool":{"bool":88}})");
+  ASSERT_EQ(res.size(), 3);
+  ASSERT_THAT(res, testing::ElementsAre(std::nullopt, false, true));
+  res.clear();
+
+  ASSERT_TRUE(json_->Set(key_, "$", "[true,true,99]").ok());
+  ASSERT_TRUE(json_->Toggle(key_, "$..*", res).ok());
+  ASSERT_TRUE(json_->Get(key_, {}, &json_val_).ok());
+  ASSERT_EQ(json_val_.Dump().GetValue(), "[false,false,99]");
+  ASSERT_EQ(res.size(), 3);
+  ASSERT_THAT(res, testing::ElementsAre(std::nullopt, false, false));
+}
+
 TEST_F(RedisJsonTest, ArrPop) {
   std::vector<std::optional<JsonValue>> res;
 
diff --git a/tests/gocase/unit/type/json/json_test.go 
b/tests/gocase/unit/type/json/json_test.go
index b5d741cd..0d5f3fd9 100644
--- a/tests/gocase/unit/type/json/json_test.go
+++ b/tests/gocase/unit/type/json/json_test.go
@@ -245,4 +245,34 @@ func TestJson(t *testing.T) {
                require.ErrorContains(t, rdb.Do(ctx, "JSON.ARRPOP", "a", "$", 
"0", "1").Err(), "wrong number of arguments")
        })
 
+       t.Run("JSON.TOGGLE basics", func(t *testing.T) {
+               require.NoError(t, rdb.Do(ctx, "JSON.SET", "a", "$", 
`true`).Err())
+               require.EqualValues(t, []interface{}{int64(0)}, rdb.Do(ctx, 
"JSON.TOGGLE", "a", "$").Val())
+               require.Equal(t, rdb.Do(ctx, "JSON.GET", "a").Val(), `false`)
+
+               require.NoError(t, rdb.Do(ctx, "JSON.SET", "a", "$", 
`{"bool":true}`).Err())
+               require.EqualValues(t, []interface{}{int64(0)}, rdb.Do(ctx, 
"JSON.TOGGLE", "a", "$.bool").Val())
+               require.Equal(t, rdb.Do(ctx, "JSON.GET", "a").Val(), 
`{"bool":false}`)
+
+               require.NoError(t, rdb.Do(ctx, "JSON.SET", "a", "$", 
`{"bool":true,"bools":{"bool":true}}`).Err())
+               require.EqualValues(t, []interface{}{int64(0)}, rdb.Do(ctx, 
"JSON.TOGGLE", "a", "$.bool").Val())
+               require.Equal(t, rdb.Do(ctx, "JSON.GET", "a").Val(), 
`{"bool":false,"bools":{"bool":true}}`)
+
+               require.NoError(t, rdb.Do(ctx, "JSON.SET", "a", "$", 
`{"bool":true,"bools":{"bool":true}}`).Err())
+               require.EqualValues(t, []interface{}{int64(0), int64(0)}, 
rdb.Do(ctx, "JSON.TOGGLE", "a", "$..bool").Val())
+               require.Equal(t, rdb.Do(ctx, "JSON.GET", "a").Val(), 
`{"bool":false,"bools":{"bool":false}}`)
+
+               require.NoError(t, rdb.Do(ctx, "JSON.SET", "a", "$", 
`{"bool":false,"bools":{"bool":true}}`).Err())
+               require.EqualValues(t, []interface{}{int64(1), int64(0)}, 
rdb.Do(ctx, "JSON.TOGGLE", "a", "$..bool").Val())
+               require.Equal(t, rdb.Do(ctx, "JSON.GET", "a").Val(), 
`{"bool":true,"bools":{"bool":false}}`)
+
+               require.NoError(t, rdb.Do(ctx, "JSON.SET", "a", "$", 
`{"incorrectbool":99,"bools":{"bool":true},"bool":{"bool":false}}`).Err())
+               require.EqualValues(t, []interface{}{nil, int64(1), int64(0)}, 
rdb.Do(ctx, "JSON.TOGGLE", "a", "$..bool").Val())
+               require.Equal(t, rdb.Do(ctx, "JSON.GET", "a").Val(), 
`{"bool":{"bool":true},"bools":{"bool":false},"incorrectbool":99}`)
+
+               require.NoError(t, rdb.Do(ctx, "JSON.SET", "a", "$", 
`[99,true,99]`).Err())
+               require.EqualValues(t, []interface{}{nil, int64(0), nil}, 
rdb.Do(ctx, "JSON.TOGGLE", "a", "$..*").Val())
+               require.Equal(t, rdb.Do(ctx, "JSON.GET", "a").Val(), 
`[99,false,99]`)
+       })
+
 }

Reply via email to