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 99703d65 Add the support of JSON.MGET command (#1930)
99703d65 is described below

commit 99703d65d2e46c54228a12e5bfa6dc468fac8890
Author: skyitachi <[email protected]>
AuthorDate: Thu Dec 14 20:47:27 2023 +0800

    Add the support of JSON.MGET command (#1930)
---
 src/commands/cmd_json.cc                 | 31 +++++++++++-
 src/types/redis_json.cc                  | 83 ++++++++++++++++++++++++++++++++
 src/types/redis_json.h                   |  6 +++
 tests/gocase/unit/type/json/json_test.go | 32 ++++++++++++
 4 files changed, 151 insertions(+), 1 deletion(-)

diff --git a/src/commands/cmd_json.cc b/src/commands/cmd_json.cc
index 41763bdb..737581f8 100644
--- a/src/commands/cmd_json.cc
+++ b/src/commands/cmd_json.cc
@@ -566,6 +566,34 @@ class CommandJsonStrLen : public Commander {
   }
 };
 
+class CommandJsonMGet : public Commander {
+ public:
+  Status Execute(Server *svr, Connection *conn, std::string *output) override {
+    redis::Json json(svr->storage, conn->GetNamespace());
+
+    std::string path = args_[args_.size() - 1];
+    std::vector<std::string> user_keys;
+    for (size_t i = 1; i + 1 < args_.size(); i++) {
+      user_keys.emplace_back(args_[i]);
+    }
+
+    std::vector<JsonValue> json_values;
+    auto statuses = json.MGet(user_keys, path, json_values);
+
+    std::vector<std::string> values;
+    values.resize(user_keys.size());
+
+    for (size_t i = 0; i < statuses.size(); i++) {
+      if (statuses[i].ok()) {
+        values[i] = json_values[i].value.to_string();
+      }
+    }
+
+    *output = MultiBulkString(values, statuses);
+    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),
@@ -587,6 +615,7 @@ 
REDIS_REGISTER_COMMANDS(MakeCmdAttr<CommandJsonSet>("json.set", 4, "write", 1, 1
                         MakeCmdAttr<CommandJsonNumMultBy>("json.nummultby", 4, 
"write", 1, 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<CommandJsonStrLen>("json.strlen", -2, 
"read-only", 1, 1, 1),
+                        MakeCmdAttr<CommandJsonMGet>("json.mget", -3, 
"read-only", 1, 1, 1), );
 
 }  // namespace redis
diff --git a/src/types/redis_json.cc b/src/types/redis_json.cc
index 8c0f28bf..b1755e3a 100644
--- a/src/types/redis_json.cc
+++ b/src/types/redis_json.cc
@@ -512,4 +512,87 @@ rocksdb::Status Json::ObjLen(const std::string &user_key, 
const std::string &pat
   return rocksdb::Status::OK();
 }
 
+std::vector<rocksdb::Status> Json::MGet(const std::vector<std::string> 
&user_keys, const std::string &path,
+                                        std::vector<JsonValue> &results) {
+  std::vector<Slice> ns_keys;
+  std::vector<std::string> ns_keys_string;
+  ns_keys.resize(user_keys.size());
+  ns_keys_string.resize(user_keys.size());
+
+  for (size_t i = 0; i < user_keys.size(); i++) {
+    ns_keys_string[i] = AppendNamespacePrefix(user_keys[i]);
+    ns_keys[i] = Slice(ns_keys_string[i]);
+  }
+
+  std::vector<JsonValue> json_vals;
+  json_vals.resize(ns_keys.size());
+  auto statuses = readMulti(ns_keys, json_vals);
+
+  results.resize(ns_keys.size());
+  for (size_t i = 0; i < ns_keys.size(); i++) {
+    if (!statuses[i].ok()) {
+      continue;
+    }
+    auto res = json_vals[i].Get(path);
+
+    if (!res) {
+      statuses[i] = rocksdb::Status::Corruption(res.Msg());
+    } else {
+      results[i] = *std::move(res);
+    }
+  }
+  return statuses;
+}
+
+std::vector<rocksdb::Status> Json::readMulti(const std::vector<Slice> 
&ns_keys, std::vector<JsonValue> &values) {
+  std::vector<std::string> raw_values;
+  std::vector<JsonMetadata> meta_data;
+  raw_values.resize(ns_keys.size());
+  meta_data.resize(ns_keys.size());
+
+  auto statuses = getRawMetaData(ns_keys, meta_data, &raw_values);
+  for (size_t i = 0; i < ns_keys.size(); i++) {
+    if (!statuses[i].ok()) continue;
+    if (meta_data[i].format == JsonStorageFormat::JSON) {
+      auto res = JsonValue::FromString(raw_values[i]);
+      if (!res) {
+        statuses[i] = rocksdb::Status::Corruption(res.Msg());
+        continue;
+      }
+      values[i] = *std::move(res);
+    } else if (meta_data[i].format == JsonStorageFormat::CBOR) {
+      auto res = JsonValue::FromCBOR(raw_values[i]);
+      if (!res) {
+        statuses[i] = rocksdb::Status::Corruption(res.Msg());
+        continue;
+      }
+      values[i] = *std::move(res);
+    } else {
+      statuses[i] = rocksdb::Status::NotSupported("JSON storage format not 
supported");
+    }
+  }
+  return statuses;
+}
+
+std::vector<rocksdb::Status> Json::getRawMetaData(const std::vector<Slice> 
&ns_keys,
+                                                  std::vector<JsonMetadata> 
&metadatas,
+                                                  std::vector<std::string> 
*raw_values) {
+  rocksdb::ReadOptions read_options = storage_->DefaultMultiGetOptions();
+  LatestSnapShot ss(storage_);
+  read_options.snapshot = ss.GetSnapShot();
+  raw_values->resize(ns_keys.size());
+  std::vector<rocksdb::Status> statuses(ns_keys.size());
+  std::vector<rocksdb::PinnableSlice> pin_values(ns_keys.size());
+  storage_->MultiGet(read_options, metadata_cf_handle_, ns_keys.size(), 
ns_keys.data(), pin_values.data(),
+                     statuses.data());
+  for (size_t i = 0; i < ns_keys.size(); i++) {
+    if (!statuses[i].ok()) continue;
+    Slice slice(pin_values[i].data(), pin_values[i].size());
+    statuses[i] = ParseMetadata(kRedisJson, &slice, &metadatas[i]);
+    if (!statuses[i].ok()) continue;
+    (*raw_values)[i].assign(slice.data(), slice.size());
+  }
+  return statuses;
+}
+
 }  // namespace redis
diff --git a/src/types/redis_json.h b/src/types/redis_json.h
index e0e5ec31..a3993786 100644
--- a/src/types/redis_json.h
+++ b/src/types/redis_json.h
@@ -64,6 +64,9 @@ class Json : public Database {
   rocksdb::Status StrLen(const std::string &user_key, const std::string &path, 
Optionals<uint64_t> *results);
   rocksdb::Status ObjLen(const std::string &user_key, const std::string &path, 
Optionals<uint64_t> *results);
 
+  std::vector<rocksdb::Status> MGet(const std::vector<std::string> &user_keys, 
const std::string &path,
+                                    std::vector<JsonValue> &results);
+
  private:
   rocksdb::Status write(Slice ns_key, JsonMetadata *metadata, const JsonValue 
&json_val);
   rocksdb::Status read(const Slice &ns_key, JsonMetadata *metadata, JsonValue 
*value);
@@ -71,6 +74,9 @@ class Json : public Database {
   rocksdb::Status del(const Slice &ns_key);
   rocksdb::Status numop(JsonValue::NumOpEnum op, const std::string &user_key, 
const std::string &path,
                         const std::string &value, JsonValue *result);
+  std::vector<rocksdb::Status> readMulti(const std::vector<Slice> &ns_keys, 
std::vector<JsonValue> &values);
+  std::vector<rocksdb::Status> getRawMetaData(const std::vector<Slice> 
&ns_keys, std::vector<JsonMetadata> &metadatas,
+                                              std::vector<std::string> 
*raw_values);
 };
 
 }  // namespace redis
diff --git a/tests/gocase/unit/type/json/json_test.go 
b/tests/gocase/unit/type/json/json_test.go
index 37aa168c..a849d46d 100644
--- a/tests/gocase/unit/type/json/json_test.go
+++ b/tests/gocase/unit/type/json/json_test.go
@@ -581,4 +581,36 @@ func TestJson(t *testing.T) {
                err = rdb.Do(ctx, "JSON.OBJLEN", "no-such-json-key", "$").Err()
                require.EqualError(t, err, redis.Nil.Error())
        })
+
+       t.Run("JSON.MGET basics", func(t *testing.T) {
+               require.NoError(t, rdb.Do(ctx, "JSON.SET", "a0", "$", `{"a":1, 
"b": 2, "nested": {"a": 3}, "c": null}`).Err())
+               require.NoError(t, rdb.Do(ctx, "JSON.SET", "a1", "$", `{"a":4, 
"b": 5, "nested": {"a": 6}, "c": null}`).Err())
+               require.NoError(t, rdb.Do(ctx, "SET", "a2", `{"a": 100}`).Err())
+
+               vals, err := rdb.Do(ctx, "JSON.MGET", "a0", "a1", "$.a").Slice()
+               require.NoError(t, err)
+               require.Equal(t, 2, len(vals))
+               require.EqualValues(t, "[1]", vals[0])
+               require.EqualValues(t, "[4]", vals[1])
+
+               vals, err = rdb.Do(ctx, "JSON.MGET", "a0", "a1", "a2", 
"$.a").Slice()
+               require.NoError(t, err)
+               require.Equal(t, 3, len(vals))
+               require.EqualValues(t, "[1]", vals[0])
+               require.EqualValues(t, "[4]", vals[1])
+               require.EqualValues(t, nil, vals[2])
+
+               vals, err = rdb.Do(ctx, "JSON.MGET", "a0", "a1", "$.c").Slice()
+               require.NoError(t, err)
+               require.Equal(t, 2, len(vals))
+               require.EqualValues(t, "[null]", vals[0])
+               require.EqualValues(t, "[null]", vals[1])
+
+               vals, err = rdb.Do(ctx, "JSON.MGET", "a0", "a1", 
"$.nonexists").Slice()
+               require.NoError(t, err)
+               require.Equal(t, 2, len(vals))
+               require.EqualValues(t, "[]", vals[0])
+               require.EqualValues(t, "[]", vals[1])
+
+       })
 }

Reply via email to