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 411ebfed Add the support of the json.arrappend command (#1837)
411ebfed is described below

commit 411ebfeda0402dab1fb3889a5f06d4794a98b62a
Author: Elaina <[email protected]>
AuthorDate: Fri Oct 20 20:57:51 2023 +0800

    Add the support of the json.arrappend command (#1837)
    
    Co-authored-by: mwish <[email protected]>
---
 src/commands/cmd_json.cc                 | 26 +++++++++++++++++++-
 src/types/json.h                         | 18 ++++++++++++++
 src/types/redis_json.cc                  | 37 ++++++++++++++++++++++++++++
 src/types/redis_json.h                   |  2 ++
 tests/cppunit/types/json_test.cc         | 41 ++++++++++++++++++++++++++++++++
 tests/gocase/unit/type/json/json_test.go | 29 ++++++++++++++++++++++
 6 files changed, 152 insertions(+), 1 deletion(-)

diff --git a/src/commands/cmd_json.cc b/src/commands/cmd_json.cc
index ad52467a..0a622dd7 100644
--- a/src/commands/cmd_json.cc
+++ b/src/commands/cmd_json.cc
@@ -52,7 +52,31 @@ class CommandJsonGet : public Commander {
   }
 };
 
+class CommandJsonArrAppend : public Commander {
+ public:
+  Status Execute(Server *svr, Connection *conn, std::string *output) override {
+    redis::Json json(svr->storage, conn->GetNamespace());
+
+    std::vector<uint64_t> result_count;
+
+    auto s = json.ArrAppend(args_[1], args_[2], {args_.begin() + 3, 
args_.end()}, &result_count);
+    if (!s.ok()) return {Status::RedisExecErr, s.ToString()};
+
+    *output = redis::MultiLen(result_count.size());
+    for (uint64_t c : result_count) {
+      if (c != 0) {
+        *output += redis::Integer(c);
+      } else {
+        *output += redis::NilString();
+      }
+    }
+
+    return Status::OK();
+  }
+};
+
 REDIS_REGISTER_COMMANDS(MakeCmdAttr<CommandJsonSet>("json.set", -3, "write", 
1, 1, 1),
-                        MakeCmdAttr<CommandJsonGet>("json.get", -2, 
"read-only", 1, 1, 1), );
+                        MakeCmdAttr<CommandJsonGet>("json.get", -2, 
"read-only", 1, 1, 1),
+                        MakeCmdAttr<CommandJsonArrAppend>("json.arrappend", 
-4, "write", 1, 1, 1), );
 
 }  // namespace redis
diff --git a/src/types/json.h b/src/types/json.h
index 5fbf02e2..04be4150 100644
--- a/src/types/json.h
+++ b/src/types/json.h
@@ -73,6 +73,24 @@ struct JsonValue {
     }
   }
 
+  Status ArrAppend(std::string_view path, const std::vector<jsoncons::json> 
&append_values,
+                   std::vector<uint64_t> *result_count) {
+    try {
+      jsoncons::jsonpath::json_replace(
+          value, path, [&append_values, result_count](const std::string &path, 
jsoncons::json &val) {
+            if (val.is_array()) {
+              val.insert(val.array_range().end(), append_values.begin(), 
append_values.end());
+              result_count->emplace_back(val.size());
+            } else {
+              result_count->emplace_back(0);
+            }
+          });
+    } 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 a978023f..0cb5f9a3 100644
--- a/src/types/redis_json.cc
+++ b/src/types/redis_json.cc
@@ -117,4 +117,41 @@ rocksdb::Status Json::Get(const std::string &user_key, 
const std::vector<std::st
   return rocksdb::Status::OK();
 }
 
+rocksdb::Status Json::ArrAppend(const std::string &user_key, const std::string 
&path,
+                                const std::vector<std::string> &values, 
std::vector<uint64_t> *result_count) {
+  auto ns_key = AppendNamespacePrefix(user_key);
+
+  std::vector<jsoncons::json> append_values;
+  append_values.reserve(values.size());
+  for (auto &v : values) {
+    auto value_res = JsonValue::FromString(v);
+    if (!value_res) return rocksdb::Status::InvalidArgument(value_res.Msg());
+    auto value = *std::move(value_res);
+    append_values.emplace_back(std::move(value.value));
+  }
+
+  LockGuard guard(storage_->GetLockManager(), ns_key);
+
+  std::string bytes;
+  JsonMetadata metadata;
+  Slice rest;
+  auto s = GetMetadata(kRedisJson, ns_key, &bytes, &metadata, &rest);
+  if (!s.ok()) return s;
+
+  if (metadata.format != JsonStorageFormat::JSON)
+    return rocksdb::Status::NotSupported("JSON storage format not supported");
+
+  auto value_res = JsonValue::FromString(rest.ToStringView());
+  if (!value_res) return rocksdb::Status::Corruption(value_res.Msg());
+  auto value = *std::move(value_res);
+
+  auto append_res = value.ArrAppend(path, append_values, result_count);
+  if (!append_res) return rocksdb::Status::InvalidArgument(append_res.Msg());
+
+  bool is_write = std::any_of(result_count->begin(), result_count->end(), 
[](uint64_t c) { return c > 0; });
+  if (!is_write) return rocksdb::Status::OK();
+
+  return write(ns_key, &metadata, value);
+}
+
 }  // namespace redis
