From 416ef97a1dc7fc8569cdc0358e4354c4e98ffe2c Mon Sep 17 00:00:00 2001 From: Qiaolin Yu Date: Mon, 27 Nov 2023 11:56:40 -0500 Subject: [PATCH] Support for the JSON.DEL command --- 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 edc3f5490ed..26a33e493f3 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("json.set", 4, "write", 1, 1, 1), MakeCmdAttr("json.get", -2, "read-only", 1, 1, 1), MakeCmdAttr("json.info", 2, "read-only", 1, 1, 1), @@ -491,6 +513,7 @@ REDIS_REGISTER_COMMANDS(MakeCmdAttr("json.set", 4, "write", 1, 1 MakeCmdAttr("json.merge", 4, "write", 1, 1, 1), MakeCmdAttr("json.objkeys", -2, "read-only", 1, 1, 1), MakeCmdAttr("json.arrpop", -2, "write", 1, 1, 1), - MakeCmdAttr("json.arrindex", -4, "read-only", 1, 1, 1), ); + MakeCmdAttr("json.arrindex", -4, "read-only", 1, 1, 1), + MakeCmdAttr("json.del", -2, "write", 1, 1, 1), ); } // namespace redis diff --git a/src/types/json.h b/src/types/json.h index 9df5f592748..15a01c3d912 100644 --- a/src/types/json.h +++ b/src/types/json.h @@ -21,6 +21,7 @@ #pragma once #include +#include #include #include #include @@ -475,6 +476,16 @@ struct JsonValue { return Status::OK(); } + StatusOr 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 c31fac0deb9..32bc6bb8e3b 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 29af781de3c..d42ad4be11f 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> &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 dd2d2871d3d..7083cb72944 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); +}