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);
+}