diff --git a/src/types/redis_json.h b/src/types/redis_json.h
index 413383c8..33c2625a 100644
--- a/src/types/redis_json.h
+++ b/src/types/redis_json.h
@@ -34,6 +34,8 @@ class Json : public Database {
 
   rocksdb::Status Set(const std::string &user_key, const std::string &path, 
const std::string &value);
   rocksdb::Status Get(const std::string &user_key, const 
std::vector<std::string> &paths, JsonValue *result);
+  rocksdb::Status ArrAppend(const std::string &user_key, const std::string 
&path,
+                            const std::vector<std::string> &values, 
std::vector<uint64_t> *result_count);
 
  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 22233355..b559fae9 100644
--- a/tests/cppunit/types/json_test.cc
+++ b/tests/cppunit/types/json_test.cc
@@ -119,3 +119,44 @@ TEST_F(RedisJsonTest, Get) {
   ASSERT_TRUE(json_->Get(key_, {"$..x", "$..y", "$..z"}, &json_val_).ok());
   ASSERT_EQ(json_val_.Dump(), 
R"({"$..x":[{"y":1},4],"$..y":[[2,{"z":3}],1],"$..z":[{"a":{"x":4}},3]})");
 }
+
+TEST_F(RedisJsonTest, ArrAppend) {
+  std::vector<uint64_t> res;
+
+  ASSERT_FALSE(json_->ArrAppend(key_, "$", {"1"}, &res).ok());
+
+  ASSERT_TRUE(json_->Set(key_, "$", R"({"x":1,"y":[]})").ok());
+  ASSERT_TRUE(json_->ArrAppend(key_, "$.x", {"1"}, &res).ok());
+  ASSERT_EQ(res.size(), 1);
+  ASSERT_EQ(res[0], 0);
+  ASSERT_TRUE(json_->Get(key_, {}, &json_val_).ok());
+  ASSERT_EQ(json_val_.Dump(), R"({"x":1,"y":[]})");
+  res.clear();
+
+  ASSERT_TRUE(json_->Set(key_, "$", R"({"x":[1,2,{"z":3}],"y":[]})").ok());
+  ASSERT_TRUE(json_->ArrAppend(key_, "$.x", {"1"}, &res).ok());
+  ASSERT_EQ(res.size(), 1);
+  ASSERT_EQ(res[0], 4);
+  ASSERT_TRUE(json_->Get(key_, {}, &json_val_).ok());
+  ASSERT_EQ(json_val_.Dump(), R"({"x":[1,2,{"z":3},1],"y":[]})");
+  res.clear();
+
+  ASSERT_TRUE(json_->ArrAppend(key_, "$..y", {"1", "2", "3"}, &res).ok());
+  ASSERT_EQ(res.size(), 1);
+  ASSERT_EQ(res[0], 3);
+  ASSERT_TRUE(json_->Get(key_, {}, &json_val_).ok());
+  ASSERT_EQ(json_val_.Dump(), R"({"x":[1,2,{"z":3},1],"y":[1,2,3]})");
+  res.clear();
+
+  ASSERT_TRUE(json_->Set(key_, "$.x[2]", 
R"({"x":[1,2,{"z":3,"y":[]}],"y":[{"y":1}]})").ok());
+  ASSERT_TRUE(json_->ArrAppend(key_, "$..y", {"1", "2", "3"}, &res).ok());
+  ASSERT_EQ(res.size(), 4);
+  std::sort(res.begin(), res.end());
+  ASSERT_EQ(res[0], 0);
+  ASSERT_EQ(res[1], 3);
+  ASSERT_EQ(res[2], 4);
+  ASSERT_EQ(res[3], 6);
+  ASSERT_TRUE(json_->Get(key_, {}, &json_val_).ok());
+  ASSERT_EQ(json_val_.Dump(), 
R"({"x":[1,2,{"x":[1,2,{"y":[1,2,3],"z":3}],"y":[{"y":1},1,2,3]},1],"y":[1,2,3,1,2,3]})");
+  res.clear();
+}
diff --git a/tests/gocase/unit/type/json/json_test.go 
b/tests/gocase/unit/type/json/json_test.go
index 0e5f6032..f59746e6 100644
--- a/tests/gocase/unit/type/json/json_test.go
+++ b/tests/gocase/unit/type/json/json_test.go
@@ -55,4 +55,33 @@ func TestJson(t *testing.T) {
                require.Equal(t, rdb.Do(ctx, "JSON.GET", "a", "$..x").Val(), 
`[1,{"y":2}]`)
                require.Equal(t, rdb.Do(ctx, "JSON.GET", "a", "$..x", 
"$..y").Val(), `{"$..x":[1,{"y":2}],"$..y":[{"x":{"y":2},"y":3},3,2]}`)
        })
