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 6a02fade Support for the JSON.ARRLEN command (#1856)
6a02fade is described below

commit 6a02fade89a91fe5c4c339c4187fb2d34e5f76d4
Author: Ziyi Tan <[email protected]>
AuthorDate: Thu Oct 26 22:31:46 2023 +0800

    Support for the JSON.ARRLEN command (#1856)
---
 src/commands/cmd_json.cc                 | 36 +++++++++++++++++++++++-
 src/types/json.h                         | 17 ++++++++++++
 src/types/redis_json.cc                  | 14 ++++++++++
 src/types/redis_json.h                   |  2 ++
 tests/cppunit/types/json_test.cc         | 47 ++++++++++++++++++++++++++++++++
 tests/gocase/unit/type/json/json_test.go | 33 ++++++++++++++++++++++
 6 files changed, 148 insertions(+), 1 deletion(-)

diff --git a/src/commands/cmd_json.cc b/src/commands/cmd_json.cc
index 7ce58add..16678999 100644
--- a/src/commands/cmd_json.cc
+++ b/src/commands/cmd_json.cc
@@ -171,10 +171,44 @@ class CommandJsonClear : public Commander {
   }
 };
 
+class CommandJsonArrLen : 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::optional<uint64_t>> results;
+    auto s = json.ArrLen(args_[1], path, results);
+    if (s.IsNotFound()) {
+      *output = redis::NilString();
+      return Status::OK();
+    }
+    if (!s.ok()) return {Status::RedisExecErr, s.ToString()};
+
+    *output = redis::MultiLen(results.size());
+    for (const auto &len : results) {
+      if (len.has_value()) {
+        *output += redis::Integer(len.value());
+      } else {
+        *output += redis::NilString();
+      }
+    }
+
+    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<CommandJsonType>("json.type", -2, 
"read-only", 1, 1, 1),
                         MakeCmdAttr<CommandJsonArrAppend>("json.arrappend", 
-4, "write", 1, 1, 1),
-                        MakeCmdAttr<CommandJsonClear>("json.clear", -2, 
"write", 1, 1, 1), );
+                        MakeCmdAttr<CommandJsonClear>("json.clear", -2, 
"write", 1, 1, 1),
+                        MakeCmdAttr<CommandJsonArrLen>("json.arrlen", -2, 
"read-only", 1, 1, 1), );
 
 }  // namespace redis
diff --git a/src/types/json.h b/src/types/json.h
index 8d56a92a..cea9bcc9 100644
--- a/src/types/json.h
+++ b/src/types/json.h
@@ -198,6 +198,23 @@ struct JsonValue {
     return count;
   }
 
+  Status ArrLen(std::string_view path, std::vector<std::optional<uint64_t>> 
&arr_lens) const {
+    try {
+      jsoncons::jsonpath::json_query(value, path,
+                                     [&arr_lens](const std::string & /*path*/, 
const jsoncons::json &basic_json) {
+                                       if (basic_json.is_array()) {
+                                         
arr_lens.emplace_back(static_cast<uint64_t>(basic_json.size()));
+                                       } else {
+                                         arr_lens.emplace_back(std::nullopt);
+                                       }
+                                     });
+    } catch (const jsoncons::jsonpath::jsonpath_error &e) {
+      return {Status::NotOK, e.what()};
+    }
+
+    return Status::OK();
+  }
+
   JsonValue(const JsonValue &) = default;
   JsonValue(JsonValue &&) = default;
 
diff --git a/src/types/redis_json.cc b/src/types/redis_json.cc
index 45785a2a..6096003b 100644
--- a/src/types/redis_json.cc
+++ b/src/types/redis_json.cc
@@ -187,4 +187,18 @@ rocksdb::Status Json::Clear(const std::string &user_key, 
const std::string &path
 
   return write(ns_key, &metadata, json_val);
 }
+
+rocksdb::Status Json::ArrLen(const std::string &user_key, const std::string 
&path,
+                             std::vector<std::optional<uint64_t>> &arr_lens) {
+  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 len_res = json_val.ArrLen(path, arr_lens);
+  if (!len_res) return rocksdb::Status::InvalidArgument(len_res.Msg());
+
+  return rocksdb::Status::OK();
+}
 }  // namespace redis
diff --git a/src/types/redis_json.h b/src/types/redis_json.h
index cac45796..8eeb2571 100644
--- a/src/types/redis_json.h
+++ b/src/types/redis_json.h
@@ -39,6 +39,8 @@ class Json : public Database {
   rocksdb::Status ArrAppend(const std::string &user_key, const std::string 
&path,
                             const std::vector<std::string> &values, 
std::vector<size_t> *result_count);
   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);
 
  private:
   rocksdb::Status write(Slice ns_key, JsonMetadata *metadata, const JsonValue 
&json_val);
diff --git a/tests/cppunit/types/json_test.cc b/tests/cppunit/types/json_test.cc
index 738504b9..efd646b9 100644
--- a/tests/cppunit/types/json_test.cc
+++ b/tests/cppunit/types/json_test.cc
@@ -225,3 +225,50 @@ TEST_F(RedisJsonTest, Clear) {
   ASSERT_TRUE(json_->Clear(key_, "$.some", &result).ok());
   ASSERT_EQ(result, 0);
 }
