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 21c81680 Support for the JSON.ARRTRIM command (#1881)
21c81680 is described below
commit 21c81680f88739bca666a74804e89b1667e96dae
Author: 纪华裕 <[email protected]>
AuthorDate: Sun Nov 12 21:50:21 2023 +0800
Support for the JSON.ARRTRIM command (#1881)
---
src/commands/cmd_json.cc | 42 ++++++++++++++++
src/types/json.h | 33 +++++++++++++
src/types/redis_json.cc | 19 ++++++++
src/types/redis_json.h | 3 ++
tests/gocase/unit/type/json/json_test.go | 84 ++++++++++++++++++++++++++++++++
5 files changed, 181 insertions(+)
diff --git a/src/commands/cmd_json.cc b/src/commands/cmd_json.cc
index 50dacba2..4f316b65 100644
--- a/src/commands/cmd_json.cc
+++ b/src/commands/cmd_json.cc
@@ -346,6 +346,47 @@ class CommandJsonArrPop : public Commander {
int64_t index_ = -1;
};
+class CommandJsonArrTrim : public Commander {
+ public:
+ Status Parse(const std::vector<std::string> &args) override {
+ path_ = args_[2];
+ start_ = GET_OR_RET(ParseInt<int64_t>(args_[3], 10));
+ stop_ = GET_OR_RET(ParseInt<int64_t>(args_[4], 10));
+
+ return Status::OK();
+ }
+
+ Status Execute(Server *srv, Connection *conn, std::string *output) override {
+ redis::Json json(srv->storage, conn->GetNamespace());
+
+ std::vector<std::optional<uint64_t>> results;
+
+ auto s = json.ArrTrim(args_[1], path_, start_, stop_, 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();
+ }
+
+ private:
+ std::string path_;
+ int64_t start_ = 0;
+ int64_t stop_ = 0;
+};
+
class CommanderJsonArrIndex : public Commander {
public:
Status Parse(const std::vector<std::string> &args) override {
@@ -399,6 +440,7 @@
REDIS_REGISTER_COMMANDS(MakeCmdAttr<CommandJsonSet>("json.set", 4, "write", 1, 1
MakeCmdAttr<CommandJsonInfo>("json.info", 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<CommandJsonArrTrim>("json.arrtrim", 5,
"write", 1, 1, 1),
MakeCmdAttr<CommandJsonClear>("json.clear", -2,
"write", 1, 1, 1),
MakeCmdAttr<CommandJsonToggle>("json.toggle", -2,
"write", 1, 1, 1),
MakeCmdAttr<CommandJsonArrLen>("json.arrlen", -2,
"read-only", 1, 1, 1),
diff --git a/src/types/json.h b/src/types/json.h
index 6050e1cc..62d49bfc 100644
--- a/src/types/json.h
+++ b/src/types/json.h
@@ -20,6 +20,7 @@
#pragma once
+#include <algorithm>
#include <jsoncons/json.hpp>
#include <jsoncons/json_error.hpp>
#include <jsoncons/json_options.hpp>
@@ -413,6 +414,38 @@ struct JsonValue {
return popped_values;
}
+ Status ArrTrim(std::string_view path, int64_t start, int64_t stop,
std::vector<std::optional<uint64_t>> &results) {
+ try {
+ jsoncons::jsonpath::json_replace(
+ value, path, [&results, start, stop](const std::string & /*path*/,
jsoncons::json &val) {
+ if (val.is_array()) {
+ auto len = static_cast<int64_t>(val.size());
+ auto begin_index = start < 0 ? std::max(len + start,
static_cast<int64_t>(0)) : start;
+ auto end_index = std::min(stop < 0 ? std::max(len + stop,
static_cast<int64_t>(0)) : stop, len - 1);
+
+ if (begin_index >= len || begin_index > end_index) {
+ val = jsoncons::json::array();
+ results.emplace_back(0);
+ return;
+ }
+
+ auto n_val = jsoncons::json::array();
+ auto begin_iter = val.array_range().begin();
+
+ n_val.insert(n_val.end(), begin_iter + begin_index, begin_iter +
end_index + 1);
+ val = n_val;
+ results.emplace_back(static_cast<int64_t>(n_val.size()));
+ } else {
+ results.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 e16fbbdd..de410ce6 100644
--- a/src/types/redis_json.cc
+++ b/src/types/redis_json.cc
@@ -339,4 +339,23 @@ rocksdb::Status Json::ObjKeys(const std::string &user_key,
const std::string &pa
return rocksdb::Status::OK();
}
+rocksdb::Status Json::ArrTrim(const std::string &user_key, const std::string
&path, int64_t start, int64_t stop,
+ std::vector<std::optional<uint64_t>> &results) {
+ auto ns_key = AppendNamespacePrefix(user_key);
+
+ LockGuard guard(storage_->GetLockManager(), ns_key);
+
+ JsonMetadata metadata;
+ JsonValue json_val;
+ auto s = read(ns_key, &metadata, &json_val);
+ if (!s.ok()) return s;
+
+ auto len_res = json_val.ArrTrim(path, start, stop, results);
+ if (!len_res) return rocksdb::Status::InvalidArgument(len_res.Msg());
+ bool is_write =
+ std::any_of(results.begin(), results.end(), [](const
std::optional<uint64_t> &val) { return val.has_value(); });
+ if (!is_write) return rocksdb::Status::OK();
+ return write(ns_key, &metadata, json_val);
+}
+
} // namespace redis
diff --git a/src/types/redis_json.h b/src/types/redis_json.h
index a34ff19d..1a181e59 100644
--- a/src/types/redis_json.h
+++ b/src/types/redis_json.h
@@ -52,6 +52,9 @@ class Json : public Database {
rocksdb::Status ArrIndex(const std::string &user_key, const std::string
&path, const std::string &needle,
ssize_t start, ssize_t end, std::vector<ssize_t>
*result);
+ rocksdb::Status ArrTrim(const std::string &user_key, const std::string
&path, int64_t start, int64_t stop,
+ std::vector<std::optional<uint64_t>> &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);
diff --git a/tests/gocase/unit/type/json/json_test.go
b/tests/gocase/unit/type/json/json_test.go
index 0208066b..ba5806b5 100644
--- a/tests/gocase/unit/type/json/json_test.go
+++ b/tests/gocase/unit/type/json/json_test.go
@@ -264,6 +264,90 @@ func TestJson(t *testing.T) {
require.ErrorContains(t, rdb.Do(ctx, "JSON.ARRPOP", "a", "$",
"0", "1").Err(), "wrong number of arguments")
})
+ t.Run("JSON.ARRTRIM basics", func(t *testing.T) {
+ require.NoError(t, rdb.Del(ctx, "a").Err())
+ // key no exists
+ require.EqualError(t, rdb.Do(ctx, "JSON.ARRTRIM", "not_exists",
"$", 0, 0).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.ARRTRIM", "no_json", "$", 0,
0).Err())
+ // json path no exists
+ require.NoError(t, rdb.Do(ctx, "JSON.SET", "a", "$",
`{"a1":{}}`).Err())
+ require.EqualValues(t, []interface{}{}, rdb.Do(ctx,
"JSON.ARRTRIM", "a", "$.not_exists", 0, 0).Val())
+ // json path not array
+ require.EqualValues(t, []interface{}{nil}, rdb.Do(ctx,
"JSON.ARRTRIM", "a", "$.a1", 0, 0).Val())
+ // json path has one array
+ require.NoError(t, rdb.Do(ctx, "JSON.SET", "a", "$",
`{"a1":[1,2,3,4,5,6,7,8,9]}`).Err())
+ require.EqualValues(t, []interface{}{int64(5)}, rdb.Do(ctx,
"JSON.ARRTRIM", "a", "$.a1", 2, 6).Val())
+ // json path has many array
+ require.NoError(t, rdb.Do(ctx, "JSON.SET", "a", "$",
`{"a":{"a1":[1,2,3,4,5,6]},"b":{"a1":["a",{},"b"]},"c":{"a1":[7,8,9,10,11]}}`).Err())
+ require.EqualValues(t, []interface{}([]interface{}{int64(3),
int64(2), int64(3)}), rdb.Do(ctx, "JSON.ARRTRIM", "a", "$..a1", 1, 3).Val())
+ require.EqualValues(t,
"[{\"a\":{\"a1\":[2,3,4]},\"b\":{\"a1\":[{},\"b\"]},\"c\":{\"a1\":[8,9,10]}}]",
rdb.Do(ctx, "JSON.GET", "a", "$").Val())
+ // json path has many array and one is not array
+ require.NoError(t, rdb.Do(ctx, "JSON.SET", "a", "$",
`{"a":{"a1":[1,2,3,4,5,6]},"b":{"a1":{"b":1,"c":1}},"c":{"a1":[7,8,9,10]}}`).Err())
+ require.EqualValues(t, []interface{}([]interface{}{int64(3),
interface{}(nil), int64(3)}), rdb.Do(ctx, "JSON.ARRTRIM", "a", "$..a1", 1,
3).Val())
+ require.EqualValues(t,
"[{\"a\":{\"a1\":[2,3,4]},\"b\":{\"a1\":{\"b\":1,\"c\":1}},\"c\":{\"a1\":[8,9,10]}}]",
rdb.Do(ctx, "JSON.GET", "a", "$").Val())
+ // start not a integer
+ require.Error(t, rdb.Do(ctx, "JSON.ARRTRIM", "a", "$.a1", "no",
1).Err())
+ require.Error(t, rdb.Do(ctx, "JSON.ARRTRIM", "a", "$.a1", 1.1,
1).Err())
+ // stop not a integer
+ require.Error(t, rdb.Do(ctx, "JSON.ARRTRIM", "a", "$.a1", 1,
1.1).Err())
+ // args size != 5
+ require.Error(t, rdb.Do(ctx, "JSON.ARRTRIM", "a", "$.a1",
0).Err())
+ require.Error(t, rdb.Do(ctx, "JSON.ARRTRIM", "a", "$.a1", 0, 2,
3).Err())
+ })
+
+ t.Run("JSON.ARRTRIM special <start> and <stop> args", func(t
*testing.T) {
+ // start < 0
+ require.NoError(t, rdb.Do(ctx, "JSON.SET", "a", "$",
`[1,2,3,4,5,6,7,8,9,10]`).Err())
+ require.EqualValues(t, []interface{}([]interface{}{int64(4)}),
rdb.Do(ctx, "JSON.ARRTRIM", "a", "$", -5, 8).Val())
+ require.EqualValues(t, "[[6,7,8,9]]", rdb.Do(ctx, "JSON.GET",
"a", "$").Val())
+ // start + len < 0
+ require.NoError(t, rdb.Do(ctx, "JSON.SET", "a", "$",
`[1,2,3,4,5,6,7,8,9,10]`).Err())
+ require.EqualValues(t, []interface{}([]interface{}{int64(6)}),
rdb.Do(ctx, "JSON.ARRTRIM", "a", "$", -20, 5).Val())
+ require.EqualValues(t, "[[1,2,3,4,5,6]]", rdb.Do(ctx,
"JSON.GET", "a", "$").Val())
+ // start > len
+ require.NoError(t, rdb.Do(ctx, "JSON.SET", "a", "$",
`[1,2,3,4,5,6,7,8,9,10]`).Err())
+ require.EqualValues(t, []interface{}([]interface{}{int64(0)}),
rdb.Do(ctx, "JSON.ARRTRIM", "a", "$", 15, 25).Val())
+ require.EqualValues(t, "[[]]", rdb.Do(ctx, "JSON.GET", "a",
"$").Val())
+ // start = 0
+ require.NoError(t, rdb.Do(ctx, "JSON.SET", "a", "$",
`[1,2,3,4,5,6,7,8,9,10]`).Err())
+ require.EqualValues(t, []interface{}([]interface{}{int64(9)}),
rdb.Do(ctx, "JSON.ARRTRIM", "a", "$", 0, 8).Val())
+ require.EqualValues(t, "[[1,2,3,4,5,6,7,8,9]]", rdb.Do(ctx,
"JSON.GET", "a", "$").Val())
+ // stop = 0
+ require.NoError(t, rdb.Do(ctx, "JSON.SET", "a", "$",
`[1,2,3,4,5,6,7,8,9,10]`).Err())
+ require.EqualValues(t, []interface{}([]interface{}{int64(1)}),
rdb.Do(ctx, "JSON.ARRTRIM", "a", "$", -12, 0).Val())
+ require.EqualValues(t, "[[1]]", rdb.Do(ctx, "JSON.GET", "a",
"$").Val())
+ // stop < 0
+ require.NoError(t, rdb.Do(ctx, "JSON.SET", "a", "$",
`[1,2,3,4,5,6,7,8,9,10]`).Err())
+ require.EqualValues(t, []interface{}([]interface{}{int64(9)}),
rdb.Do(ctx, "JSON.ARRTRIM", "a", "$", 0, -2).Val())
+ require.EqualValues(t, "[[1,2,3,4,5,6,7,8,9]]", rdb.Do(ctx,
"JSON.GET", "a", "$").Val())
+ // len + stop < 0
+ require.NoError(t, rdb.Do(ctx, "JSON.SET", "a", "$",
`[1,2,3,4,5,6,7,8,9,10]`).Err())
+ require.EqualValues(t, []interface{}([]interface{}{int64(1)}),
rdb.Do(ctx, "JSON.ARRTRIM", "a", "$", 0, -20).Val())
+ require.EqualValues(t, "[[1]]", rdb.Do(ctx, "JSON.GET", "a",
"$").Val())
+ // stop > len
+ require.NoError(t, rdb.Do(ctx, "JSON.SET", "a", "$",
`[1,2,3,4,5,6,7,8,9,10]`).Err())
+ require.EqualValues(t, []interface{}([]interface{}{int64(10)}),
rdb.Do(ctx, "JSON.ARRTRIM", "a", "$", 0, 20).Val())
+ require.EqualValues(t, "[[1,2,3,4,5,6,7,8,9,10]]", rdb.Do(ctx,
"JSON.GET", "a", "$").Val())
+ // start > stop
+ require.NoError(t, rdb.Do(ctx, "JSON.SET", "a", "$",
`[1,2,3,4,5,6,7,8,9,10]`).Err())
+ require.EqualValues(t, []interface{}([]interface{}{int64(0)}),
rdb.Do(ctx, "JSON.ARRTRIM", "a", "$", 8, 5).Val())
+ require.EqualValues(t, "[[]]", rdb.Do(ctx, "JSON.GET", "a",
"$").Val())
+ // start < 0 and stop < 0
+ require.NoError(t, rdb.Do(ctx, "JSON.SET", "a", "$",
`[1,2,3,4,5,6,7,8,9,10]`).Err())
+ require.EqualValues(t, []interface{}([]interface{}{int64(4)}),
rdb.Do(ctx, "JSON.ARRTRIM", "a", "$", -8, -5).Val())
+ require.EqualValues(t, "[[3,4,5,6]]", rdb.Do(ctx, "JSON.GET",
"a", "$").Val())
+ // start < 0 , stop < 0 and start > end
+ require.NoError(t, rdb.Do(ctx, "JSON.SET", "a", "$",
`[1,2,3,4,5,6,7,8,9,10]`).Err())
+ require.EqualValues(t, []interface{}([]interface{}{int64(0)}),
rdb.Do(ctx, "JSON.ARRTRIM", "a", "$", -5, -8).Val())
+ require.EqualValues(t, "[[]]", rdb.Do(ctx, "JSON.GET", "a",
"$").Val())
+ // start + len < 0 , stop + len < 0
+ require.NoError(t, rdb.Do(ctx, "JSON.SET", "a", "$",
`[1,2,3,4,5,6,7,8,9,10]`).Err())
+ require.EqualValues(t, []interface{}([]interface{}{int64(1)}),
rdb.Do(ctx, "JSON.ARRTRIM", "a", "$", -30, -20).Val())
+ require.EqualValues(t, "[[1]]", rdb.Do(ctx, "JSON.GET", "a",
"$").Val())
+ })
+
t.Run("JSON.TOGGLE basics", func(t *testing.T) {
require.NoError(t, rdb.Do(ctx, "JSON.SET", "a", "$",
`true`).Err())
require.EqualValues(t, []interface{}{int64(0)}, rdb.Do(ctx,
"JSON.TOGGLE", "a", "$").Val())