fix(json_family): fix JSON.STRAPPEND command for JSON legacy mode (#3264)

* fix(json_family): fix JSON.STRAPPEND command for JSON legacy mode

Signed-off-by: Stepan Bagritsevich <bagr.stepan@gmail.com>

* fix(json_family): add tests

Signed-off-by: Stepan Bagritsevich <bagr.stepan@gmail.com>

* refactor(json_family): address comments

Signed-off-by: Stepan Bagritsevich <bagr.stepan@gmail.com>

* refactor(json_family): code clean up

Signed-off-by: Stepan Bagritsevich <bagr.stepan@gmail.com>

* refactor(json_family): address comments 2

Signed-off-by: Stepan Bagritsevich <bagr.stepan@gmail.com>

* refactor(json_family_test): remove map_single_element_vector_ flag

Signed-off-by: Stepan Bagritsevich <bagr.stepan@gmail.com>

* fix(json_family_test): add more tests

Signed-off-by: Stepan Bagritsevich <bagr.stepan@gmail.com>

---------

Signed-off-by: Stepan Bagritsevich <bagr.stepan@gmail.com>
This commit is contained in:
Stepan Bagritsevich 2024-07-18 09:30:27 +04:00 committed by GitHub
parent 1acc824eff
commit d51fea09e2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 456 additions and 54 deletions

View file

@ -11,36 +11,92 @@
#include "core/json/json_object.h"
#include "core/json/path.h"
#include "core/string_or_view.h"
#include "facade/op_status.h"
#include "glog/logging.h"
namespace dfly {
using facade::OpResult;
using facade::OpStatus;
using Nothing = std::monostate;
using JsonExpression = jsoncons::jsonpath::jsonpath_expression<JsonType>;
template <typename T>
using JsonPathEvaluateCallback = absl::FunctionRef<T(std::string_view, const JsonType&)>;
template <typename T = Nothing> class MutateCallbackResult {
public:
MutateCallbackResult() = default;
explicit MutateCallbackResult(bool should_be_deleted) : should_be_deleted_(should_be_deleted_) {
}
MutateCallbackResult(bool should_be_deleted, T&& value)
: should_be_deleted_(should_be_deleted), value_(std::forward<T>(value)) {
}
bool HasValue() const {
return value_.has_value();
}
T&& GetValue() && {
return std::move(value_).value();
}
bool ShouldBeDeleted() const {
return should_be_deleted_;
}
private:
bool should_be_deleted_;
std::optional<T> value_;
};
template <typename T>
using JsonPathMutateCallback =
absl::FunctionRef<MutateCallbackResult<T>(std::optional<std::string_view>, JsonType*)>;
namespace details {
template <typename T> void OptionalEmplace(T value, std::optional<T>* optional) {
optional->emplace(std::move(value));
}
template <typename T>
void OptionalEmplace(std::optional<T> value, std::optional<std::optional<T>>* optional) {
if (value.has_value()) {
optional->emplace(std::move(value));
}
}
} // namespace details
template <typename T> class JsonCallbackResult {
public:
/* In the case of a restricted path (legacy mode), the result consists of a single value */
using JsonV1Result = std::optional<T>;
/* In the case of an enhanced path (starts with $), the result is an array of multiple values */
using JsonV2Result = std::vector<T>;
explicit JsonCallbackResult(bool legacy_mode_is_enabled)
: legacy_mode_is_enabled_(legacy_mode_is_enabled) {
if (!legacy_mode_is_enabled_) {
JsonCallbackResult() = default;
explicit JsonCallbackResult(bool legacy_mode_is_enabled) {
if (!legacy_mode_is_enabled) {
result_ = JsonV2Result{};
}
}
void AddValue(T&& value) {
void AddValue(T value) {
if (IsV1()) {
AsV1().emplace(std::forward<T>(value));
details::OptionalEmplace(std::move(value), &AsV1());
} else {
AsV2().emplace_back(std::forward<T>(value));
AsV2().emplace_back(std::move(value));
}
}
bool IsV1() const {
return legacy_mode_is_enabled_;
return std::holds_alternative<JsonV1Result>(result_);
}
JsonV1Result& AsV1() {
@ -51,9 +107,16 @@ template <typename T> class JsonCallbackResult {
return std::get<JsonV2Result>(result_);
}
const JsonV1Result& AsV1() const {
return std::get<JsonV1Result>(result_);
}
const JsonV2Result& AsV2() const {
return std::get<JsonV2Result>(result_);
}
private:
std::variant<JsonV1Result, JsonV2Result> result_;
bool legacy_mode_is_enabled_;
};
class WrappedJsonPath {
@ -102,6 +165,54 @@ class WrappedJsonPath {
return eval_result;
}
template <typename T>
OpResult<JsonCallbackResult<T>> Mutate(JsonType* json_entry, JsonPathMutateCallback<T> cb) const {
JsonCallbackResult<T> mutate_result{IsLegacyModePath()};
auto mutate_callback = [&cb, &mutate_result](std::optional<std::string_view> path,
JsonType* val) -> bool {
auto res = cb(path, val);
if (res.HasValue()) {
mutate_result.AddValue(std::move(res).GetValue());
}
return res.ShouldBeDeleted();
};
if (HoldsJsonPath()) {
const auto& json_path = AsJsonPath();
json::MutatePath(json_path, mutate_callback, json_entry);
} else {
using namespace jsoncons::jsonpath;
using namespace jsoncons::jsonpath::detail;
using Evaluator = jsonpath_evaluator<JsonType, JsonType&>;
using ValueType = Evaluator::value_type;
using Reference = Evaluator::reference;
using JsonSelector = Evaluator::path_expression_type;
custom_functions<JsonType> funcs = custom_functions<JsonType>();
std::error_code ec;
static_resources<ValueType, Reference> static_resources(funcs);
Evaluator e;
JsonSelector expr = e.compile(static_resources, path_.view(), ec);
if (ec) {
VLOG(1) << "Failed to mutate json with error: " << ec.message();
return OpStatus::SYNTAX_ERR;
}
dynamic_resources<ValueType, Reference> resources;
auto f = [&mutate_callback](const basic_path_node<char>& path, JsonType& val) {
mutate_callback(to_string(path), &val);
};
expr.evaluate(resources, *json_entry, JsonSelector::path_node_type{}, *json_entry,
std::move(f), result_options::nodups | result_options::path);
}
return mutate_result;
}
bool IsLegacyModePath() const {
return is_legacy_mode_path_;
}

View file

@ -106,6 +106,53 @@ ParseResult<WrappedJsonPath> ParseJsonPath(std::string_view path) {
} // namespace json_parser
namespace reply_generic {
void Send(std::size_t value, RedisReplyBuilder* rb) {
rb->SendLong(value);
}
template <typename T> void Send(const std::optional<T>& opt, RedisReplyBuilder* rb) {
if (opt.has_value()) {
Send(opt.value(), rb);
} else {
rb->SendNull();
}
}
template <typename T> void Send(const std::vector<T>& vec, RedisReplyBuilder* rb) {
if (vec.empty()) {
rb->SendNullArray();
} else {
rb->StartArray(vec.size());
for (auto&& x : vec) {
Send(x, rb);
}
}
}
template <typename T> void Send(const JsonCallbackResult<T>& result, RedisReplyBuilder* rb) {
if (result.IsV1()) {
/* The specified path was restricted (JSON legacy mode), then the result consists only of a
* single value */
Send(result.AsV1(), rb);
} else {
/* The specified path was enhanced (starts with '$'), then the result is an array of multiple
* values */
Send(result.AsV2(), rb);
}
}
template <typename T> void Send(const OpResult<T>& result, RedisReplyBuilder* rb) {
if (result) {
Send(result.value(), rb);
} else {
rb->SendError(result.status());
}
}
} // namespace reply_generic
using JsonPathV2 = variant<json::Path, JsonExpression>;
using ExprCallback = absl::FunctionRef<void(string_view, const JsonType&)>;
@ -241,6 +288,35 @@ error_code JsonReplace(JsonType& instance, string_view path, json::MutateCallbac
return ec;
}
template <typename T>
OpResult<JsonCallbackResult<T>> UpdateEntry(const OpArgs& op_args, std::string_view key,
const WrappedJsonPath& json_path,
JsonPathMutateCallback<T> cb,
JsonReplaceVerify verify_op = {}) {
auto it_res = op_args.GetDbSlice().FindMutable(op_args.db_cntx, key, OBJ_JSON);
RETURN_ON_BAD_STATUS(it_res);
PrimeValue& pv = it_res->it->second;
JsonType* json_val = pv.GetJson();
DCHECK(json_val) << "should have a valid JSON object for key '" << key << "' the type for it is '"
<< pv.ObjType() << "'";
op_args.shard->search_indices()->RemoveDoc(key, op_args.db_cntx, pv);
auto mutate_res = json_path.Mutate(json_val, cb);
// Make sure that we don't have other internal issue with the operation
if (mutate_res && verify_op) {
verify_op(*json_val);
}
it_res->post_updater.Run();
op_args.shard->search_indices()->AddDoc(key, op_args.db_cntx, pv);
return mutate_res;
}
// jsoncons version
OpStatus UpdateEntry(const OpArgs& op_args, std::string_view key, std::string_view path,
json::MutateCallback callback, JsonReplaceVerify verify_op = {}) {
@ -837,12 +913,9 @@ OpResult<vector<StringVec>> OpObjKeys(const OpArgs& op_args, string_view key,
return vec;
}
// Retruns array of string lengths after a successful operation.
OpResult<vector<OptSizeT>> OpStrAppend(const OpArgs& op_args, string_view key, string_view path,
JsonPathV2 expression, facade::ArgRange strs) {
vector<OptSizeT> vec;
OpStatus status;
auto cb = [&](const auto&, JsonType* val) {
auto OpStrAppend(const OpArgs& op_args, string_view key, const WrappedJsonPath& path,
facade::ArgRange strs) {
auto cb = [&](const auto&, JsonType* val) -> MutateCallbackResult<std::optional<std::size_t>> {
if (val->is_string()) {
string new_val = val->as_string();
for (string_view str : strs) {
@ -850,24 +923,13 @@ OpResult<vector<OptSizeT>> OpStrAppend(const OpArgs& op_args, string_view key, s
}
*val = new_val;
vec.emplace_back(new_val.size());
} else {
vec.emplace_back(nullopt);
return {false, new_val.size()};
}
return false;
return {false, std::nullopt};
};
if (holds_alternative<json::Path>(expression)) {
const json::Path& json_path = std::get<json::Path>(expression);
status = UpdateEntry(op_args, key, json_path, cb);
} else {
status = UpdateEntry(op_args, key, path, cb);
}
if (status != OpStatus::OK) {
return status;
}
return vec;
return UpdateEntry<std::optional<std::size_t>>(op_args, key, path, std::move(cb));
}
// Returns the numbers of values cleared.
@ -1891,22 +1953,17 @@ void JsonFamily::StrAppend(CmdArgList args, ConnectionContext* cntx) {
string_view key = ArgS(args, 0);
string_view path = ArgS(args, 1);
JsonPathV2 expression = PARSE_PATHV2(path);
WrappedJsonPath json_path = GET_OR_SEND_UNEXPECTED(json_parser::ParseJsonPath(path));
auto strs = args.subspan(2);
auto cb = [&](Transaction* t, EngineShard* shard) {
return OpStrAppend(t->GetOpArgs(shard), key, path, std::move(expression),
facade::ArgRange{strs});
return OpStrAppend(t->GetOpArgs(shard), key, json_path, facade::ArgRange{strs});
};
Transaction* trans = cntx->transaction;
OpResult<vector<OptSizeT>> result = trans->ScheduleSingleHopT(std::move(cb));
if (result) {
PrintOptVec(cntx, result);
} else {
cntx->SendError(result.status());
}
auto result = trans->ScheduleSingleHopT(std::move(cb));
auto* rb = static_cast<RedisReplyBuilder*>(cntx->reply_builder());
reply_generic::Send(result, rb);
}
void JsonFamily::ObjKeys(CmdArgList args, ConnectionContext* cntx) {

View file

@ -675,6 +675,8 @@ TEST_F(JsonFamilyTest, StrAppend) {
auto resp = Run({"JSON.SET", "json", ".", json});
ASSERT_THAT(resp, "OK");
/* Test simple response from only one value */
resp = Run({"JSON.STRAPPEND", "json", "$.a.a", "a", "b"});
EXPECT_THAT(resp, IntArg(3));
@ -691,26 +693,31 @@ TEST_F(JsonFamilyTest, StrAppend) {
resp,
R"({"a":{"a":"aaba"},"b":{"a":"a","b":1},"c":{"a":"a","b":"bb"},"d":{"a":1,"b":"b","c":3}})");
resp = Run({"JSON.STRAPPEND", "json", "$.c.b", "a"});
EXPECT_THAT(resp, IntArg(3));
resp = Run({"JSON.GET", "json"});
EXPECT_EQ(
resp,
R"({"a":{"a":"aaba"},"b":{"a":"a","b":1},"c":{"a":"a","b":"bba"},"d":{"a":1,"b":"b","c":3}})");
/*
Test response from several possible values
In JSON V2, the response is an array of all possible values
*/
resp = Run({"JSON.STRAPPEND", "json", "$.b.*", "a"});
ASSERT_EQ(RespExpr::ARRAY, resp.type);
ASSERT_THAT(resp, ArgType(RespExpr::ARRAY));
EXPECT_THAT(resp.GetVec(), ElementsAre(IntArg(2), ArgType(RespExpr::NIL)));
resp = Run({"JSON.GET", "json"});
EXPECT_EQ(
resp,
R"({"a":{"a":"aaba"},"b":{"a":"aa","b":1},"c":{"a":"a","b":"bb"},"d":{"a":1,"b":"b","c":3}})");
R"({"a":{"a":"aaba"},"b":{"a":"aa","b":1},"c":{"a":"a","b":"bba"},"d":{"a":1,"b":"b","c":3}})");
resp = Run({"JSON.STRAPPEND", "json", "$.c.*", "a"});
ASSERT_EQ(RespExpr::ARRAY, resp.type);
EXPECT_THAT(resp.GetVec(), ElementsAre(IntArg(2), IntArg(3)));
resp = Run({"JSON.GET", "json"});
EXPECT_EQ(
resp,
R"({"a":{"a":"aaba"},"b":{"a":"aa","b":1},"c":{"a":"aa","b":"bba"},"d":{"a":1,"b":"b","c":3}})");
resp = Run({"JSON.STRAPPEND", "json", "$.c.b", "a"});
EXPECT_THAT(resp, IntArg(4));
ASSERT_THAT(resp, ArgType(RespExpr::ARRAY));
EXPECT_THAT(resp.GetVec(), ElementsAre(IntArg(2), IntArg(4)));
resp = Run({"JSON.GET", "json"});
EXPECT_EQ(
@ -718,7 +725,7 @@ TEST_F(JsonFamilyTest, StrAppend) {
R"({"a":{"a":"aaba"},"b":{"a":"aa","b":1},"c":{"a":"aa","b":"bbaa"},"d":{"a":1,"b":"b","c":3}})");
resp = Run({"JSON.STRAPPEND", "json", "$.d.*", "a"});
ASSERT_EQ(RespExpr::ARRAY, resp.type);
ASSERT_THAT(resp, ArgType(RespExpr::ARRAY));
EXPECT_THAT(resp.GetVec(),
ElementsAre(ArgType(RespExpr::NIL), IntArg(2), ArgType(RespExpr::NIL)));
@ -727,21 +734,248 @@ TEST_F(JsonFamilyTest, StrAppend) {
resp,
R"({"a":{"a":"aaba"},"b":{"a":"aa","b":1},"c":{"a":"aa","b":"bbaa"},"d":{"a":1,"b":"ba","c":3}})");
json = R"(
{"a":{"a":"a", "b":"aa", "c":"aaa"}, "b":{"a":"aaa", "b":"aa", "c":"a"}}
)";
resp = Run({"JSON.SET", "json", ".", json});
EXPECT_EQ(resp, "OK");
resp = Run({"JSON.STRAPPEND", "json", "$.a.*", "a"});
ASSERT_THAT(resp, ArgType(RespExpr::ARRAY));
EXPECT_THAT(resp.GetVec(), ElementsAre(IntArg(2), IntArg(3), IntArg(4)));
resp = Run({"JSON.GET", "json"});
EXPECT_EQ(resp, R"({"a":{"a":"aa","b":"aaa","c":"aaaa"},"b":{"a":"aaa","b":"aa","c":"a"}})");
resp = Run({"JSON.STRAPPEND", "json", "$.b.*", "a"});
ASSERT_THAT(resp, ArgType(RespExpr::ARRAY));
EXPECT_THAT(resp.GetVec(), ElementsAre(IntArg(4), IntArg(3), IntArg(2)));
resp = Run({"JSON.GET", "json"});
EXPECT_EQ(resp, R"({"a":{"a":"aa","b":"aaa","c":"aaaa"},"b":{"a":"aaaa","b":"aaa","c":"aa"}})");
json = R"(
{"a":{"a":"a", "b":"aa", "c":["aaaaa", "aaaaa"]}, "b":{"a":"aaa", "b":["aaaaa", "aaaaa"], "c":"a"}}
)";
resp = Run({"JSON.SET", "json", ".", json});
EXPECT_EQ(resp, "OK");
resp = Run({"JSON.STRAPPEND", "json", "$.a.*", "a"});
ASSERT_THAT(resp, ArgType(RespExpr::ARRAY));
EXPECT_THAT(resp.GetVec(), ElementsAre(IntArg(2), IntArg(3), ArgType(RespExpr::NIL)));
resp = Run({"JSON.GET", "json"});
EXPECT_EQ(
resp,
R"({"a":{"a":"aa","b":"aaa","c":["aaaaa","aaaaa"]},"b":{"a":"aaa","b":["aaaaa","aaaaa"],"c":"a"}})");
resp = Run({"JSON.STRAPPEND", "json", "$.b.*", "a"});
ASSERT_THAT(resp, ArgType(RespExpr::ARRAY));
EXPECT_THAT(resp.GetVec(), ElementsAre(IntArg(4), ArgType(RespExpr::NIL), IntArg(2)));
resp = Run({"JSON.GET", "json"});
EXPECT_EQ(
resp,
R"({"a":{"a":"aa","b":"aaa","c":["aaaaa","aaaaa"]},"b":{"a":"aaaa","b":["aaaaa","aaaaa"],"c":"aa"}})");
json = R"(
{"a":{"a":"a", "b":"aa", "c":{"c": "aaaaa"}}, "b":{"a":"aaa", "b":{"b": "aaaaa"}, "c":"a"}}
)";
resp = Run({"JSON.SET", "json", ".", json});
EXPECT_EQ(resp, "OK");
resp = Run({"JSON.STRAPPEND", "json", "$.a.*", "a"});
ASSERT_THAT(resp, ArgType(RespExpr::ARRAY));
EXPECT_THAT(resp.GetVec(), ElementsAre(IntArg(2), IntArg(3), ArgType(RespExpr::NIL)));
resp = Run({"JSON.GET", "json"});
EXPECT_EQ(
resp,
R"({"a":{"a":"aa","b":"aaa","c":{"c":"aaaaa"}},"b":{"a":"aaa","b":{"b":"aaaaa"},"c":"a"}})");
resp = Run({"JSON.STRAPPEND", "json", "$.b.*", "a"});
ASSERT_THAT(resp, ArgType(RespExpr::ARRAY));
EXPECT_THAT(resp.GetVec(), ElementsAre(IntArg(4), ArgType(RespExpr::NIL), IntArg(2)));
resp = Run({"JSON.GET", "json"});
EXPECT_EQ(
resp,
R"({"a":{"a":"aa","b":"aaa","c":{"c":"aaaaa"}},"b":{"a":"aaaa","b":{"b":"aaaaa"},"c":"aa"}})");
json = R"(
{"a":"foo", "inner": {"a": "bye"}, "inner1": {"a": 7}}
)";
resp = Run({"JSON.SET", "json", ".", json});
ASSERT_THAT(resp, "OK");
EXPECT_EQ(resp, "OK");
resp = Run({"JSON.STRAPPEND", "json", "$..a", "bar"});
ASSERT_EQ(RespExpr::ARRAY, resp.type);
ASSERT_THAT(resp, ArgType(RespExpr::ARRAY));
EXPECT_THAT(resp.GetVec(), ElementsAre(IntArg(6), IntArg(6), ArgType(RespExpr::NIL)));
resp = Run({"JSON.GET", "json"});
EXPECT_EQ(resp, R"({"a":"foobar","inner":{"a":"byebar"},"inner1":{"a":7}})");
}
TEST_F(JsonFamilyTest, StrAppendLegacyMode) {
string json = R"(
{"a":{"a":"a"}, "b":{"a":"a", "b":1}, "c":{"a":"a", "b":"bb"}, "d":{"a":1, "b":"b", "c":3}}
)";
auto resp = Run({"JSON.SET", "json", ".", json});
ASSERT_THAT(resp, "OK");
/* Test simple response from only one value */
resp = Run({"JSON.STRAPPEND", "json", ".a.a", "a", "b"});
ASSERT_THAT(resp, ArgType(RespExpr::INT64));
EXPECT_THAT(resp, IntArg(3));
resp = Run({"JSON.GET", "json"});
EXPECT_THAT(
resp,
R"({"a":{"a":"aab"},"b":{"a":"a","b":1},"c":{"a":"a","b":"bb"},"d":{"a":1,"b":"b","c":3}})");
resp = Run({"JSON.STRAPPEND", "json", ".a.*", "a"});
ASSERT_THAT(resp, ArgType(RespExpr::INT64));
EXPECT_THAT(resp, IntArg(4));
resp = Run({"JSON.GET", "json"});
EXPECT_THAT(
resp,
R"({"a":{"a":"aaba"},"b":{"a":"a","b":1},"c":{"a":"a","b":"bb"},"d":{"a":1,"b":"b","c":3}})");
resp = Run({"JSON.STRAPPEND", "json", ".c.b", "a"});
ASSERT_THAT(resp, ArgType(RespExpr::INT64));
EXPECT_THAT(resp, IntArg(3));
resp = Run({"JSON.GET", "json"});
EXPECT_THAT(
resp,
R"({"a":{"a":"aaba"},"b":{"a":"a","b":1},"c":{"a":"a","b":"bba"},"d":{"a":1,"b":"b","c":3}})");
/*
Test response from several possible values
In JSON legacy mode, the response contains only one value - the new length of the last updated
string.
*/
resp = Run({"JSON.STRAPPEND", "json", ".b.*", "a"});
ASSERT_THAT(resp, ArgType(RespExpr::INT64));
EXPECT_THAT(resp, IntArg(2));
resp = Run({"JSON.GET", "json"});
EXPECT_THAT(
resp,
R"({"a":{"a":"aaba"},"b":{"a":"aa","b":1},"c":{"a":"a","b":"bba"},"d":{"a":1,"b":"b","c":3}})");
resp = Run({"JSON.STRAPPEND", "json", ".c.*", "a"});
ASSERT_THAT(resp, ArgType(RespExpr::INT64));
EXPECT_THAT(resp, IntArg(4));
resp = Run({"JSON.GET", "json"});
EXPECT_THAT(
resp,
R"({"a":{"a":"aaba"},"b":{"a":"aa","b":1},"c":{"a":"aa","b":"bbaa"},"d":{"a":1,"b":"b","c":3}})");
resp = Run({"JSON.STRAPPEND", "json", ".d.*", "a"});
ASSERT_THAT(resp, ArgType(RespExpr::INT64));
EXPECT_THAT(resp, IntArg(2));
resp = Run({"JSON.GET", "json"});
EXPECT_THAT(
resp,
R"({"a":{"a":"aaba"},"b":{"a":"aa","b":1},"c":{"a":"aa","b":"bbaa"},"d":{"a":1,"b":"ba","c":3}})");
json = R"(
{"a":{"a":"a", "b":"aa", "c":"aaa"}, "b":{"a":"aaa", "b":"aa", "c":"a"}}
)";
resp = Run({"JSON.SET", "json", ".", json});
EXPECT_EQ(resp, "OK");
resp = Run({"JSON.STRAPPEND", "json", ".a.*", "a"});
ASSERT_THAT(resp, ArgType(RespExpr::INT64));
EXPECT_THAT(resp, IntArg(4));
resp = Run({"JSON.GET", "json"});
EXPECT_EQ(resp, R"({"a":{"a":"aa","b":"aaa","c":"aaaa"},"b":{"a":"aaa","b":"aa","c":"a"}})");
resp = Run({"JSON.STRAPPEND", "json", ".b.*", "a"});
ASSERT_THAT(resp, ArgType(RespExpr::INT64));
EXPECT_THAT(resp, IntArg(2));
resp = Run({"JSON.GET", "json"});
EXPECT_EQ(resp, R"({"a":{"a":"aa","b":"aaa","c":"aaaa"},"b":{"a":"aaaa","b":"aaa","c":"aa"}})");
json = R"(
{"a":{"a":"a", "b":"aa", "c":["aaaaa", "aaaaa"]}, "b":{"a":"aaa", "b":["aaaaa", "aaaaa"], "c":"a"}}
)";
resp = Run({"JSON.SET", "json", ".", json});
EXPECT_EQ(resp, "OK");
resp = Run({"JSON.STRAPPEND", "json", ".a.*", "a"});
ASSERT_THAT(resp, ArgType(RespExpr::INT64));
EXPECT_THAT(resp, IntArg(3));
resp = Run({"JSON.GET", "json"});
EXPECT_EQ(
resp,
R"({"a":{"a":"aa","b":"aaa","c":["aaaaa","aaaaa"]},"b":{"a":"aaa","b":["aaaaa","aaaaa"],"c":"a"}})");
resp = Run({"JSON.STRAPPEND", "json", ".b.*", "a"});
ASSERT_THAT(resp, ArgType(RespExpr::INT64));
EXPECT_THAT(resp, IntArg(2));
resp = Run({"JSON.GET", "json"});
EXPECT_EQ(
resp,
R"({"a":{"a":"aa","b":"aaa","c":["aaaaa","aaaaa"]},"b":{"a":"aaaa","b":["aaaaa","aaaaa"],"c":"aa"}})");
json = R"(
{"a":{"a":"a", "b":"aa", "c":{"c": "aaaaa"}}, "b":{"a":"aaa", "b":{"b": "aaaaa"}, "c":"a"}}
)";
resp = Run({"JSON.SET", "json", ".", json});
EXPECT_EQ(resp, "OK");
resp = Run({"JSON.STRAPPEND", "json", ".a.*", "a"});
ASSERT_THAT(resp, ArgType(RespExpr::INT64));
EXPECT_THAT(resp, IntArg(3));
resp = Run({"JSON.GET", "json"});
EXPECT_EQ(
resp,
R"({"a":{"a":"aa","b":"aaa","c":{"c":"aaaaa"}},"b":{"a":"aaa","b":{"b":"aaaaa"},"c":"a"}})");
resp = Run({"JSON.STRAPPEND", "json", ".b.*", "a"});
ASSERT_THAT(resp, ArgType(RespExpr::INT64));
EXPECT_THAT(resp, IntArg(2));
resp = Run({"JSON.GET", "json"});
EXPECT_EQ(
resp,
R"({"a":{"a":"aa","b":"aaa","c":{"c":"aaaaa"}},"b":{"a":"aaaa","b":{"b":"aaaaa"},"c":"aa"}})");
json = R"(
{"a":"foo", "inner": {"a": "bye"}, "inner1": {"a": 7}}
)";
resp = Run({"JSON.SET", "json", ".", json});
EXPECT_EQ(resp, "OK");
resp = Run({"JSON.STRAPPEND", "json", "..a", "bar"});
ASSERT_THAT(resp, ArgType(RespExpr::INT64));
EXPECT_THAT(resp, IntArg(6));
resp = Run({"JSON.GET", "json"});
EXPECT_EQ(resp, R"({"a":"foobar","inner":{"a":"byebar"},"inner1":{"a":7}})");
}
TEST_F(JsonFamilyTest, Clear) {
string json = R"(
[[], [0], [0,1], [0,1,2], 1, true, null, "d"]