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 35e041f6 Support for the JSON.CLEAR command (#1850)
35e041f6 is described below

commit 35e041f628847d67a75e03573ac29265ba046ede
Author: 2rueSid <[email protected]>
AuthorDate: Tue Oct 24 07:14:18 2023 -0400

    Support for the JSON.CLEAR command (#1850)
---
 src/commands/cmd_json.cc                 | 26 ++++++++++++++++++-
 src/types/json.h                         | 30 ++++++++++++++++++++++
 src/types/redis_json.cc                  | 20 +++++++++++++++
 src/types/redis_json.h                   |  1 +
 tests/cppunit/types/json_test.cc         | 44 ++++++++++++++++++++++++++++++++
 tests/gocase/unit/type/json/json_test.go | 22 ++++++++++++++++
 6 files changed, 142 insertions(+), 1 deletion(-)

diff --git a/src/commands/cmd_json.cc b/src/commands/cmd_json.cc
index 6b5445c7..d17b3b55 100644
--- a/src/commands/cmd_json.cc
+++ b/src/commands/cmd_json.cc
@@ -148,9 +148,33 @@ class CommandJsonType : public Commander {
   }
 };
 
+class CommandJsonClear : public Commander {
+ public:
+  Status Execute(Server *svr, Connection *conn, std::string *output) override {
+    redis::Json json(svr->storage, conn->GetNamespace());
+
+    int result = 0;
+
+    // If path not specified set it to $
+    std::string path = (args_.size() > 2) ? args_[2] : "$";
+    auto s = json.Clear(args_[1], path, &result);
+
+    if (s.IsNotFound()) {
+      *output = redis::NilString();
+      return Status::OK();
+    }
+
+    if (!s.ok()) return {Status::RedisExecErr, s.ToString()};
+
+    *output = redis::Integer(result);
+    return Status::OK();
+  }
+};
+
 REDIS_REGISTER_COMMANDS(MakeCmdAttr<CommandJsonSet>("json.set", 4, "write", 1, 
1, 1),
                         MakeCmdAttr<CommandJsonGet>("json.get", -2, 
"read-only", 1, 1, 1),
                         MakeCmdAttr<CommandJsonType>("json.type", -2, 
"read-only", 1, 1, 1),
-                        MakeCmdAttr<CommandJsonArrAppend>("json.arrappend", 
-4, "write", 1, 1, 1), );
+                        MakeCmdAttr<CommandJsonArrAppend>("json.arrappend", 
-4, "write", 1, 1, 1),
+                        MakeCmdAttr<CommandJsonClear>("json.clear", -2, 
"write", 1, 1, 1), );
 
 }  // namespace redis
diff --git a/src/types/json.h b/src/types/json.h
index a3dd4c95..a099c470 100644
--- a/src/types/json.h
+++ b/src/types/json.h
@@ -172,6 +172,36 @@ struct JsonValue {
     return Status::OK();
   }
 
+  Status Clear(std::string_view path, int *result) {
+    try {
+      int cleared_count = 0;
+      jsoncons::jsonpath::json_replace(value, path,
+                                       [&cleared_count](const std::string &
+                                                        /*path*/,
+                                                        jsoncons::json &val) {
+                                         bool is_array = val.is_array() && 
!val.empty();
+                                         bool is_object = val.is_object() && 
!val.empty();
+                                         bool is_number = val.is_number() && 
val.as<double>() != 0;
+
+                                         if (is_array)
+                                           val = jsoncons::json::array();
+                                         else if (is_object)
+                                           val = jsoncons::json::object();
+                                         else if (is_number)
+                                           val = 0;
+                                         else
+                                           return;
+
+                                         cleared_count++;
+                                       });
+
+      *result = cleared_count;
+    } catch (const jsoncons::jsonpath::jsonpath_error &e) {
+      return {Status::NotOK, e.what()};
+    }
+    return Status::OK();
+  }
+
   JsonValue(const JsonValue &) = default;
   JsonValue(JsonValue &&) = default;
 
