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

maplefu 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 0638c468 Add support of the command JSON.MSET (#2228)
0638c468 is described below

commit 0638c4687271f6d6ce82cb5ab1cba341f5aeef45
Author: zjregee <[email protected]>
AuthorDate: Mon Apr 8 20:07:06 2024 +0800

    Add support of the command JSON.MSET (#2228)
    
    Co-authored-by: hulk <[email protected]>
    Co-authored-by: Twice <[email protected]>
---
 src/commands/cmd_json.cc                 | 28 +++++++++++++++-
 src/types/redis_json.cc                  | 57 ++++++++++++++++++++++++++++++++
 src/types/redis_json.h                   |  2 ++
 tests/gocase/unit/type/json/json_test.go | 14 ++++++++
 4 files changed, 100 insertions(+), 1 deletion(-)

diff --git a/src/commands/cmd_json.cc b/src/commands/cmd_json.cc
index 54a28271..e29bad83 100644
--- a/src/commands/cmd_json.cc
+++ b/src/commands/cmd_json.cc
@@ -594,6 +594,31 @@ class CommandJsonMGet : public Commander {
   }
 };
 
+class CommandJsonMSet : public Commander {
+ public:
+  Status Execute(Server *svr, Connection *conn, std::string *output) override {
+    if ((args_.size() - 1) % 3 != 0) {
+      return {Status::RedisExecErr, errWrongNumOfArguments};
+    }
+
+    redis::Json json(svr->storage, conn->GetNamespace());
+
+    std::vector<std::string> user_keys;
+    std::vector<std::string> paths;
+    std::vector<std::string> values;
+    for (size_t i = 0; i < (args_.size() - 1) / 3; i++) {
+      user_keys.emplace_back(args_[i * 3 + 1]);
+      paths.emplace_back(args_[i * 3 + 2]);
+      values.emplace_back(args_[i * 3 + 3]);
+    }
+
+    if (auto s = json.MSet(user_keys, paths, values); !s.ok()) return 
{Status::RedisExecErr, s.ToString()};
+
+    *output = redis::SimpleString("OK");
+    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),
@@ -616,6 +641,7 @@ 
REDIS_REGISTER_COMMANDS(MakeCmdAttr<CommandJsonSet>("json.set", 4, "write", 1, 1
                         MakeCmdAttr<CommandJsonObjLen>("json.objlen", -2, 
"read-only", 1, 1, 1),
                         MakeCmdAttr<CommandJsonStrAppend>("json.strappend", 
-3, "write", 1, 1, 1),
                         MakeCmdAttr<CommandJsonStrLen>("json.strlen", -2, 
"read-only", 1, 1, 1),
-                        MakeCmdAttr<CommandJsonMGet>("json.mget", -3, 
"read-only", 1, 1, 1), );
+                        MakeCmdAttr<CommandJsonMGet>("json.mget", -3, 
"read-only", 1, -2, 1),
+                        MakeCmdAttr<CommandJsonMSet>("json.mset", -4, "write", 
1, -3, 3), );
 
 }  // namespace redis
diff --git a/src/types/redis_json.cc b/src/types/redis_json.cc
index dbf04563..ca879981 100644
--- a/src/types/redis_json.cc
+++ b/src/types/redis_json.cc
@@ -548,6 +548,63 @@ std::vector<rocksdb::Status> Json::MGet(const 
std::vector<std::string> &user_key
   return statuses;
 }
 
+rocksdb::Status Json::MSet(const std::vector<std::string> &user_keys, const 
std::vector<std::string> &paths,
+                           const std::vector<std::string> &values) {
+  std::vector<std::string> ns_keys;
+  ns_keys.reserve(user_keys.size());
+  for (const auto &user_key : user_keys) {
+    std::string ns_key = AppendNamespacePrefix(user_key);
+    ns_keys.emplace_back(std::move(ns_key));
+  }
+  MultiLockGuard guard(storage_->GetLockManager(), ns_keys);
+
+  auto batch = storage_->GetWriteBatchBase();
+  WriteBatchLogData log_data(kRedisJson);
+  batch->PutLogData(log_data.Encode());
+
+  for (size_t i = 0; i < user_keys.size(); i++) {
+    auto json_res = JsonValue::FromString(values[i], 
storage_->GetConfig()->json_max_nesting_depth);
+    if (!json_res) return rocksdb::Status::InvalidArgument(json_res.Msg());
+
+    JsonMetadata metadata;
+    JsonValue value;
+
+    if (auto s = read(ns_keys[i], &metadata, &value); s.IsNotFound()) {
+      if (paths[i] != "$") return rocksdb::Status::InvalidArgument("new 
objects must be created at the root");
+
+      value = *std::move(json_res);
+    } else {
+      if (!s.ok()) return s;
+
+      JsonValue new_val = *std::move(json_res);
+      auto set_res = value.Set(paths[i], std::move(new_val));
+      if (!set_res) return rocksdb::Status::InvalidArgument(set_res.Msg());
+    }
+
+    auto format = storage_->GetConfig()->json_storage_format;
+    metadata.format = format;
+
+    std::string val;
+    metadata.Encode(&val);
+
+    Status res;
+    if (format == JsonStorageFormat::JSON) {
+      res = value.Dump(&val, storage_->GetConfig()->json_max_nesting_depth);
+    } else if (format == JsonStorageFormat::CBOR) {
+      res = value.DumpCBOR(&val, 
storage_->GetConfig()->json_max_nesting_depth);
+    } else {
+      return rocksdb::Status::InvalidArgument("JSON storage format not 
supported");
+    }
+    if (!res) {
+      return rocksdb::Status::InvalidArgument("Failed to encode JSON into 
storage: " + res.Msg());
+    }
+
+    batch->Put(metadata_cf_handle_, ns_keys[i], val);
+  }
+
+  return storage_->Write(storage_->DefaultWriteOptions(), 
batch->GetWriteBatch());
+}
+
 std::vector<rocksdb::Status> Json::readMulti(const std::vector<Slice> 
&ns_keys, std::vector<JsonValue> &values) {
   rocksdb::ReadOptions read_options = storage_->DefaultMultiGetOptions();
   LatestSnapShot ss(storage_);
diff --git a/src/types/redis_json.h b/src/types/redis_json.h
index 8d0f15cb..f72b2c00 100644
--- a/src/types/redis_json.h
+++ b/src/types/redis_json.h
@@ -66,6 +66,8 @@ class Json : public Database {
 
   std::vector<rocksdb::Status> MGet(const std::vector<std::string> &user_keys, 
const std::string &path,
                                     std::vector<JsonValue> &results);
+  rocksdb::Status MSet(const std::vector<std::string> &user_keys, const 
std::vector<std::string> &paths,
+                       const std::vector<std::string> &values);
 
  private:
   rocksdb::Status write(Slice ns_key, JsonMetadata *metadata, const JsonValue 
&json_val);
diff --git a/tests/gocase/unit/type/json/json_test.go 
b/tests/gocase/unit/type/json/json_test.go
index a1489f7a..51785d29 100644
--- a/tests/gocase/unit/type/json/json_test.go
+++ b/tests/gocase/unit/type/json/json_test.go
@@ -612,6 +612,20 @@ func TestJson(t *testing.T) {
                require.EqualValues(t, "[]", vals[1])
 
        })
+
+       t.Run("JSON.MSET basics", func(t *testing.T) {
+               require.NoError(t, rdb.Do(ctx, "JSON.DEL", "a0").Err())
+               require.Error(t, rdb.Do(ctx, "JSON.MSET", "a0", "$.a", `{"a": 
1, "b": 2, "nested": {"a": 3}, "c": null}`, "a1", "$", `{"a": 4, "b": 5, 
"nested": {"a": 6}, "c": null}`).Err())
+               require.NoError(t, rdb.Do(ctx, "JSON.MSET", "a0", "$", `{"a": 
1, "b": 2, "nested": {"a": 3}, "c": null}`, "a1", "$", `{"a": 4, "b": 5, 
"nested": {"a": 6}, "c": null}`).Err())
+
+               EqualJSON(t, `{"a": 1, "b": 2, "nested": {"a": 3}, "c": null}`, 
rdb.Do(ctx, "JSON.GET", "a0").Val())
+               EqualJSON(t, `[{"a": 1, "b": 2, "nested": {"a": 3}, "c": 
null}]`, rdb.Do(ctx, "JSON.GET", "a0", "$").Val())
+               EqualJSON(t, `[1]`, rdb.Do(ctx, "JSON.GET", "a0", "$.a").Val())
+
+               EqualJSON(t, `{"a": 4, "b": 5, "nested": {"a": 6}, "c": null}`, 
rdb.Do(ctx, "JSON.GET", "a1").Val())
+               EqualJSON(t, `[{"a": 4, "b": 5, "nested": {"a": 6}, "c": 
null}]`, rdb.Do(ctx, "JSON.GET", "a1", "$").Val())
+               EqualJSON(t, `[4]`, rdb.Do(ctx, "JSON.GET", "a1", "$.a").Val())
+       })
 }
 
 func EqualJSON(t *testing.T, expected string, actual interface{}) {

Reply via email to