+
+       t.Run("JSON.ARRAPPEND basics", func(t *testing.T) {
+               require.NoError(t, rdb.Do(ctx, "SET", "a", `1`).Err())
+               require.Error(t, rdb.Do(ctx, "JSON.ARRAPPEND", "a", "$", 
`1`).Err())
+               require.NoError(t, rdb.Do(ctx, "DEL", "a").Err())
+
+               require.NoError(t, rdb.Do(ctx, "JSON.SET", "a", "$", ` {"x":1, 
"y": {"x":1} } `).Err())
+               require.Equal(t, []interface{}{}, rdb.Do(ctx, "JSON.ARRAPPEND", 
"a", "$..k", `1`).Val())
+               require.Error(t, rdb.Do(ctx, "JSON.ARRAPPEND", "a", "$").Err())
+               require.Error(t, rdb.Do(ctx, "JSON.ARRAPPEND", "a", "$", ` 1, 
2, 3`).Err())
+               require.Error(t, rdb.Do(ctx, "JSON.ARRAPPEND", "a", "$", `1`, ` 
1, 2, 3`).Err())
+               require.Equal(t, []interface{}{nil, nil}, rdb.Do(ctx, 
"JSON.ARRAPPEND", "a", "$..x", `1`).Val())
+
+               require.NoError(t, rdb.Do(ctx, "JSON.SET", "a", "$", ` {"x":1, 
"y": {"x":[]} } `).Err())
+               require.Equal(t, []interface{}{int64(1), nil}, rdb.Do(ctx, 
"JSON.ARRAPPEND", "a", "$..x", `1`).Val())
+               require.Equal(t, `[{"x":1,"y":{"x":[1]}}]`, rdb.Do(ctx, 
"JSON.GET", "a", "$").Val())
+
+               require.NoError(t, rdb.Do(ctx, "JSON.SET", "a", "$", ` {"x":[], 
"y":[]} `).Err())
+               require.Equal(t, []interface{}{int64(1)}, rdb.Do(ctx, 
"JSON.ARRAPPEND", "a", "$.x", `1`).Val())
+               require.Equal(t, `{"x":[1],"y":[]}`, rdb.Do(ctx, "JSON.GET", 
"a").Val())
+               require.Equal(t, []interface{}{int64(4)}, rdb.Do(ctx, 
"JSON.ARRAPPEND", "a", "$.x", `1`, `2`, `3`).Val())
+               require.Equal(t, []interface{}{int64(1)}, rdb.Do(ctx, 
"JSON.ARRAPPEND", "a", "$.y", ` {"x":[], "y":[]} `).Val())
+               require.Equal(t, `[{"x":[1,1,2,3],"y":[{"x":[],"y":[]}]}]`, 
rdb.Do(ctx, "JSON.GET", "a", "$").Val())
+
+               require.Equal(t, []interface{}{int64(2), int64(6)}, rdb.Do(ctx, 
"JSON.ARRAPPEND", "a", "$..x", `1`, `2`).Val())
+               require.Equal(t, `[[1,2]]`, rdb.Do(ctx, "JSON.GET", "a", 
"$.y[0].x").Val())
+               require.Equal(t, `[]`, rdb.Do(ctx, "JSON.GET", "a", 
"$.x.x").Val())
+               require.Equal(t, 
`[{"x":[1,1,2,3,1,2],"y":[{"x":[1,2],"y":[]}]}]`, rdb.Do(ctx, "JSON.GET", "a", 
"$").Val())
+       })
 }

Reply via email to