Signed-off-by: iko1 <me@remotecpp.dev>
This commit is contained in:
parent
ad014ebb86
commit
28706715dc
|
@ -141,28 +141,37 @@ bool JsonErrorHandler(json_errc ec, const ser_context&) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
OpResult<json> GetJson(const OpArgs& op_args, string_view key) {
|
optional<json> ConstructJsonFromString(string_view val) {
|
||||||
OpResult<PrimeIterator> it_res = op_args.shard->db_slice().Find(op_args.db_cntx, key, OBJ_STRING);
|
|
||||||
if (!it_res.ok())
|
|
||||||
return it_res.status();
|
|
||||||
|
|
||||||
error_code ec;
|
error_code ec;
|
||||||
json_decoder<json> decoder;
|
json_decoder<json> decoder;
|
||||||
const PrimeValue& pv = it_res.value()->second;
|
|
||||||
|
|
||||||
string val = GetString(op_args.shard, pv);
|
|
||||||
basic_json_parser<char> parser(basic_json_decode_options<char>{}, &JsonErrorHandler);
|
basic_json_parser<char> parser(basic_json_decode_options<char>{}, &JsonErrorHandler);
|
||||||
|
|
||||||
parser.update(val);
|
parser.update(val);
|
||||||
parser.finish_parse(decoder, ec);
|
parser.finish_parse(decoder, ec);
|
||||||
|
|
||||||
if (!decoder.is_valid()) {
|
if (!decoder.is_valid()) {
|
||||||
return OpStatus::SYNTAX_ERR;
|
return nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
return decoder.get_result();
|
return decoder.get_result();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
OpResult<json> GetJson(const OpArgs& op_args, string_view key) {
|
||||||
|
OpResult<PrimeIterator> it_res = op_args.shard->db_slice().Find(op_args.db_cntx, key, OBJ_STRING);
|
||||||
|
if (!it_res.ok())
|
||||||
|
return it_res.status();
|
||||||
|
|
||||||
|
const PrimeValue& pv = it_res.value()->second;
|
||||||
|
|
||||||
|
string val = GetString(op_args.shard, pv);
|
||||||
|
optional<json> j = ConstructJsonFromString(val);
|
||||||
|
if (!j) {
|
||||||
|
return OpStatus::SYNTAX_ERR;
|
||||||
|
}
|
||||||
|
|
||||||
|
return *j;
|
||||||
|
}
|
||||||
|
|
||||||
// Returns the index of the next right bracket
|
// Returns the index of the next right bracket
|
||||||
optional<size_t> GetNextIndex(string_view str) {
|
optional<size_t> GetNextIndex(string_view str) {
|
||||||
size_t current_idx = 0;
|
size_t current_idx = 0;
|
||||||
|
@ -692,8 +701,113 @@ OpResult<vector<OptSizeT>> OpArrTrim(const OpArgs& op_args, string_view key, str
|
||||||
return vec;
|
return vec;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns numeric vector that represents the new length of the array at each path.
|
||||||
|
OpResult<vector<OptSizeT>> OpArrInsert(const OpArgs& op_args, string_view key, string_view path,
|
||||||
|
int index, const vector<json>& new_values) {
|
||||||
|
OpResult<json> result = GetJson(op_args, key);
|
||||||
|
if (!result) {
|
||||||
|
return result.status();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool out_of_boundaries_encountered = false;
|
||||||
|
vector<OptSizeT> vec;
|
||||||
|
// Insert user-supplied value into the supplied index that should be valid.
|
||||||
|
// If at least one index isn't valid within an array in the json doc, the operation is discarded.
|
||||||
|
// Negative indexes start from the end of the array.
|
||||||
|
auto cb = [&](const string& path, json& val) {
|
||||||
|
if (out_of_boundaries_encountered) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!val.is_array()) {
|
||||||
|
vec.emplace_back(nullopt);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t removal_index;
|
||||||
|
if (index < 0) {
|
||||||
|
if (val.empty()) {
|
||||||
|
out_of_boundaries_encountered = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int temp_index = index + val.size();
|
||||||
|
if (temp_index < 0) {
|
||||||
|
out_of_boundaries_encountered = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
removal_index = temp_index;
|
||||||
|
} else {
|
||||||
|
if ((size_t)index > val.size()) {
|
||||||
|
out_of_boundaries_encountered = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
removal_index = index;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto it = next(val.array_range().begin(), removal_index);
|
||||||
|
for (auto& new_val : new_values) {
|
||||||
|
it = val.insert(it, new_val);
|
||||||
|
it++;
|
||||||
|
}
|
||||||
|
|
||||||
|
vec.emplace_back(val.size());
|
||||||
|
};
|
||||||
|
|
||||||
|
json j = result.value();
|
||||||
|
error_code ec = JsonReplace(j, path, cb);
|
||||||
|
if (ec) {
|
||||||
|
VLOG(1) << "Failed to evaluate expression on json with error: " << ec.message();
|
||||||
|
return OpStatus::SYNTAX_ERR;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (out_of_boundaries_encountered) {
|
||||||
|
return OpStatus::OUT_OF_RANGE;
|
||||||
|
}
|
||||||
|
|
||||||
|
SetString(op_args, key, j.as_string());
|
||||||
|
return vec;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
void JsonFamily::ArrInsert(CmdArgList args, ConnectionContext* cntx) {
|
||||||
|
string_view key = ArgS(args, 1);
|
||||||
|
string_view path = ArgS(args, 2);
|
||||||
|
int index = -1;
|
||||||
|
|
||||||
|
if (!absl::SimpleAtoi(ArgS(args, 3), &index)) {
|
||||||
|
VLOG(1) << "Failed to convert the following value to numeric: " << ArgS(args, 3);
|
||||||
|
(*cntx)->SendError(kInvalidIntErr);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
vector<json> new_values;
|
||||||
|
for (size_t i = 4; i < args.size(); i++) {
|
||||||
|
optional<json> val = ConstructJsonFromString(ArgS(args, i));
|
||||||
|
if (!val) {
|
||||||
|
(*cntx)->SendError(kSyntaxErr);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
new_values.emplace_back(move(*val));
|
||||||
|
}
|
||||||
|
|
||||||
|
auto cb = [&](Transaction* t, EngineShard* shard) {
|
||||||
|
return OpArrInsert(t->GetOpArgs(shard), key, path, index, new_values);
|
||||||
|
};
|
||||||
|
|
||||||
|
Transaction* trans = cntx->transaction;
|
||||||
|
OpResult<vector<OptSizeT>> result = trans->ScheduleSingleHopT(move(cb));
|
||||||
|
if (result) {
|
||||||
|
PrintOptVec(cntx, result);
|
||||||
|
} else {
|
||||||
|
(*cntx)->SendError(result.status());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void JsonFamily::ArrTrim(CmdArgList args, ConnectionContext* cntx) {
|
void JsonFamily::ArrTrim(CmdArgList args, ConnectionContext* cntx) {
|
||||||
string_view key = ArgS(args, 1);
|
string_view key = ArgS(args, 1);
|
||||||
string_view path = ArgS(args, 2);
|
string_view path = ArgS(args, 2);
|
||||||
|
@ -1102,6 +1216,8 @@ void JsonFamily::Register(CommandRegistry* registry) {
|
||||||
*registry << CI{"JSON.CLEAR", CO::WRITE | CO::DENYOOM | CO::FAST, 3, 1, 1, 1}.HFUNC(Clear);
|
*registry << CI{"JSON.CLEAR", CO::WRITE | CO::DENYOOM | CO::FAST, 3, 1, 1, 1}.HFUNC(Clear);
|
||||||
*registry << CI{"JSON.ARRPOP", CO::WRITE | CO::DENYOOM | CO::FAST, -3, 1, 1, 1}.HFUNC(ArrPop);
|
*registry << CI{"JSON.ARRPOP", CO::WRITE | CO::DENYOOM | CO::FAST, -3, 1, 1, 1}.HFUNC(ArrPop);
|
||||||
*registry << CI{"JSON.ARRTRIM", CO::WRITE | CO::DENYOOM | CO::FAST, 5, 1, 1, 1}.HFUNC(ArrTrim);
|
*registry << CI{"JSON.ARRTRIM", CO::WRITE | CO::DENYOOM | CO::FAST, 5, 1, 1, 1}.HFUNC(ArrTrim);
|
||||||
|
*registry << CI{"JSON.ARRINSERT", CO::WRITE | CO::DENYOOM | CO::FAST, -4, 1, 1, 1}.HFUNC(
|
||||||
|
ArrInsert);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace dfly
|
} // namespace dfly
|
||||||
|
|
|
@ -34,6 +34,7 @@ class JsonFamily {
|
||||||
static void Clear(CmdArgList args, ConnectionContext* cntx);
|
static void Clear(CmdArgList args, ConnectionContext* cntx);
|
||||||
static void ArrPop(CmdArgList args, ConnectionContext* cntx);
|
static void ArrPop(CmdArgList args, ConnectionContext* cntx);
|
||||||
static void ArrTrim(CmdArgList args, ConnectionContext* cntx);
|
static void ArrTrim(CmdArgList args, ConnectionContext* cntx);
|
||||||
|
static void ArrInsert(CmdArgList args, ConnectionContext* cntx);
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace dfly
|
} // namespace dfly
|
||||||
|
|
|
@ -746,4 +746,34 @@ TEST_F(JsonFamilyTest, ArrTrim) {
|
||||||
EXPECT_EQ(resp, R"({"a":[1,3,2],"nested":{"a":false}})");
|
EXPECT_EQ(resp, R"({"a":[1,3,2],"nested":{"a":false}})");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_F(JsonFamilyTest, ArrInsert) {
|
||||||
|
string json = R"(
|
||||||
|
[[], ["a"], ["a", "b"]]
|
||||||
|
)";
|
||||||
|
|
||||||
|
auto resp = Run({"set", "json", json});
|
||||||
|
ASSERT_THAT(resp, "OK");
|
||||||
|
|
||||||
|
resp = Run({"JSON.ARRINSERT", "json", "$[*]", "0", R"("a")"});
|
||||||
|
ASSERT_EQ(RespExpr::ARRAY, resp.type);
|
||||||
|
EXPECT_THAT(resp.GetVec(), ElementsAre(IntArg(1), IntArg(2), IntArg(3)));
|
||||||
|
|
||||||
|
resp = Run({"GET", "json"});
|
||||||
|
EXPECT_EQ(resp, R"([["a"],["a","a"],["a","a","b"]])");
|
||||||
|
|
||||||
|
resp = Run({"JSON.ARRINSERT", "json", "$[*]", "-1", R"("b")"});
|
||||||
|
ASSERT_EQ(RespExpr::ARRAY, resp.type);
|
||||||
|
EXPECT_THAT(resp.GetVec(), ElementsAre(IntArg(2), IntArg(3), IntArg(4)));
|
||||||
|
|
||||||
|
resp = Run({"GET", "json"});
|
||||||
|
EXPECT_EQ(resp, R"([["b","a"],["a","b","a"],["a","a","b","b"]])");
|
||||||
|
|
||||||
|
resp = Run({"JSON.ARRINSERT", "json", "$[*]", "1", R"("c")"});
|
||||||
|
ASSERT_EQ(RespExpr::ARRAY, resp.type);
|
||||||
|
EXPECT_THAT(resp.GetVec(), ElementsAre(IntArg(3), IntArg(4), IntArg(5)));
|
||||||
|
|
||||||
|
resp = Run({"GET", "json"});
|
||||||
|
EXPECT_EQ(resp, R"([["b","c","a"],["a","c","b","a"],["a","c","a","b","b"]])");
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace dfly
|
} // namespace dfly
|
||||||
|
|
Loading…
Reference in New Issue