diff --git a/src/types/redis_json.cc b/src/types/redis_json.cc
index 7381fef6..f7b4e6d2 100644
--- a/src/types/redis_json.cc
+++ b/src/types/redis_json.cc
@@ -163,4 +163,24 @@ rocksdb::Status Json::Type(const std::string &user_key, 
const std::string &path,
   return rocksdb::Status::OK();
 }
 
+rocksdb::Status Json::Clear(const std::string &user_key, const std::string 
&path, int *result) {
+  auto ns_key = AppendNamespacePrefix(user_key);
+
+  LockGuard guard(storage_->GetLockManager(), ns_key);
+
+  JsonValue json_val;
+  JsonMetadata metadata;
+  auto s = read(ns_key, &metadata, &json_val);
+
+  if (!s.ok()) return s;
+
+  auto res = json_val.Clear(path, result);
+  if (!res.OK()) return s;
+
+  if (*result == 0) {
+    return rocksdb::Status::OK();
+  }
+
+  return write(ns_key, &metadata, json_val);
+}
 }  // namespace redis
diff --git a/src/types/redis_json.h b/src/types/redis_json.h
index d9492706..c8b5b9b3 100644
--- a/src/types/redis_json.h
+++ b/src/types/redis_json.h
@@ -38,6 +38,7 @@ class Json : public Database {
   rocksdb::Status Type(const std::string &user_key, const std::string &path, 
std::vector<std::string> *results);
   rocksdb::Status ArrAppend(const std::string &user_key, const std::string 
&path,
                             const std::vector<std::string> &values, 
std::vector<size_t> *result_count);
+  rocksdb::Status Clear(const std::string &user_key, const std::string &path, 
int *result);
 
  private:
   rocksdb::Status write(Slice ns_key, JsonMetadata *metadata, const JsonValue 
&json_val);
diff --git a/tests/cppunit/types/json_test.cc b/tests/cppunit/types/json_test.cc
index e3be9774..79ff4fd5 100644
--- a/tests/cppunit/types/json_test.cc
+++ b/tests/cppunit/types/json_test.cc
@@ -181,3 +181,47 @@ TEST_F(RedisJsonTest, ArrAppend) {
             
R"({"x":[1,2,{"x":[1,2,{"y":[1,2,3],"z":3}],"y":[{"y":1},1,2,3]},1],"y":[1,2,3,1,2,3]})");
   res.clear();
 }
