1
0
Fork 0
mirror of https://github.com/dragonflydb/dragonfly.git synced 2024-12-15 17:51:06 +00:00

feat(server): implement json.debug command (#104) (#463)

Signed-off-by: iko1 <me@remotecpp.dev>
This commit is contained in:
iko1 2022-11-09 22:49:59 +02:00 committed by GitHub
parent 214c10f165
commit 4567925440
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 128 additions and 0 deletions

View file

@ -8,6 +8,7 @@ extern "C" {
#include "redis/object.h"
}
#include <absl/strings/match.h>
#include <absl/strings/str_join.h>
#include <jsoncons/json.hpp>
@ -286,6 +287,33 @@ string ConvertToJsonPointer(string_view json_path) {
return result;
}
size_t CountJsonFields(const json& j) {
size_t res = 0;
json_type type = j.type();
if (type == json_type::array_value) {
res += j.size();
for (const auto& item : j.array_range()) {
if (item.type() == json_type::array_value || item.type() == json_type::object_value) {
res += CountJsonFields(item);
}
}
} else if (type == json_type::object_value) {
res += j.size();
for (const auto& item : j.object_range()) {
if (item.value().type() == json_type::array_value ||
item.value().type() == json_type::object_value) {
res += CountJsonFields(item.value());
}
}
} else {
res += 1;
}
return res;
}
OpResult<string> OpGet(const OpArgs& op_args, string_view key,
vector<pair<string_view, JsonExpression>> expressions) {
OpResult<json> result = GetJson(op_args, key);
@ -905,8 +933,75 @@ OpResult<vector<OptString>> OpMGet(const OpArgs& op_args, const vector<string_vi
return vec;
}
// Returns numeric vector that represents the number of fields of JSON value at each path.
OpResult<vector<OptSizeT>> OpFields(const OpArgs& op_args, string_view key,
JsonExpression expression) {
OpResult<json> result = GetJson(op_args, key);
if (!result) {
return result.status();
}
vector<OptSizeT> vec;
auto cb = [&vec](const string_view& path, const json& val) {
vec.emplace_back(CountJsonFields(val));
};
expression.evaluate(*result, cb);
return vec;
}
} // namespace
void JsonFamily::Debug(CmdArgList args, ConnectionContext* cntx) {
function<decltype(OpFields)> func;
string_view command = ArgS(args, 1);
// The 'MEMORY' sub-command is not supported yet, calling to operation function should be added
// here.
if (absl::EqualsIgnoreCase(command, "help")) {
(*cntx)->StartArray(2);
(*cntx)->SendSimpleString(
"JSON.DEBUG FIELDS <key> <path> - report number of fields in the JSON element.");
(*cntx)->SendSimpleString("JSON.DEBUG HELP - print help message.");
return;
} else if (absl::EqualsIgnoreCase(command, "fields")) {
func = &OpFields;
} else {
(*cntx)->SendError(facade::UnknownSubCmd(command, "JSON.DEBUG"), facade::kSyntaxErrType);
return;
}
if (args.size() < 4) {
(*cntx)->SendError(facade::WrongNumArgsError(command), facade::kSyntaxErrType);
return;
}
error_code ec;
string_view key = ArgS(args, 2);
string_view path = ArgS(args, 3);
JsonExpression expression = jsonpath::make_expression<json>(path, ec);
if (ec) {
VLOG(1) << "Invalid JSONPath syntax: " << ec.message();
(*cntx)->SendError(kSyntaxErr);
return;
}
auto cb = [&](Transaction* t, EngineShard* shard) {
return func(t->GetOpArgs(shard), key, move(expression));
};
Transaction* trans = cntx->transaction;
OpResult<vector<OptSizeT>> result = trans->ScheduleSingleHopT(move(cb));
if (result) {
PrintOptVec(cntx, result);
} else {
(*cntx)->SendError(result.status());
}
}
void JsonFamily::MGet(CmdArgList args, ConnectionContext* cntx) {
error_code ec;
string_view path = ArgS(args, args.size() - 1);
@ -1482,6 +1577,7 @@ void JsonFamily::Register(CommandRegistry* registry) {
*registry << CI{"JSON.ARRAPPEND", CO::WRITE | CO::DENYOOM | CO::FAST, -4, 1, 1, 1}.HFUNC(
ArrAppend);
*registry << CI{"JSON.ARRINDEX", CO::READONLY | CO::FAST, -4, 1, 1, 1}.HFUNC(ArrIndex);
*registry << CI{"JSON.DEBUG", CO::READONLY | CO::FAST, -2, 1, 1, 1}.HFUNC(Debug);
}
} // namespace dfly

View file

@ -38,6 +38,7 @@ class JsonFamily {
static void ArrInsert(CmdArgList args, ConnectionContext* cntx);
static void ArrAppend(CmdArgList args, ConnectionContext* cntx);
static void ArrIndex(CmdArgList args, ConnectionContext* cntx);
static void Debug(CmdArgList args, ConnectionContext* cntx);
};
} // namespace dfly

View file

@ -861,4 +861,35 @@ TEST_F(JsonFamilyTest, MGet) {
EXPECT_THAT(resp.GetVec(), ElementsAre(R"("Israel")", R"("Germany")", ArgType(RespExpr::NIL)));
}
TEST_F(JsonFamilyTest, DebugFields) {
string json = R"(
[1, 2.3, "foo", true, null, {}, [], {"a":1, "b":2}, [1,2,3]]
)";
auto resp = Run({"set", "json1", json});
ASSERT_THAT(resp, "OK");
resp = Run({"JSON.DEBUG", "fields", "json1", "$[*]"});
ASSERT_EQ(RespExpr::ARRAY, resp.type);
EXPECT_THAT(resp.GetVec(), ElementsAre(IntArg(1), IntArg(1), IntArg(1), IntArg(1), IntArg(1),
IntArg(0), IntArg(0), IntArg(2), IntArg(3)));
resp = Run({"JSON.DEBUG", "fields", "json1", "$"});
EXPECT_THAT(resp, IntArg(14));
json = R"(
[[1,2,3, [4,5,6,[6,7,8]]], {"a": {"b": {"c": 1337}}}]
)";
resp = Run({"set", "json1", json});
ASSERT_THAT(resp, "OK");
resp = Run({"JSON.DEBUG", "fields", "json1", "$[*]"});
ASSERT_EQ(RespExpr::ARRAY, resp.type);
EXPECT_THAT(resp.GetVec(), ElementsAre(IntArg(11), IntArg(3)));
resp = Run({"JSON.DEBUG", "fields", "json1", "$"});
EXPECT_THAT(resp, IntArg(16));
}
} // namespace dfly