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 9bd993c9 feat(json): add support of JSON.RESP command (#2390)
9bd993c9 is described below

commit 9bd993c985a2f1d0e8c90fde9d00c40744ce579a
Author: lizhenglei <[email protected]>
AuthorDate: Tue Jul 23 15:54:02 2024 +0800

    feat(json): add support of JSON.RESP command (#2390)
    
    Co-authored-by: lizhenglei <[email protected]>
    Co-authored-by: hulk <[email protected]>
    Co-authored-by: Twice <[email protected]>
---
 src/commands/cmd_json.cc                 | 35 +++++++++++++++++++++++-
 src/types/json.h                         | 46 ++++++++++++++++++++++++++++++++
 src/types/redis_json.cc                  | 14 ++++++++++
 src/types/redis_json.h                   |  4 +++
 tests/gocase/unit/type/json/json_test.go | 40 +++++++++++++++++++++++++++
 5 files changed, 138 insertions(+), 1 deletion(-)

diff --git a/src/commands/cmd_json.cc b/src/commands/cmd_json.cc
index 7708d2f8..cb84aa9d 100644
--- a/src/commands/cmd_json.cc
+++ b/src/commands/cmd_json.cc
@@ -673,6 +673,38 @@ class CommandJsonDebug : public Commander {
     return Status::OK();
   }
 };
+
+class CommandJsonResp : public Commander {
+ public:
+  Status Execute(Server *svr, Connection *conn, std::string *output) override {
+    redis::Json json(svr->storage, conn->GetNamespace());
+
+    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"};
+    }
+    std::vector<std::string> results;
+    auto s = json.Resp(args_[1], path, &results, conn->GetProtocolVersion());
+    if (s.IsNotFound()) {
+      *output = conn->NilString();
+      return Status::OK();
+    }
+
+    if (!s.ok()) return {Status::RedisExecErr, s.ToString()};
+    if (args_.size() == 2) {
+      output->append(results.back());
+    } else {
+      output->append(MultiLen(results.size()));
+      for (const auto &result : results) {
+        output->append(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),
@@ -697,6 +729,7 @@ 
REDIS_REGISTER_COMMANDS(MakeCmdAttr<CommandJsonSet>("json.set", 4, "write", 1, 1
                         MakeCmdAttr<CommandJsonStrLen>("json.strlen", -2, 
"read-only", 1, 1, 1),
                         MakeCmdAttr<CommandJsonMGet>("json.mget", -3, 
"read-only", 1, -2, 1),
                         MakeCmdAttr<CommandJsonMSet>("json.mset", -4, "write", 
1, -3, 3),
-                        MakeCmdAttr<CommandJsonDebug>("json.debug", -3, 
"read-only", 2, 2, 1));
+                        MakeCmdAttr<CommandJsonDebug>("json.debug", -3, 
"read-only", 2, 2, 1),
+                        MakeCmdAttr<CommandJsonResp>("json.resp", -2, 
"read-only", 1, 1, 1));
 
 }  // namespace redis
diff --git a/src/types/json.h b/src/types/json.h
index 13abb5cc..4a1ab791 100644
--- a/src/types/json.h
+++ b/src/types/json.h
@@ -38,6 +38,7 @@
 
 #include "common/string_util.h"
 #include "jsoncons_ext/jsonpath/jsonpath_error.hpp"
+#include "server/redis_reply.h"
 #include "status.h"
 #include "storage/redis_metadata.h"
 
@@ -628,6 +629,51 @@ struct JsonValue {
     }
     return status;
   }
+  static void TransformResp(const jsoncons::json &origin, std::string 
&json_resp, redis::RESP resp) {
+    if (origin.is_object()) {
+      json_resp += redis::MultiLen(origin.size() * 2 + 1);
+      json_resp += redis::SimpleString("{");
+
+      for (const auto &json_kv : origin.object_range()) {
+        json_resp += redis::BulkString(json_kv.key());
+        TransformResp(json_kv.value(), json_resp, resp);
+      }
+
+    } else if (origin.is_int64() || origin.is_uint64()) {
+      json_resp += redis::Integer(origin.as_integer<int64_t>());
+
+    } else if (origin.is_string() || origin.is_double()) {
+      json_resp += redis::BulkString(origin.as_string());
+    } else if (origin.is_bool()) {
+      json_resp += redis::SimpleString(origin.as_bool() ? "true" : "false");
+
+    } else if (origin.is_null()) {
+      json_resp += redis::NilString(resp);
+
+    } else if (origin.is_array()) {
+      json_resp += redis::MultiLen(origin.size() + 1);
+      json_resp += redis::SimpleString("[");
+
+      for (const auto &json_array_value : origin.array_range()) {
+        TransformResp(json_array_value, json_resp, resp);
+      }
+    }
+  }
+
+  StatusOr<std::vector<std::string>> ConvertToResp(std::string_view path, 
redis::RESP resp) const {
+    std::vector<std::string> json_resps;
+    try {
+      jsoncons::jsonpath::json_query(value, path, [&](const std::string & 
/*path*/, const jsoncons::json &origin) {
+        std::string json_resp;
+        TransformResp(origin, json_resp, resp);
+        json_resps.emplace_back(json_resp);
+      });
+    } catch (const jsoncons::jsonpath::jsonpath_error &e) {
+      return {Status::NotOK, e.what()};
+    }
+
+    return json_resps;
+  }
 
   JsonValue(const JsonValue &) = default;
   JsonValue(JsonValue &&) = default;
diff --git a/src/types/redis_json.cc b/src/types/redis_json.cc
index 5f14b024..eba2eae1 100644
--- a/src/types/redis_json.cc
+++ b/src/types/redis_json.cc
@@ -646,4 +646,18 @@ rocksdb::Status Json::DebugMemory(const std::string 
&user_key, const std::string
   return rocksdb::Status::OK();
 }
 
+rocksdb::Status Json::Resp(const std::string &user_key, const std::string 
&path, std::vector<std::string> *results,
+                           RESP resp) {
+  auto ns_key = AppendNamespacePrefix(user_key);
+  JsonMetadata metadata;
+  JsonValue json_val;
+  auto s = read(ns_key, &metadata, &json_val);
+  if (!s.ok()) return s;
+
+  auto json_resps = json_val.ConvertToResp(path, resp);
+  if (!json_resps) return rocksdb::Status::InvalidArgument(json_resps.Msg());
+  *results = std::move(*json_resps);
+  return rocksdb::Status::OK();
+}
+
 }  // namespace redis
diff --git a/src/types/redis_json.h b/src/types/redis_json.h
index 8dc21235..d34a2f3f 100644
--- a/src/types/redis_json.h
+++ b/src/types/redis_json.h
@@ -25,6 +25,7 @@
 #include <string>
 
 #include "json.h"
+#include "server/redis_reply.h"
 #include "storage/redis_metadata.h"
 
 namespace redis {
@@ -70,6 +71,9 @@ class Json : public Database {
                        const std::vector<std::string> &values);
   rocksdb::Status DebugMemory(const std::string &user_key, const std::string 
&path, std::vector<size_t> *results);
 
+  rocksdb::Status Resp(const std::string &user_key, const std::string &path, 
std::vector<std::string> *results,
+                       RESP resp);
+
  private:
   rocksdb::Status write(Slice ns_key, JsonMetadata *metadata, const JsonValue 
&json_val);
   rocksdb::Status read(const Slice &ns_key, JsonMetadata *metadata, JsonValue 
*value);
diff --git a/tests/gocase/unit/type/json/json_test.go 
b/tests/gocase/unit/type/json/json_test.go
index e720c89b..a5e02dba 100644
--- a/tests/gocase/unit/type/json/json_test.go
+++ b/tests/gocase/unit/type/json/json_test.go
@@ -657,6 +657,46 @@ func TestJson(t *testing.T) {
                require.Equal(t, []interface{}{}, rdb.Do(ctx, "JSON.DEBUG", 
"MEMORY", "not_exists", "$").Val())
 
        })
+
+       t.Run("JSON.RESP basics", func(t *testing.T) {
+               require.NoError(t, rdb.Do(ctx, "JSON.SET", "item:2", "$", 
`{"name":"Wireless earbuds","description":"Wireless Bluetooth in-ear 
headphones","connection":{"wireless":true,"type":"null"},"price":64.99,"stock":17,"colors":[null,"white"],
 "max_level":[80, 100, 120]}`).Err())
+               //array object null both  have
+               var result = make([]interface{}, 0)
+               var resultarray1 = make([]interface{}, 0)
+               var resultobject1 = make([]interface{}, 0)
+               var resultarray2 = make([]interface{}, 0)
+               resultobject1 = append(resultobject1, "{", "type", "null", 
"wireless", "true")
+               resultarray1 = append(resultarray1, "[", nil, "white")
+               resultarray2 = append(resultarray2, "[", int64(80), int64(100), 
int64(120))
+               result = append(result, "{", "colors", resultarray1, 
"connection", resultobject1, "description", "Wireless Bluetooth in-ear 
headphones", "max_level", resultarray2, "name", "Wireless earbuds", "price", 
"64.99", "stock", int64(17))
+               require.Equal(t, result, rdb.Do(ctx, "JSON.RESP", 
"item:2").Val())
+               require.Equal(t, []interface{}{result}, rdb.Do(ctx, 
"JSON.RESP", "item:2", "$").Val())
+
+               //array
+               require.Equal(t, []interface{}{resultarray1}, rdb.Do(ctx, 
"JSON.RESP", "item:2", "$.colors").Val())
+               //object
+               require.Equal(t, []interface{}{resultobject1}, rdb.Do(ctx, 
"JSON.RESP", "item:2", "$.connection").Val())
+               //string
+               var stringvalue = make([]interface{}, 0)
+               require.Equal(t, append(stringvalue, "Wireless Bluetooth in-ear 
headphones"), rdb.Do(ctx, "JSON.RESP", "item:2", "$.description").Val())
+               //bool
+               var boolvalue = make([]interface{}, 0)
+               require.Equal(t, append(boolvalue, "true"), rdb.Do(ctx, 
"JSON.RESP", "item:2", "$.connection.wireless").Val())
+               //int
+               var intvalue = make([]interface{}, 0)
+               require.Equal(t, append(intvalue, int64(17)), rdb.Do(ctx, 
"JSON.RESP", "item:2", "$.stock").Val())
+               require.NoError(t, rdb.Do(ctx, "JSON.SET", "item:3", "$", `{ 
"c1": [ { "a2": 1, "b2": "John Doe", "c2": 30, "d2": [ "Developer", "Team Lead" 
] }, { "a2": 2, "b2": "Jane Smith", "c2": 25, "d2": [ "Developer" ] } ] 
}`).Err())
+               require.Equal(t, []interface{}{int64(1), int64(2)}, rdb.Do(ctx, 
"JSON.RESP", "item:3", "$..a2").Val())
+
+               //key no_exists
+               require.ErrorIs(t, rdb.Do(ctx, "JSON.RESP", "no_exists", 
"$").Err(), redis.Nil)
+               require.ErrorIs(t, rdb.Do(ctx, "JSON.RESP", "no_exists").Err(), 
redis.Nil)
+
+               //have key no find
+               require.Equal(t, make([]interface{}, 0), rdb.Do(ctx, 
"JSON.RESP", "item:2", "$.a").Val())
+
+       })
+
 }
 
 func EqualJSON(t *testing.T, expected string, actual interface{}) {

Reply via email to