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 a79a386a Support for the JSON.OBJKEYS command (#1872)
a79a386a is described below

commit a79a386a71095b5a700c56b3d557cb79119618bb
Author: 纪华裕 <[email protected]>
AuthorDate: Tue Nov 7 13:01:10 2023 +0800

    Support for the JSON.OBJKEYS command (#1872)
---
 src/commands/cmd_json.cc                 | 30 ++++++++++++++++++++++++++++++
 src/types/json.h                         | 21 +++++++++++++++++++++
 src/types/redis_json.cc                  | 14 ++++++++++++++
 src/types/redis_json.h                   |  2 ++
 tests/gocase/unit/type/json/json_test.go | 25 +++++++++++++++++++++++++
 5 files changed, 92 insertions(+)

diff --git a/src/commands/cmd_json.cc b/src/commands/cmd_json.cc
index b35ac23c..86017fb5 100644
--- a/src/commands/cmd_json.cc
+++ b/src/commands/cmd_json.cc
@@ -167,6 +167,35 @@ class CommandJsonType : public Commander {
   }
 };
 
+class CommandJsonObjkeys : public Commander {
+ public:
+  Status Execute(Server *srv, Connection *conn, std::string *output) override {
+    redis::Json json(srv->storage, conn->GetNamespace());
+
+    std::vector<std::optional<std::vector<std::string>>> results;
+
+    // If path not specified set it to $
+    std::string path = (args_.size() > 2) ? args_[2] : "$";
+    auto s = json.ObjKeys(args_[1], path, results);
+    if (!s.ok() && !s.IsNotFound()) return {Status::RedisExecErr, 
s.ToString()};
+    if (s.IsNotFound()) {
+      *output = redis::NilString();
+      return Status::OK();
+    }
+
+    *output = redis::MultiLen(results.size());
+    for (const auto &item : results) {
+      if (item.has_value()) {
+        *output += redis::MultiBulkString(item.value(), false);
+      } else {
+        *output += redis::NilString();
+      }
+    }
+
+    return Status::OK();
+  }
+};
+
 class CommandJsonClear : public Commander {
  public:
   Status Execute(Server *svr, Connection *conn, std::string *output) override {
@@ -269,6 +298,7 @@ 
REDIS_REGISTER_COMMANDS(MakeCmdAttr<CommandJsonSet>("json.set", 4, "write", 1, 1
                         MakeCmdAttr<CommandJsonArrAppend>("json.arrappend", 
-4, "write", 1, 1, 1),
                         MakeCmdAttr<CommandJsonClear>("json.clear", -2, 
"write", 1, 1, 1),
                         MakeCmdAttr<CommandJsonArrLen>("json.arrlen", -2, 
"read-only", 1, 1, 1),
+                        MakeCmdAttr<CommandJsonObjkeys>("json.objkeys", -2, 
"read-only", 1, 1, 1),
                         MakeCmdAttr<CommandJsonArrPop>("json.arrpop", -2, 
"write", 1, 1, 1), );
 
 }  // namespace redis
diff --git a/src/types/json.h b/src/types/json.h
index d27022ef..442bef76 100644
--- a/src/types/json.h
+++ b/src/types/json.h
@@ -29,6 +29,7 @@
 #include <jsoncons_ext/jsonpath/json_query.hpp>
 #include <jsoncons_ext/jsonpath/jsonpath_error.hpp>
 #include <limits>
+#include <string>
 
 #include "status.h"
 
@@ -253,6 +254,26 @@ struct JsonValue {
     return Status::OK();
   }
 
+  Status ObjKeys(std::string_view path, 
std::vector<std::optional<std::vector<std::string>>> &keys) const {
+    try {
+      jsoncons::jsonpath::json_query(value, path,
+                                     [&keys](const std::string & /*path*/, 
const jsoncons::json &basic_json) {
+                                       if (basic_json.is_object()) {
+                                         std::vector<std::string> ret;
+                                         for (const auto &member : 
basic_json.object_range()) {
+                                           ret.push_back(member.key());
+                                         }
+                                         keys.emplace_back(ret);
+                                       } else {
+                                         keys.emplace_back(std::nullopt);
+                                       }
+                                     });
+    } catch (const jsoncons::jsonpath::jsonpath_error &e) {
+      return {Status::NotOK, e.what()};
+    }
+    return Status::OK();
+  }
+
   StatusOr<std::vector<std::optional<JsonValue>>> ArrPop(std::string_view 
path, int64_t index = -1) {
     std::vector<std::optional<JsonValue>> popped_values;
 
diff --git a/src/types/redis_json.cc b/src/types/redis_json.cc
index c95f8496..44da953d 100644
--- a/src/types/redis_json.cc
+++ b/src/types/redis_json.cc
@@ -252,4 +252,18 @@ rocksdb::Status Json::ArrPop(const std::string &user_key, 
const std::string &pat
 
   return write(ns_key, &metadata, json_val);
 }
+
+rocksdb::Status Json::ObjKeys(const std::string &user_key, const std::string 
&path,
+                              
std::vector<std::optional<std::vector<std::string>>> &keys) {
+  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 keys_res = json_val.ObjKeys(path, keys);
+  if (!keys_res) return rocksdb::Status::InvalidArgument(keys_res.Msg());
+
+  return rocksdb::Status::OK();
+}
+
 }  // namespace redis
diff --git a/src/types/redis_json.h b/src/types/redis_json.h
index 82b9e579..708199af 100644
--- a/src/types/redis_json.h
+++ b/src/types/redis_json.h
@@ -42,6 +42,8 @@ class Json : public Database {
   rocksdb::Status Clear(const std::string &user_key, const std::string &path, 
size_t *result);
   rocksdb::Status ArrLen(const std::string &user_key, const std::string &path,
                          std::vector<std::optional<uint64_t>> &arr_lens);
+  rocksdb::Status ObjKeys(const std::string &user_key, const std::string &path,
+                          std::vector<std::optional<std::vector<std::string>>> 
&keys);
   rocksdb::Status ArrPop(const std::string &user_key, const std::string &path, 
int64_t index,
                          std::vector<std::optional<JsonValue>> *results);
 
diff --git a/tests/gocase/unit/type/json/json_test.go 
b/tests/gocase/unit/type/json/json_test.go
index 61c4276c..b5d741cd 100644
--- a/tests/gocase/unit/type/json/json_test.go
+++ b/tests/gocase/unit/type/json/json_test.go
@@ -196,6 +196,31 @@ func TestJson(t *testing.T) {
                require.EqualValues(t, []uint64{}, lens)
        })
 
+       t.Run("JSON.OBJKEYS basics", func(t *testing.T) {
+               require.NoError(t, rdb.Del(ctx, "a").Err())
+               // key no exists
+               require.EqualError(t, rdb.Do(ctx, "JSON.OBJKEYS", "not_exists", 
"$").Err(), redis.Nil.Error())
+               // key not json
+               require.NoError(t, rdb.Do(ctx, "SET", "no_json", "1").Err())
+               require.Error(t, rdb.Do(ctx, "JSON.OBJKEYS", "no_json", 
"$").Err())
+               // json path no exists
+               require.NoError(t, rdb.Do(ctx, "JSON.SET", "a", "$", 
`{"a1":[1,2]}`).Err())
+               require.EqualValues(t, []interface{}{}, rdb.Do(ctx, 
"JSON.OBJKEYS", "a", "$.not_exists").Val())
+               // json path not object
+               require.EqualValues(t, []interface{}{nil}, rdb.Do(ctx, 
"JSON.OBJKEYS", "a", "$.a1").Val())
+               // default path
+               require.EqualValues(t, []interface{}{[]interface{}{"a1"}}, 
rdb.Do(ctx, "JSON.OBJKEYS", "a").Val())
+               // json path has one object
+               require.NoError(t, rdb.Do(ctx, "JSON.SET", "a", "$", 
`{"a1":{"b":1,"c":1}}`).Err())
+               require.EqualValues(t, []interface{}{[]interface{}{"b", "c"}}, 
rdb.Do(ctx, "JSON.OBJKEYS", "a", "$.a1").Val())
+               // json path has many object
+               require.NoError(t, rdb.Do(ctx, "JSON.SET", "a", "$", 
`{"a":{"a1":{"b":1,"c":1}},"b":{"a1":{"e":1,"f":1}}}`).Err())
+               require.EqualValues(t, []interface{}{[]interface{}{"b", "c"}, 
[]interface{}{"e", "f"}}, rdb.Do(ctx, "JSON.OBJKEYS", "a", "$..a1").Val())
+               // json path has many object and one is not object
+               require.NoError(t, rdb.Do(ctx, "JSON.SET", "a", "$", 
`{"a":{"a1":{"b":1,"c":1}},"b":{"a1":[1]},"c":{"a1":{"e":1,"f":1}}}`).Err())
+               require.EqualValues(t, []interface{}{[]interface{}{"b", "c"}, 
interface{}(nil), []interface{}{"e", "f"}}, rdb.Do(ctx, "JSON.OBJKEYS", "a", 
"$..a1").Val())
+       })
+
        t.Run("JSON.ARRPOP basics", func(t *testing.T) {
                require.NoError(t, rdb.Do(ctx, "JSON.SET", "a", "$", 
`[3,"str",2.1,{},[5,6]]`).Err())
                require.EqualValues(t, []interface{}{"[5,6]"}, rdb.Do(ctx, 
"JSON.ARRPOP", "a").Val())

Reply via email to