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 155f3d90 Support for the JSON.DEL command (#1859)
155f3d90 is described below

commit 155f3d900839135eee9f0c1dddab98b84ab211a1
Author: Qiaolin Yu <[email protected]>
AuthorDate: Thu Nov 30 10:06:48 2023 -0500

    Support for the JSON.DEL command (#1859)
---
 src/commands/cmd_json.cc         | 25 ++++++++++++++++++-
 src/types/json.h                 | 11 ++++++++
 src/types/redis_json.cc          | 35 ++++++++++++++++++++++++++
 src/types/redis_json.h           |  2 ++
 tests/cppunit/types/json_test.cc | 54 ++++++++++++++++++++++++++++++++++++++--
 5 files changed, 124 insertions(+), 3 deletions(-)

diff --git a/src/commands/cmd_json.cc b/src/commands/cmd_json.cc
index edc3f549..26a33e49 100644
--- a/src/commands/cmd_json.cc
+++ b/src/commands/cmd_json.cc
@@ -478,6 +478,28 @@ class CommanderJsonArrIndex : public Commander {
   ssize_t end_;
 };
 
+class CommandJsonDel : public Commander {
+ public:
+  Status Execute(Server *svr, Connection *conn, std::string *output) override {
+    redis::Json json(svr->storage, conn->GetNamespace());
+    size_t result = 0;
+    std::string path = "$";
+    if (args_.size() == 3) {
+      path = args_[2];
+    } else if (args_.size() > 3) {
+      return {Status::RedisExecErr, "The number of arguments is more than 
expected"};
+    }
+    auto s = json.Del(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<CommandJsonInfo>("json.info", 2, 
"read-only", 1, 1, 1),
@@ -491,6 +513,7 @@ 
REDIS_REGISTER_COMMANDS(MakeCmdAttr<CommandJsonSet>("json.set", 4, "write", 1, 1
                         MakeCmdAttr<CommandJsonMerge>("json.merge", 4, 
"write", 1, 1, 1),
                         MakeCmdAttr<CommandJsonObjkeys>("json.objkeys", -2, 
"read-only", 1, 1, 1),
                         MakeCmdAttr<CommandJsonArrPop>("json.arrpop", -2, 
"write", 1, 1, 1),
-                        MakeCmdAttr<CommanderJsonArrIndex>("json.arrindex", 
-4, "read-only", 1, 1, 1), );
+                        MakeCmdAttr<CommanderJsonArrIndex>("json.arrindex", 
-4, "read-only", 1, 1, 1),
+                        MakeCmdAttr<CommandJsonDel>("json.del", -2, "write", 
1, 1, 1), );
 
 }  // namespace redis
diff --git a/src/types/json.h b/src/types/json.h
index 9df5f592..15a01c3d 100644
--- a/src/types/json.h
+++ b/src/types/json.h
@@ -21,6 +21,7 @@
 #pragma once
 
 #include <algorithm>
+#include <cstddef>
 #include <jsoncons/json.hpp>
 #include <jsoncons/json_error.hpp>
 #include <jsoncons/json_options.hpp>
@@ -475,6 +476,16 @@ struct JsonValue {
     return Status::OK();
   }
 
+  StatusOr<size_t> Del(const std::string &path) {
+    size_t count = 0;
+    try {
+      count = jsoncons::jsonpath::remove(value, path);
+    } catch (const jsoncons::jsonpath::jsonpath_error &e) {
+      return {Status::NotOK, e.what()};
+    }
+    return count;
+  }
+
   JsonValue(const JsonValue &) = default;
   JsonValue(JsonValue &&) = default;
 
diff --git a/src/types/redis_json.cc b/src/types/redis_json.cc
index c31fac0d..32bc6bb8 100644
--- a/src/types/redis_json.cc
+++ b/src/types/redis_json.cc
@@ -84,6 +84,16 @@ rocksdb::Status Json::create(const std::string &ns_key, 
JsonMetadata &metadata,
   return write(ns_key, &metadata, json_val);
 }
 
+rocksdb::Status Json::del(const Slice &ns_key) {
+  auto batch = storage_->GetWriteBatchBase();
+  WriteBatchLogData log_data(kRedisJson);
+  batch->PutLogData(log_data.Encode());
+
+  batch->Delete(metadata_cf_handle_, ns_key);
+
+  return storage_->Write(storage_->DefaultWriteOptions(), 
batch->GetWriteBatch());
+}
+
 rocksdb::Status Json::Info(const std::string &user_key, JsonStorageFormat 
*storage_format) {
   auto ns_key = AppendNamespacePrefix(user_key);
 
@@ -390,4 +400,29 @@ rocksdb::Status Json::ArrTrim(const std::string &user_key, 
const std::string &pa
   return write(ns_key, &metadata, json_val);
 }
 
+rocksdb::Status Json::Del(const std::string &user_key, const std::string 
&path, size_t *result) {
+  auto ns_key = AppendNamespacePrefix(user_key);
+
+  LockGuard guard(storage_->GetLockManager(), ns_key);
+  if (path == "$") {
+    *result = 1;
+    return del(ns_key);
+  }
+  JsonValue json_val;
+  JsonMetadata metadata;
+  auto s = read(ns_key, &metadata, &json_val);
+
+  if (!s.ok()) return s;
+
+  auto res = json_val.Del(path);
+  if (!res) return rocksdb::Status::InvalidArgument(res.Msg());
+
+  *result = *res;
+  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 29af781d..d42ad4be 100644
--- a/src/types/redis_json.h
+++ b/src/types/redis_json.h
@@ -56,11 +56,13 @@ class Json : public Database {
 
   rocksdb::Status ArrTrim(const std::string &user_key, const std::string 
&path, int64_t start, int64_t stop,
                           std::vector<std::optional<uint64_t>> &results);
+  rocksdb::Status Del(const std::string &user_key, const std::string &path, 
size_t *result);
 
  private:
   rocksdb::Status write(Slice ns_key, JsonMetadata *metadata, const JsonValue 
&json_val);
   rocksdb::Status read(const Slice &ns_key, JsonMetadata *metadata, JsonValue 
*value);
   rocksdb::Status create(const std::string &ns_key, JsonMetadata &metadata, 
const std::string &value);
+  rocksdb::Status del(const Slice &ns_key);
 };
 
 }  // namespace redis
diff --git a/tests/cppunit/types/json_test.cc b/tests/cppunit/types/json_test.cc
index dd2d2871..7083cb72 100644
--- a/tests/cppunit/types/json_test.cc
+++ b/tests/cppunit/types/json_test.cc
@@ -89,12 +89,13 @@ TEST_F(RedisJsonTest, Set) {
   ASSERT_TRUE(json_->Get(key_, {}, &json_val_).ok());
   ASSERT_EQ(json_val_.Dump().GetValue(), R"([{},[{},4]])");
 
-  ASSERT_TRUE(json_->Del(key_).ok());
+  size_t result = 0;
+  ASSERT_TRUE(json_->Del(key_, "$", &result).ok());
   ASSERT_TRUE(json_->Set(key_, "$", "[{ }, [ ]]").ok());
   ASSERT_TRUE(json_->Get(key_, {}, &json_val_).ok());
   ASSERT_EQ(json_val_.Dump().GetValue(), "[{},[]]");
   ASSERT_THAT(json_->Set(key_, "$[1]", "invalid").ToString(), 
MatchesRegex(".*syntax_error.*"));
-  ASSERT_TRUE(json_->Del(key_).ok());
+  ASSERT_TRUE(json_->Del(key_, "$", &result).ok());
 }
 
 TEST_F(RedisJsonTest, Get) {
@@ -485,3 +486,52 @@ TEST_F(RedisJsonTest, ArrIndex) {
   ASSERT_TRUE(json_->ArrIndex(key_, "$.arr", "3", 0, 2, &res).ok() && 
res.size() == 1);
   ASSERT_EQ(res[0], -1);
 }
+
+TEST_F(RedisJsonTest, Del) {
+  size_t 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_->Del(key_, "$", &result).ok());
+  ASSERT_TRUE(json_->Get(key_, {}, &json_val_).IsNotFound());
+  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_->Del(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,"str":"foo"})");
+  ASSERT_EQ(result, 1);
+
+  ASSERT_TRUE(json_->Del(key_, "$.arr", &result).ok());
+  ASSERT_TRUE(json_->Get(key_, {}, &json_val_).ok());
+  ASSERT_EQ(json_val_.Dump().GetValue(), 
R"({"bool":true,"float":3.14,"int":42,"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_->Del(key_, "$.*", &result).ok());
+  ASSERT_TRUE(json_->Get(key_, {}, &json_val_).ok());
+  ASSERT_EQ(json_val_.Dump().GetValue(), R"({})");
+  ASSERT_EQ(result, 6);
+
+  ASSERT_TRUE(json_->Del(key_, "$.some", &result).ok());
+  ASSERT_EQ(result, 0);
+
+  ASSERT_TRUE(json_->Set(key_, "$", R"({"a": 1, "nested": {"a": 2, "b": 
3}})").ok());
+  ASSERT_TRUE(json_->Del(key_, "$..a", &result).ok());
+  ASSERT_TRUE(json_->Get(key_, {}, &json_val_).ok());
+  ASSERT_EQ(json_val_.Dump().GetValue(), R"({"nested":{"b":3}})");
+  ASSERT_EQ(result, 2);
+}

Reply via email to