+
+TEST_F(RedisJsonTest, ArrLen) {
+  ASSERT_TRUE(
+      json_->Set(key_, "$", 
R"({"a1":[1,2],"a2":[[1,5,7],[8],[9]],"i":1,"d":1.0,"s":"string","o":{"a3":[1,1,1]}})")
+          .ok());
+  // 1. simple array
+  std::vector<std::optional<uint64_t>> res;
+  ASSERT_TRUE(json_->ArrLen(key_, "$.a1", res).ok());
+  ASSERT_EQ(res.size(), 1);
+  ASSERT_EQ(res[0], 2);
+  res.clear();
+  // 2. nested array
+  ASSERT_TRUE(json_->ArrLen(key_, "$.a2", res).ok());
+  ASSERT_EQ(res.size(), 1);
+  ASSERT_EQ(res[0], 3);
+  res.clear();
+  ASSERT_TRUE(json_->ArrLen(key_, "$.a2[0]", res).ok());
+  ASSERT_EQ(res.size(), 1);
+  ASSERT_EQ(res[0], 3);
+  res.clear();
+  // 3.non-array type
+  ASSERT_TRUE(json_->ArrLen(key_, "$.i", res).ok());
+  ASSERT_EQ(res.size(), 1);
+  ASSERT_EQ(res[0], std::nullopt);
+  res.clear();
+  ASSERT_TRUE(json_->ArrLen(key_, "$.d", res).ok());
+  ASSERT_EQ(res.size(), 1);
+  ASSERT_EQ(res[0], std::nullopt);
+  res.clear();
+  ASSERT_TRUE(json_->ArrLen(key_, "$.s", res).ok());
+  ASSERT_EQ(res.size(), 1);
+  ASSERT_EQ(res[0], std::nullopt);
+  res.clear();
+  // 4. object
+  ASSERT_TRUE(json_->ArrLen(key_, "$.o", res).ok());
+  ASSERT_EQ(res.size(), 1);
+  ASSERT_EQ(res[0], std::nullopt);
+  res.clear();
+  ASSERT_TRUE(json_->ArrLen(key_, "$.o.a3", res).ok());
+  ASSERT_EQ(res.size(), 1);
+  ASSERT_EQ(res[0], 3);
+  res.clear();
+  // 5. key/path is not found
+  ASSERT_FALSE(json_->ArrLen("not_exists", "$.*", res).ok());
+  ASSERT_TRUE(json_->ArrLen(key_, "$.not_exists", res).ok());
+  ASSERT_TRUE(res.empty());
+}
diff --git a/tests/gocase/unit/type/json/json_test.go 
b/tests/gocase/unit/type/json/json_test.go
index 2a0cd01e..c0e7f803 100644
--- a/tests/gocase/unit/type/json/json_test.go
+++ b/tests/gocase/unit/type/json/json_test.go
@@ -152,4 +152,37 @@ func TestJson(t *testing.T) {
                require.NoError(t, err)
        })
 
+       t.Run("JSON.ARRLEN basics", func(t *testing.T) {
+               require.NoError(t, rdb.Do(ctx, "DEL", "a").Err())
+               require.NoError(t, rdb.Do(ctx, "JSON.SET", "a", "$", 
`{"a1":[1,2],"a2":[[1,5,7],[8],[9]],"i":1,"d":1.0,"s":"string","o":{"a3":[1,1,1]}}`).Err())
+
+               lens, err := rdb.Do(ctx, "JSON.ARRLEN", "a", 
"$.a1").Uint64Slice()
+               require.NoError(t, err)
+               require.EqualValues(t, []uint64{2}, lens)
+
+               lens, err = rdb.Do(ctx, "JSON.ARRLEN", "a", 
"$.a2").Uint64Slice()
+               require.NoError(t, err)
+               require.EqualValues(t, []uint64{3}, lens)
+
+               lens, err = rdb.Do(ctx, "JSON.ARRLEN", "a", 
"$.a2[0]").Uint64Slice()
+               require.NoError(t, err)
+               require.EqualValues(t, []uint64{3}, lens)
+
+               require.EqualValues(t, []interface{}{nil}, rdb.Do(ctx, 
"JSON.ARRLEN", "a", "$.i").Val())
+               require.EqualValues(t, []interface{}{nil}, rdb.Do(ctx, 
"JSON.ARRLEN", "a", "$.d").Val())
+               require.EqualValues(t, []interface{}{nil}, rdb.Do(ctx, 
"JSON.ARRLEN", "a", "$.s").Val())
+               require.EqualValues(t, []interface{}{nil}, rdb.Do(ctx, 
"JSON.ARRLEN", "a", "$.o").Val())
+
+               lens, err = rdb.Do(ctx, "JSON.ARRLEN", "a", 
"$.o.a3").Uint64Slice()
+               require.NoError(t, err)
+               require.EqualValues(t, []uint64{3}, lens)
+
+               _, err = rdb.Do(ctx, "JSON.ARRLEN", "not_exists", 
"$.*").Uint64Slice()
+               require.EqualError(t, err, redis.Nil.Error())
+
+               lens, err = rdb.Do(ctx, "JSON.ARRLEN", "a", 
"$.not_exists").Uint64Slice()
+               require.NoError(t, err)
+               require.EqualValues(t, []uint64{}, lens)
+       })
+
 }

Reply via email to