mirror of
https://github.com/dragonflydb/dragonfly.git
synced 2025-05-10 18:05:44 +02:00
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:
parent
1acc824eff
commit
d51fea09e2
3 changed files with 456 additions and 54 deletions
|
@ -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_;
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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"]
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue