This is an automated email from the ASF dual-hosted git repository.
twice 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 3557ad3f feat(json): add support of the command JSON.DEBUG MEMORY
(#2323)
3557ad3f is described below
commit 3557ad3f9f862ea421f5a94ec6d026b3517487e0
Author: lizhenglei <[email protected]>
AuthorDate: Fri May 31 22:24:54 2024 +0800
feat(json): add support of the command JSON.DEBUG MEMORY (#2323)
Co-authored-by: 80597928 <[email protected]>
Co-authored-by: Twice <[email protected]>
---
src/commands/cmd_json.cc | 45 +++++++++++++++++++++++++++++++-
src/types/json.h | 24 +++++++++++++++++
src/types/redis_json.cc | 20 ++++++++++++++
src/types/redis_json.h | 1 +
tests/gocase/unit/type/json/json_test.go | 23 ++++++++++++++++
5 files changed, 112 insertions(+), 1 deletion(-)
diff --git a/src/commands/cmd_json.cc b/src/commands/cmd_json.cc
index 887cd6c6..8f788953 100644
--- a/src/commands/cmd_json.cc
+++ b/src/commands/cmd_json.cc
@@ -45,6 +45,14 @@ std::string OptionalsToString(const Connection *conn,
Optionals<T> &opts) {
return str;
}
+std::string SizeToString(const std::vector<std::size_t> &elems) {
+ std::string result = MultiLen(elems.size());
+ for (const auto &elem : elems) {
+ result += redis::Integer(elem);
+ }
+ return result;
+}
+
class CommandJsonSet : public Commander {
public:
Status Execute(Server *srv, Connection *conn, std::string *output) override {
@@ -632,6 +640,40 @@ class CommandJsonMSet : public Commander {
}
};
+class CommandJsonDebug : public Commander {
+ public:
+ Status Execute(Server *svr, Connection *conn, std::string *output) override {
+ redis::Json json(svr->storage, conn->GetNamespace());
+
+ std::string path = "$";
+
+ if (!util::EqualICase(args_[1], "memory")) {
+ return {Status::RedisExecErr, "ERR wrong number of arguments for
'json.debug' command"};
+ }
+
+ if (args_.size() == 4) {
+ path = args_[3];
+ } else if (args_.size() > 4) {
+ return {Status::RedisExecErr, "The number of arguments is more than
expected"};
+ }
+
+ std::vector<std::size_t> results;
+ auto s = json.DebugMemory(args_[2], path, &results);
+
+ if (s.IsNotFound()) {
+ if (args_.size() == 3) {
+ *output = redis::Integer(0);
+ } else {
+ *output = SizeToString(results);
+ }
+ return Status::OK();
+ }
+ if (!s.ok()) return {Status::RedisExecErr, s.ToString()};
+
+ *output = SizeToString(results);
+ 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),
@@ -655,6 +697,7 @@
REDIS_REGISTER_COMMANDS(MakeCmdAttr<CommandJsonSet>("json.set", 4, "write", 1, 1
MakeCmdAttr<CommandJsonStrAppend>("json.strappend",
-3, "write", 1, 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<CommandJsonMSet>("json.mset", -4, "write",
1, -3, 3),
+ MakeCmdAttr<CommandJsonDebug>("json.debug", -3,
"read-only", 2, 2, 1));
} // namespace redis
diff --git a/src/types/json.h b/src/types/json.h
index 2f5c0c45..fe651ccc 100644
--- a/src/types/json.h
+++ b/src/types/json.h
@@ -217,6 +217,30 @@ struct JsonValue {
return results;
}
+ StatusOr<std::vector<size_t>> GetBytes(std::string_view path,
JsonStorageFormat format,
+ int max_nesting_depth =
std::numeric_limits<int>::max()) const {
+ std::vector<size_t> results;
+ Status s;
+ try {
+ jsoncons::jsonpath::json_query(value, path, [&](const std::string &
/*path*/, const jsoncons::json &origin) {
+ if (!s) return;
+ std::string buffer;
+ JsonValue query_value(origin);
+ if (format == JsonStorageFormat::JSON) {
+ s = query_value.Dump(&buffer, max_nesting_depth);
+ } else if (format == JsonStorageFormat::CBOR) {
+ s = query_value.DumpCBOR(&buffer, max_nesting_depth);
+ }
+ results.emplace_back(buffer.size());
+ });
+ } catch (const jsoncons::jsonpath::jsonpath_error &e) {
+ return {Status::NotOK, e.what()};
+ }
+ if (!s) return {Status::NotOK, s.Msg()};
+
+ return results;
+ }
+
StatusOr<JsonValue> Get(std::string_view path) const {
try {
return jsoncons::jsonpath::json_query(value, path);
diff --git a/src/types/redis_json.cc b/src/types/redis_json.cc
index ca879981..9929b22c 100644
--- a/src/types/redis_json.cc
+++ b/src/types/redis_json.cc
@@ -627,4 +627,24 @@ std::vector<rocksdb::Status> Json::readMulti(const
std::vector<Slice> &ns_keys,
return statuses;
}
+rocksdb::Status Json::DebugMemory(const std::string &user_key, const
std::string &path, std::vector<size_t> *results) {
+ auto ns_key = AppendNamespacePrefix(user_key);
+ JsonMetadata metadata;
+ if (path == "$") {
+ std::string bytes;
+ Slice rest;
+ auto s = GetMetadata(GetOptions{}, {kRedisJson}, ns_key, &bytes,
&metadata, &rest);
+ if (!s.ok()) return s;
+ results->emplace_back(rest.size());
+ } else {
+ JsonValue json_val;
+ auto s = read(ns_key, &metadata, &json_val);
+ if (!s.ok()) return s;
+ auto str_bytes = json_val.GetBytes(path, metadata.format,
storage_->GetConfig()->json_max_nesting_depth);
+ if (!str_bytes) return rocksdb::Status::InvalidArgument(str_bytes.Msg());
+ *results = std::move(*str_bytes);
+ }
+ return rocksdb::Status::OK();
+}
+
} // namespace redis
diff --git a/src/types/redis_json.h b/src/types/redis_json.h
index f72b2c00..8dc21235 100644
--- a/src/types/redis_json.h
+++ b/src/types/redis_json.h
@@ -68,6 +68,7 @@ class Json : public Database {
std::vector<JsonValue> &results);
rocksdb::Status MSet(const std::vector<std::string> &user_keys, const
std::vector<std::string> &paths,
const std::vector<std::string> &values);
+ rocksdb::Status DebugMemory(const std::string &user_key, const std::string
&path, std::vector<size_t> *results);
private:
rocksdb::Status write(Slice ns_key, JsonMetadata *metadata, const JsonValue
&json_val);
diff --git a/tests/gocase/unit/type/json/json_test.go
b/tests/gocase/unit/type/json/json_test.go
index 0f2bbf8f..e609d14f 100644
--- a/tests/gocase/unit/type/json/json_test.go
+++ b/tests/gocase/unit/type/json/json_test.go
@@ -631,6 +631,29 @@ func TestJson(t *testing.T) {
EqualJSON(t, `[{"a": 4, "b": 5, "nested": {"a": 6}, "c":
null}]`, rdb.Do(ctx, "JSON.GET", "a1", "$").Val())
EqualJSON(t, `[4]`, rdb.Do(ctx, "JSON.GET", "a1", "$.a").Val())
})
+
+ t.Run("JSON.DEBUG MEMORY basics", func(t *testing.T) {
+ require.NoError(t, rdb.Do(ctx, "JSON.SET", "a", "$",
`{"b":true,"x":1, "y":1.2, "z": {"x":[1,2,3], "y": null},
"v":{"x":"y"},"f":{"x":[]}}`).Err())
+ //object
+ var result1 = make([]interface{}, 0)
+ result1 = append(result1, int64(43))
+ require.Equal(t, result1, rdb.Do(ctx, "JSON.DEBUG", "MEMORY",
"a", "$").Val())
+ //integer string array empty_array
+ var result2 = make([]interface{}, 0)
+ result2 = append(result2, int64(1), int64(1), int64(2),
int64(4))
+ require.Equal(t, result2, rdb.Do(ctx, "JSON.DEBUG", "MEMORY",
"a", "$..x").Val())
+ //null object
+ var result3 = make([]interface{}, 0)
+ result3 = append(result3, int64(9), int64(1))
+ require.Equal(t, result3, rdb.Do(ctx, "JSON.DEBUG", "MEMORY",
"a", "$..y").Val())
+ //no no_exists
+ require.Equal(t, []interface{}{}, rdb.Do(ctx, "JSON.DEBUG",
"MEMORY", "a", "$..no_exists").Val())
+ //no key no path
+ require.Equal(t, rdb.Do(ctx, "JSON.DEBUG", "MEMORY",
"not_exists").Val(), int64(0))
+ //no key have path
+ require.Equal(t, []interface{}{}, rdb.Do(ctx, "JSON.DEBUG",
"MEMORY", "not_exists", "$").Val())
+
+ })
}
func EqualJSON(t *testing.T, expected string, actual interface{}) {