+
+TEST_F(RedisJsonTest, Clear) {
+  int result = 0;
+
+  ASSERT_TRUE(
+      json_
+          ->Set(key_, "$",
+                R"({"obj":{"a":1, "b":2}, "arr":[1,2,3], "str": "foo", "bool": 
true, "int": 42, "float": 3.14})")
+          .ok());
+
+  ASSERT_TRUE(json_->Clear(key_, "$", &result).ok());
+  ASSERT_TRUE(json_->Get(key_, {}, &json_val_).ok());
+  ASSERT_EQ(json_val_.Dump().GetValue(), "{}");
+  ASSERT_EQ(result, 1);
+
+  ASSERT_TRUE(
+      json_
+          ->Set(key_, "$",
+                R"({"obj":{"a":1, "b":2}, "arr":[1,2,3], "str": "foo", "bool": 
true, "int": 42, "float": 3.14})")
+          .ok());
+
+  ASSERT_TRUE(json_->Clear(key_, "$.obj", &result).ok());
+  ASSERT_TRUE(json_->Get(key_, {}, &json_val_).ok());
+  ASSERT_EQ(json_val_.Dump().GetValue(), 
R"({"arr":[1,2,3],"bool":true,"float":3.14,"int":42,"obj":{},"str":"foo"})");
+  ASSERT_EQ(result, 1);
+
+  ASSERT_TRUE(json_->Clear(key_, "$.arr", &result).ok());
+  ASSERT_TRUE(json_->Get(key_, {}, &json_val_).ok());
+  ASSERT_EQ(json_val_.Dump().GetValue(), 
R"({"arr":[],"bool":true,"float":3.14,"int":42,"obj":{},"str":"foo"})");
+  ASSERT_EQ(result, 1);
+
+  ASSERT_TRUE(
+      json_
+          ->Set(key_, "$",
+                R"({"obj":{"a":1, "b":2}, "arr":[1,2,3], "str": "foo", "bool": 
true, "int": 42, "float": 3.14})")
+          .ok());
+  ASSERT_TRUE(json_->Clear(key_, "$.*", &result).ok());
+  ASSERT_TRUE(json_->Get(key_, {}, &json_val_).ok());
+  ASSERT_EQ(json_val_.Dump().GetValue(), 
R"({"arr":[],"bool":true,"float":0,"int":0,"obj":{},"str":"foo"})");
+  ASSERT_EQ(result, 4);
+
+  ASSERT_TRUE(json_->Clear(key_, "$.some", &result).ok());
+  ASSERT_EQ(result, 0);
+}
diff --git a/tests/gocase/unit/type/json/json_test.go 
b/tests/gocase/unit/type/json/json_test.go
index 5a60ee02..2a0cd01e 100644
--- a/tests/gocase/unit/type/json/json_test.go
+++ b/tests/gocase/unit/type/json/json_test.go
@@ -130,4 +130,26 @@ func TestJson(t *testing.T) {
                _, err = rdb.Do(ctx, "JSON.TYPE", "not_exists", 
"$").StringSlice()
                require.EqualError(t, err, redis.Nil.Error())
        })
+
+       t.Run("Clear JSON values", func(t *testing.T) {
+               require.NoError(t, rdb.Do(ctx, "JSON.SET", "bb", "$", 
`{"obj":{"a":1, "b":2}, "arr":[1,2,3], "str": "foo", "bool": true, "int": 42, 
"float": 3.14}`).Err())
+
+               require.NoError(t, rdb.Do(ctx, "JSON.CLEAR", "bb", "$").Err())
+               require.Equal(t, `{}`, rdb.Do(ctx, "JSON.GET", "bb").Val())
+
+               require.NoError(t, rdb.Do(ctx, "JSON.SET", "bb", "$", 
`{"obj":{"a":1, "b":2}, "arr":[1,2,3], "str": "foo", "bool": true, "int": 42, 
"float": 3.14}`).Err())
+               require.NoError(t, rdb.Do(ctx, "JSON.CLEAR", "bb", 
"$.obj").Err())
+               require.Equal(t, 
`{"arr":[1,2,3],"bool":true,"float":3.14,"int":42,"obj":{},"str":"foo"}`, 
rdb.Do(ctx, "JSON.GET", "bb").Val())
+
+               require.NoError(t, rdb.Do(ctx, "JSON.CLEAR", "bb", 
"$.arr").Err())
+               require.Equal(t, 
`{"arr":[],"bool":true,"float":3.14,"int":42,"obj":{},"str":"foo"}`, 
rdb.Do(ctx, "JSON.GET", "bb").Val())
+
+               require.NoError(t, rdb.Do(ctx, "JSON.SET", "bb", "$", 
`{"obj":{"a":1, "b":2}, "arr":[1,2,3], "str": "foo", "bool": true, "int": 42, 
"float": 3.14}`).Err())
+               require.NoError(t, rdb.Do(ctx, "JSON.CLEAR", "bb", "$.*").Err())
+               require.Equal(t, 
`{"arr":[],"bool":true,"float":0,"int":0,"obj":{},"str":"foo"}`, rdb.Do(ctx, 
"JSON.GET", "bb").Val())
+
+               _, err := rdb.Do(ctx, "JSON.CLEAR", "bb", "$.some").Result()
+               require.NoError(t, err)
+       })
+
 }

Reply via email to