fix(json_family): Fix the JSON.SET bug if the path is in legacy mode and is not the root (#3693)

Signed-off-by: Stepan Bagritsevich <stefan@dragonflydb.io>
This commit is contained in:
Stepan Bagritsevich 2024-09-10 15:50:32 +02:00 committed by GitHub
parent 9b8aa8eab4
commit 3ece1725a1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 49 additions and 17 deletions

View file

@ -391,14 +391,21 @@ string ConvertToJsonPointer(string_view json_path) {
return result;
}
string ConvertExpressionToJsonPointer(string_view json_path) {
if (json_path.empty() || !absl::StartsWith(json_path, "$.")) {
std::optional<std::string> ConvertExpressionToJsonPointer(string_view json_path) {
if (json_path.empty()) {
VLOG(1) << "retrieved malformed JSON path expression: " << json_path;
return {};
return std::nullopt;
}
// remove prefix
json_path.remove_prefix(2);
// Remove prefix
if (json_path.front() == '$') {
json_path.remove_prefix(1);
}
if (json_path.front() == '.') {
json_path.remove_prefix(1);
}
DCHECK(json_path.length());
std::string pointer;
vector<string> splitted = absl::StrSplit(json_path, '.');
@ -406,12 +413,12 @@ string ConvertExpressionToJsonPointer(string_view json_path) {
if (it.front() == '[' && it.back() == ']') {
std::string index = it.substr(1, it.size() - 2);
if (index.empty()) {
return {};
return std::nullopt;
}
for (char ch : index) {
if (!std::isdigit(ch)) {
return {};
return std::nullopt;
}
}
@ -1186,17 +1193,17 @@ OpResult<bool> OpSet(const OpArgs& op_args, string_view key, string_view path,
};
auto inserter = [&](JsonType& json) {
// Set a new value if the path doesn't exist and the nx condition is not set.
// Set a new value if the path doesn't exist and the xx condition is not set.
if (!path_exists && !is_xx_condition) {
string pointer = ConvertExpressionToJsonPointer(path);
if (pointer.empty()) {
auto pointer = ConvertExpressionToJsonPointer(path);
if (!pointer) {
VLOG(1) << "Failed to convert the following expression path to a valid JSON pointer: "
<< path;
return OpStatus::SYNTAX_ERR;
}
error_code ec;
jsoncons::jsonpointer::add(json, pointer, new_json, ec);
jsoncons::jsonpointer::add(json, pointer.value(), new_json, ec);
if (ec) {
VLOG(1) << "Failed to add a JSON value to the following path: " << path
<< " with the error: " << ec.message();

View file

@ -2402,23 +2402,48 @@ TEST_F(JsonFamilyTest, SetLegacy) {
{"a": 2}
)";
resp = Run({"JSON.SET", "json3", "$", json});
resp = Run({"JSON.SET", "json3", ".", json});
EXPECT_THAT(resp, "OK");
resp = Run({"JSON.SET", "json3", "$.b", "8"});
resp = Run({"JSON.SET", "json3", ".b", "8"});
EXPECT_THAT(resp, "OK");
resp = Run({"JSON.SET", "json3", "$.c", "[1,2,3]"});
resp = Run({"JSON.SET", "json3", ".c", "[1,2,3]"});
EXPECT_THAT(resp, "OK");
resp = Run({"JSON.SET", "json3", "$.z", "3", "XX"});
resp = Run({"JSON.SET", "json3", ".z", "3", "XX"});
EXPECT_THAT(resp, ArgType(RespExpr::NIL));
resp = Run({"JSON.SET", "json3", "$.b", "4", "NX"});
resp = Run({"JSON.SET", "json3", ".z", "3"});
EXPECT_THAT(resp, "OK");
resp = Run({"JSON.SET", "json3", ".z", "4", "XX"});
EXPECT_THAT(resp, "OK");
resp = Run({"JSON.SET", "json3", ".b", "4", "NX"});
EXPECT_THAT(resp, ArgType(RespExpr::NIL));
resp = Run({"JSON.SET", "json3", ".b", "5"});
EXPECT_THAT(resp, "OK");
resp = Run({"JSON.SET", "json3", ".", "[]", "NX"});
EXPECT_THAT(resp, ArgType(RespExpr::NIL));
resp = Run({"JSON.GET", "json3"});
EXPECT_EQ(resp, R"({"a":2,"b":8,"c":[1,2,3]})");
EXPECT_EQ(resp, R"({"a":2,"b":5,"c":[1,2,3],"z":4})");
json = R"(
{"foo": "bar"}
)";
resp = Run({"JSON.SET", "json4", ".", json});
EXPECT_THAT(resp, "OK");
resp = Run({"JSON.SET", "json4", "foo", "\"baz\"", "XX"});
EXPECT_THAT(resp, "OK");
resp = Run({"JSON.SET", "json4", "foo2", "\"qaz\"", "NX"});
EXPECT_THAT(resp, "OK");
}
TEST_F(JsonFamilyTest, MSet) {