mirror of
https://github.com/dragonflydb/dragonfly.git
synced 2024-12-14 11:58:02 +00:00
fix(json): fix json dot path (#2186)
* fix(json): fix json dot path Signed-off-by: Vladislav Oleshko <vlad@dragonflydb.io> --------- Signed-off-by: Vladislav Oleshko <vlad@dragonflydb.io>
This commit is contained in:
parent
c4cae58fc8
commit
27dcec38ee
2 changed files with 59 additions and 67 deletions
|
@ -19,6 +19,7 @@ extern "C" {
|
|||
|
||||
#include "base/logging.h"
|
||||
#include "core/json_object.h"
|
||||
#include "facade/cmd_arg_parser.h"
|
||||
#include "server/acl/acl_commands_def.h"
|
||||
#include "server/command_registry.h"
|
||||
#include "server/error.h"
|
||||
|
@ -389,9 +390,10 @@ void SendJsonValue(ConnectionContext* cntx, const JsonType& j) {
|
|||
}
|
||||
}
|
||||
|
||||
OpResult<string> OpGet(const OpArgs& op_args, string_view key,
|
||||
vector<pair<string_view, JsonExpression>> expressions, bool should_format,
|
||||
const OptString& indent, const OptString& new_line, const OptString& space) {
|
||||
OpResult<string> OpJsonGet(const OpArgs& op_args, string_view key,
|
||||
const vector<pair<string_view, optional<JsonExpression>>>& expressions,
|
||||
bool should_format, const OptString& indent, const OptString& new_line,
|
||||
const OptString& space) {
|
||||
OpResult<JsonType*> result = GetJson(op_args, key);
|
||||
if (!result) {
|
||||
return result.status();
|
||||
|
@ -427,22 +429,16 @@ OpResult<string> OpGet(const OpArgs& op_args, string_view key,
|
|||
}
|
||||
}
|
||||
|
||||
if (expressions.size() == 1) {
|
||||
json out = expressions[0].second.evaluate(json_entry);
|
||||
if (should_format) {
|
||||
json_printable jp(out, options, indenting::indent);
|
||||
std::stringstream ss;
|
||||
jp.dump(ss);
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
return out.as<string>();
|
||||
}
|
||||
auto eval_wrapped = [&json_entry](const optional<JsonExpression>& expr) {
|
||||
return expr ? expr->evaluate(json_entry) : json_entry;
|
||||
};
|
||||
|
||||
json out;
|
||||
for (auto& expr : expressions) {
|
||||
json eval = expr.second.evaluate(json_entry);
|
||||
out[expr.first] = eval;
|
||||
if (expressions.size() == 1) {
|
||||
out = eval_wrapped(expressions[0].second);
|
||||
} else {
|
||||
for (auto& [expr_str, expr] : expressions)
|
||||
out[expr_str] = eval_wrapped(expr);
|
||||
}
|
||||
|
||||
if (should_format) {
|
||||
|
@ -1785,54 +1781,50 @@ void JsonFamily::StrLen(CmdArgList args, ConnectionContext* cntx) {
|
|||
|
||||
void JsonFamily::Get(CmdArgList args, ConnectionContext* cntx) {
|
||||
DCHECK_GE(args.size(), 1U);
|
||||
string_view key = ArgS(args, 0);
|
||||
|
||||
facade::CmdArgParser parser{args};
|
||||
string_view key = parser.Next();
|
||||
|
||||
OptString indent;
|
||||
OptString new_line;
|
||||
OptString space;
|
||||
vector<pair<string_view, JsonExpression>> expressions;
|
||||
for (size_t i = 1; i < args.size(); ++i) {
|
||||
string_view param = ArgS(args, i);
|
||||
if (absl::EqualsIgnoreCase(param, "space")) {
|
||||
if (++i >= args.size()) {
|
||||
return (*cntx)->SendError(facade::WrongNumArgsError(cntx->cid->name()),
|
||||
facade::kSyntaxErrType);
|
||||
} else {
|
||||
space = ArgS(args, i);
|
||||
continue;
|
||||
}
|
||||
} else if (absl::EqualsIgnoreCase(param, "newline")) {
|
||||
if (++i >= args.size()) {
|
||||
return (*cntx)->SendError(facade::WrongNumArgsError(cntx->cid->name()),
|
||||
facade::kSyntaxErrType);
|
||||
} else {
|
||||
new_line = ArgS(args, i);
|
||||
continue;
|
||||
}
|
||||
} else if (absl::EqualsIgnoreCase(param, "indent")) {
|
||||
if (++i >= args.size()) {
|
||||
return (*cntx)->SendError(facade::WrongNumArgsError(cntx->cid->name()),
|
||||
facade::kSyntaxErrType);
|
||||
} else {
|
||||
indent = ArgS(args, i);
|
||||
continue;
|
||||
vector<pair<string_view, optional<JsonExpression>>> expressions;
|
||||
|
||||
while (parser.HasNext()) {
|
||||
if (parser.Check("SPACE").IgnoreCase().ExpectTail(1)) {
|
||||
space = parser.Next();
|
||||
continue;
|
||||
}
|
||||
if (parser.Check("NEWLINE").IgnoreCase().ExpectTail(1)) {
|
||||
new_line = parser.Next();
|
||||
continue;
|
||||
}
|
||||
if (parser.Check("INDENT").IgnoreCase().ExpectTail(1)) {
|
||||
indent = parser.Next();
|
||||
continue;
|
||||
}
|
||||
|
||||
optional<JsonExpression> expr;
|
||||
string_view expr_str = parser.Next();
|
||||
|
||||
if (expr_str != ".") {
|
||||
error_code ec;
|
||||
expr = ParseJsonPath(expr_str, &ec);
|
||||
if (ec) {
|
||||
LOG(WARNING) << "path '" << expr_str << "': Invalid JSONPath syntax: " << ec.message();
|
||||
return (*cntx)->SendError(kSyntaxErr);
|
||||
}
|
||||
}
|
||||
|
||||
error_code ec;
|
||||
JsonExpression expr = ParseJsonPath(param, &ec);
|
||||
|
||||
if (ec) {
|
||||
LOG(WARNING) << "path '" << param << "': Invalid JSONPath syntax: " << ec.message();
|
||||
return (*cntx)->SendError(kSyntaxErr);
|
||||
}
|
||||
expressions.emplace_back(param, move(expr));
|
||||
expressions.emplace_back(expr_str, move(expr));
|
||||
}
|
||||
|
||||
if (auto err = parser.Error(); err)
|
||||
return (*cntx)->SendError(err->MakeReply());
|
||||
|
||||
bool should_format = (indent || new_line || space);
|
||||
auto cb = [&](Transaction* t, EngineShard* shard) {
|
||||
return OpGet(t->GetOpArgs(shard), key, move(expressions), should_format, indent, new_line,
|
||||
space);
|
||||
return OpJsonGet(t->GetOpArgs(shard), key, expressions, should_format, indent, new_line, space);
|
||||
};
|
||||
|
||||
Transaction* trans = cntx->transaction;
|
||||
|
@ -1842,8 +1834,7 @@ void JsonFamily::Get(CmdArgList args, ConnectionContext* cntx) {
|
|||
(*cntx)->SendBulkString(*result);
|
||||
} else {
|
||||
if (result == facade::OpStatus::KEY_NOTFOUND) {
|
||||
// Match what Redis returning
|
||||
(*cntx)->SendNull();
|
||||
(*cntx)->SendNull(); // Match Redis
|
||||
} else {
|
||||
(*cntx)->SendError(result.status());
|
||||
}
|
||||
|
|
|
@ -4,6 +4,10 @@
|
|||
|
||||
#include "server/json_family.h"
|
||||
|
||||
#include <absl/strings/str_replace.h>
|
||||
|
||||
#include <jsoncons/json.hpp>
|
||||
|
||||
#include "base/gtest.h"
|
||||
#include "base/logging.h"
|
||||
#include "facade/facade_test.h"
|
||||
|
@ -106,6 +110,14 @@ TEST_F(JsonFamilyTest, SetGetFromPhonebook) {
|
|||
auto resp = Run({"JSON.SET", "json", ".", PhonebookJson});
|
||||
ASSERT_THAT(resp, "OK");
|
||||
|
||||
auto compact_json = jsoncons::json::parse(PhonebookJson).as_string();
|
||||
|
||||
resp = Run({"JSON.GET", "json", "."});
|
||||
EXPECT_EQ(resp, compact_json);
|
||||
|
||||
resp = Run({"JSON.GET", "json", "$"});
|
||||
EXPECT_EQ(resp, "[" + compact_json + "]");
|
||||
|
||||
resp = Run({"JSON.GET", "json", "$.address.*"});
|
||||
EXPECT_EQ(resp, R"(["New York","NY","21 2nd Street","10021-3100"])");
|
||||
|
||||
|
@ -1038,15 +1050,4 @@ TEST_F(JsonFamilyTest, Set) {
|
|||
EXPECT_EQ(resp, R"([{"a":2,"b":8,"c":[1,2,3]}])");
|
||||
}
|
||||
|
||||
TEST_F(JsonFamilyTest, LegacyV1) {
|
||||
string json = R"({"key":[1,2,3,4]})";
|
||||
|
||||
auto resp = Run({"JSON.SET", "json1", ".", json});
|
||||
EXPECT_THAT(resp, "OK");
|
||||
|
||||
// JSON.GET key "." is the same as JSON.GET key "$"
|
||||
resp = Run({"JSON.GET", "json1", "."});
|
||||
EXPECT_THAT(resp, absl::StrCat("[", json, "]"));
|
||||
}
|
||||
|
||||
} // namespace dfly
|
||||
|
|
Loading…
Reference in a new issue