diff --git a/src/server/json_family.cc b/src/server/json_family.cc index 3d8932ce97aa..029cfbd90c0c 100644 --- a/src/server/json_family.cc +++ b/src/server/json_family.cc @@ -6,9 +6,9 @@ extern "C" { #include "redis/object.h" -#include "redis/util.h" } +#include #include #include @@ -28,8 +28,9 @@ using namespace jsoncons; using JsonExpression = jsonpath::jsonpath_expression; using OptBool = optional; +using OptDouble = optional; using OptSizeT = optional; -using JsonReplaceCb = std::function; +using JsonReplaceCb = function; using CI = CommandId; namespace { @@ -84,6 +85,42 @@ string JsonType(const json& val) { return ""; } +bool ParseDouble(string_view src, double* value) { + if (src.empty()) + return false; + + if (src == "-inf") { + *value = -HUGE_VAL; + } else if (src == "+inf") { + *value = HUGE_VAL; + } else { + absl::from_chars_result result = absl::from_chars(src.data(), src.end(), *value); + if (int(result.ec) != 0 || result.ptr != src.end() || isnan(*value)) + return false; + } + return true; +} + +template +void PrintOptVec(ConnectionContext* cntx, const OpResult>>& result) { + if (result->empty()) { + (*cntx)->SendNullArray(); + } else { + (*cntx)->StartArray(result->size()); + for (auto& it : *result) { + if (it.has_value()) { + if constexpr (is_integral_v) { + (*cntx)->SendLong(*it); + } else if (is_floating_point_v) { + (*cntx)->SendDouble(*it); + } + } else { + (*cntx)->SendNull(); + } + } + } +} + error_code JsonReplace(json& instance, string_view& path, JsonReplaceCb callback) { using evaluator_t = jsoncons::jsonpath::detail::jsonpath_evaluator; using value_type = evaluator_t::value_type; @@ -259,8 +296,100 @@ OpResult> OpToggle(const OpArgs& op_args, string_view key, strin return vec; } +template +OpResult> OpDoubleArithmetic(const OpArgs& op_args, string_view key, + string_view path, double num, Op arithmetic_op) { + OpResult result = GetJson(op_args, key); + if (!result) { + return result.status(); + } + + double int_part; + bool has_fractional_part = (modf(num, &int_part) != 0); + vector vec; + + auto cb = [&](const string& path, json& val) { + if (val.is_number()) { + double result = arithmetic_op(val.as(), num); + if (val.is_double() || has_fractional_part) { + val = result; + vec.emplace_back(result); + } else { + val = (uint64_t)result; + vec.emplace_back(result); + } + } else { + vec.emplace_back(nullopt); + } + }; + + json j = result.value(); + error_code ec = JsonReplace(j, path, cb); + if (ec) { + VLOG(1) << "Failed to evaulate expression on json with error: " << ec.message(); + return OpStatus::SYNTAX_ERR; + } + + SetString(op_args, key, j.as_string()); + return vec; +} + } // namespace +void JsonFamily::NumIncrBy(CmdArgList args, ConnectionContext* cntx) { + string_view key = ArgS(args, 1); + string_view path = ArgS(args, 2); + string_view num = ArgS(args, 3); + + double dnum; + if (!ParseDouble(num, &dnum)) { + (*cntx)->SendError(kWrongTypeErr); + return; + } + + auto cb = [&](Transaction* t, EngineShard* shard) { + return OpDoubleArithmetic(t->GetOpArgs(shard), key, path, dnum, plus{}); + }; + + DVLOG(1) << "Before Get::ScheduleSingleHopT " << key; + Transaction* trans = cntx->transaction; + OpResult> result = trans->ScheduleSingleHopT(move(cb)); + + if (result) { + DVLOG(1) << "JSON.NUMINCRBY " << trans->DebugId() << ": " << key; + PrintOptVec(cntx, result); + } else { + (*cntx)->SendError(result.status()); + } +} + +void JsonFamily::NumMultBy(CmdArgList args, ConnectionContext* cntx) { + string_view key = ArgS(args, 1); + string_view path = ArgS(args, 2); + string_view num = ArgS(args, 3); + + double dnum; + if (!ParseDouble(num, &dnum)) { + (*cntx)->SendError(kWrongTypeErr); + return; + } + + auto cb = [&](Transaction* t, EngineShard* shard) { + return OpDoubleArithmetic(t->GetOpArgs(shard), key, path, dnum, multiplies{}); + }; + + DVLOG(1) << "Before Get::ScheduleSingleHopT " << key; + Transaction* trans = cntx->transaction; + OpResult> result = trans->ScheduleSingleHopT(move(cb)); + + if (result) { + DVLOG(1) << "JSON.NUMMULTBY " << trans->DebugId() << ": " << key; + PrintOptVec(cntx, result); + } else { + (*cntx)->SendError(result.status()); + } +} + void JsonFamily::Toggle(CmdArgList args, ConnectionContext* cntx) { string_view key = ArgS(args, 1); string_view path = ArgS(args, 2); @@ -275,18 +404,7 @@ void JsonFamily::Toggle(CmdArgList args, ConnectionContext* cntx) { if (result) { DVLOG(1) << "JSON.TOGGLE " << trans->DebugId() << ": " << key; - if (result->empty()) { - (*cntx)->SendNullArray(); - } else { - (*cntx)->StartArray(result->size()); - for (auto& it : *result) { - if (it.has_value()) { - (*cntx)->SendLong(*it); - } else { - (*cntx)->SendNull(); - } - } - } + PrintOptVec(cntx, result); } else { (*cntx)->SendError(result.status()); } @@ -353,18 +471,7 @@ void JsonFamily::ArrLen(CmdArgList args, ConnectionContext* cntx) { if (result) { DVLOG(1) << "JSON.ARRLEN " << trans->DebugId() << ": " << key; - if (result->empty()) { - (*cntx)->SendNullArray(); - } else { - (*cntx)->StartArray(result->size()); - for (auto& it : *result) { - if (it.has_value()) { - (*cntx)->SendLong(*it); - } else { - (*cntx)->SendNull(); - } - } - } + PrintOptVec(cntx, result); } else { (*cntx)->SendError(result.status()); } @@ -393,18 +500,7 @@ void JsonFamily::ObjLen(CmdArgList args, ConnectionContext* cntx) { if (result) { DVLOG(1) << "JSON.OBJLEN " << trans->DebugId() << ": " << key; - if (result->empty()) { - (*cntx)->SendNullArray(); - } else { - (*cntx)->StartArray(result->size()); - for (auto& it : *result) { - if (it.has_value()) { - (*cntx)->SendLong(*it); - } else { - (*cntx)->SendNull(); - } - } - } + PrintOptVec(cntx, result); } else { (*cntx)->SendError(result.status()); } @@ -433,18 +529,7 @@ void JsonFamily::StrLen(CmdArgList args, ConnectionContext* cntx) { if (result) { DVLOG(1) << "JSON.STRLEN " << trans->DebugId() << ": " << key; - if (result->empty()) { - (*cntx)->SendNullArray(); - } else { - (*cntx)->StartArray(result->size()); - for (auto& it : *result) { - if (it.has_value()) { - (*cntx)->SendLong(*it); - } else { - (*cntx)->SendNull(); - } - } - } + PrintOptVec(cntx, result); } else { (*cntx)->SendError(result.status()); } @@ -495,6 +580,10 @@ void JsonFamily::Register(CommandRegistry* registry) { *registry << CI{"JSON.OBJLEN", CO::READONLY | CO::FAST, 3, 1, 1, 1}.HFUNC(ObjLen); *registry << CI{"JSON.ARRLEN", CO::READONLY | CO::FAST, 3, 1, 1, 1}.HFUNC(ArrLen); *registry << CI{"JSON.TOGGLE", CO::WRITE | CO::DENYOOM | CO::FAST, 3, 1, 1, 1}.HFUNC(Toggle); + *registry << CI{"JSON.NUMINCRBY", CO::WRITE | CO::DENYOOM | CO::FAST, 4, 1, 1, 1}.HFUNC( + NumIncrBy); + *registry << CI{"JSON.NUMMULTBY", CO::WRITE | CO::DENYOOM | CO::FAST, 4, 1, 1, 1}.HFUNC( + NumMultBy); } } // namespace dfly diff --git a/src/server/json_family.h b/src/server/json_family.h index 57565fb971bd..adba4db76567 100644 --- a/src/server/json_family.h +++ b/src/server/json_family.h @@ -13,6 +13,7 @@ class ConnectionContext; class CommandRegistry; using facade::OpResult; using facade::OpStatus; +using facade::RedisReplyBuilder; class JsonFamily { public: @@ -25,6 +26,8 @@ class JsonFamily { static void ObjLen(CmdArgList args, ConnectionContext* cntx); static void ArrLen(CmdArgList args, ConnectionContext* cntx); static void Toggle(CmdArgList args, ConnectionContext* cntx); + static void NumIncrBy(CmdArgList args, ConnectionContext* cntx); + static void NumMultBy(CmdArgList args, ConnectionContext* cntx); }; } // namespace dfly diff --git a/src/server/json_family_test.cc b/src/server/json_family_test.cc index edab27060e4c..f406f1436cfa 100644 --- a/src/server/json_family_test.cc +++ b/src/server/json_family_test.cc @@ -262,4 +262,214 @@ TEST_F(JsonFamilyTest, Toggle) { EXPECT_EQ(resp, R"([true,false,1,null,"foo",[],{}])"); } +TEST_F(JsonFamilyTest, NumIncrBy) { + string json = R"( + {"e":1.5,"a":1} + )"; + + auto resp = Run({"set", "json", json}); + ASSERT_THAT(resp, "OK"); + + resp = Run({"JSON.NUMINCRBY", "json", "$.a", "1.1"}); + ASSERT_THAT(resp, ArgType(RespExpr::ARRAY)); + EXPECT_THAT(resp.GetVec(), ElementsAre("2.1")); + + resp = Run({"JSON.NUMINCRBY", "json", "$.e", "1"}); + ASSERT_THAT(resp, ArgType(RespExpr::ARRAY)); + EXPECT_THAT(resp.GetVec(), ElementsAre("2.5")); + + resp = Run({"JSON.NUMINCRBY", "json", "$.e", "inf"}); + EXPECT_THAT(resp, ErrArg("(error) OVERFLOW Addition would overflow")); + + json = R"( + {"e":1.5,"a":1} + )"; + + resp = Run({"JSON.NUMINCRBY", "json", "$.e", "1.7e308"}); + ASSERT_THAT(resp, ArgType(RespExpr::ARRAY)); + EXPECT_THAT(resp.GetVec(), ElementsAre("1.7e308")); + + resp = Run({"JSON.NUMINCRBY", "json", "$.e", "1.7e308"}); + EXPECT_THAT(resp, ErrArg("(error) OVERFLOW Addition would overflow")); + + resp = Run({"JSON.GET", "json", "$.*"}); + EXPECT_EQ(resp, R"([1.5,1.7e308])"); + + json = R"( + {"a":[], "b":[1], "c":[1,2], "d":[1,2,3]} + )"; + + resp = Run({"set", "json", json}); + ASSERT_THAT(resp, "OK"); + + resp = Run({"JSON.NUMINCRBY", "json", "$.d[*]", "10"}); + ASSERT_THAT(resp, ArgType(RespExpr::ARRAY)); + EXPECT_THAT(resp.GetVec(), ElementsAre("11", "12", "13")); + + resp = Run({"JSON.GET", "json", "$.d[*]"}); + EXPECT_EQ(resp, R"([11,12,13])"); + + json = R"( + {"a":[], "b":[1], "c":[1,2], "d":[1,2,3]} + )"; + + resp = Run({"set", "json", json}); + ASSERT_THAT(resp, "OK"); + + resp = Run({"JSON.NUMINCRBY", "json", "$.a[*]", "1"}); + EXPECT_THAT(resp, ArgType(RespExpr::NIL_ARRAY)); + + resp = Run({"JSON.NUMINCRBY", "json", "$.b[*]", "1"}); + EXPECT_THAT(resp, "2"); + + resp = Run({"JSON.NUMINCRBY", "json", "$.c[*]", "1"}); + ASSERT_THAT(resp, ArgType(RespExpr::ARRAY)); + EXPECT_THAT(resp.GetVec(), ElementsAre("2", "3")); + + resp = Run({"JSON.NUMINCRBY", "json", "$.d[*]", "1"}); + ASSERT_THAT(resp, ArgType(RespExpr::ARRAY)); + EXPECT_THAT(resp.GetVec(), ElementsAre("2", "3", "4")); + + resp = Run({"JSON.GET", "json", "$.*"}); + EXPECT_EQ(resp, R"([[],[2],[2,3],[2,3,4]])"); + + json = R"( + {"a":{}, "b":{"a":1}, "c":{"a":1, "b":2}, "d":{"a":1, "b":2, "c":3}} + )"; + + resp = Run({"set", "json", json}); + ASSERT_THAT(resp, "OK"); + + resp = Run({"JSON.NUMINCRBY", "json", "$.a.*", "1"}); + EXPECT_THAT(resp, ArgType(RespExpr::NIL_ARRAY)); + + resp = Run({"JSON.NUMINCRBY", "json", "$.b.*", "1"}); + EXPECT_THAT(resp, "2"); + + resp = Run({"JSON.NUMINCRBY", "json", "$.c.*", "1"}); + ASSERT_THAT(resp, ArgType(RespExpr::ARRAY)); + EXPECT_THAT(resp.GetVec(), ElementsAre("2", "3")); + + resp = Run({"JSON.NUMINCRBY", "json", "$.d.*", "1"}); + ASSERT_THAT(resp, ArgType(RespExpr::ARRAY)); + EXPECT_THAT(resp.GetVec(), ElementsAre("2", "3", "4")); + + resp = Run({"JSON.GET", "json", "$.*"}); + EXPECT_EQ(resp, R"([{},{"a":2},{"a":2,"b":3},{"a":2,"b":3,"c":4}])"); + + json = R"( + {"a":{"a":"a"}, "b":{"a":"a", "b":1}, "c":{"a":"a", "b":"b"}, "d":{"a":1, "b":"b", "c":3}} + )"; + + resp = Run({"set", "json", json}); + ASSERT_THAT(resp, "OK"); + + resp = Run({"JSON.NUMINCRBY", "json", "$.a.*", "1"}); + EXPECT_THAT(resp, ArgType(RespExpr::NIL)); + + resp = Run({"JSON.NUMINCRBY", "json", "$.b.*", "1"}); + ASSERT_THAT(resp, ArgType(RespExpr::ARRAY)); + EXPECT_THAT(resp.GetVec(), ElementsAre(ArgType(RespExpr::NIL), "2")); + + resp = Run({"JSON.NUMINCRBY", "json", "$.c.*", "1"}); + ASSERT_THAT(resp, ArgType(RespExpr::ARRAY)); + EXPECT_THAT(resp.GetVec(), ElementsAre(ArgType(RespExpr::NIL), ArgType(RespExpr::NIL))); + + resp = Run({"JSON.NUMINCRBY", "json", "$.d.*", "1"}); + ASSERT_THAT(resp, ArgType(RespExpr::ARRAY)); + EXPECT_THAT(resp.GetVec(), ElementsAre("2", ArgType(RespExpr::NIL), "4")); + + resp = Run({"JSON.GET", "json", "$.*"}); + EXPECT_EQ(resp, R"([{"a":"a"},{"a":"a","b":2},{"a":"a","b":"b"},{"a":2,"b":"b","c":4}])"); +} + +TEST_F(JsonFamilyTest, NumMultBy) { + string json = R"( + {"a":[], "b":[1], "c":[1,2], "d":[1,2,3]} + )"; + + auto resp = Run({"set", "json", json}); + ASSERT_THAT(resp, "OK"); + + resp = Run({"JSON.NUMMULTBY", "json", "$.d[*]", "2"}); + ASSERT_THAT(resp, ArgType(RespExpr::ARRAY)); + EXPECT_THAT(resp.GetVec(), ElementsAre("2", "4", "6")); + + resp = Run({"JSON.GET", "json", "$.d[*]"}); + EXPECT_EQ(resp, R"([2,4,6])"); + + json = R"( + {"a":[], "b":[1], "c":[1,2], "d":[1,2,3]} + )"; + + resp = Run({"set", "json", json}); + ASSERT_THAT(resp, "OK"); + + resp = Run({"JSON.NUMMULTBY", "json", "$.a[*]", "2"}); + EXPECT_THAT(resp, ArgType(RespExpr::NIL_ARRAY)); + + resp = Run({"JSON.NUMMULTBY", "json", "$.b[*]", "2"}); + EXPECT_THAT(resp, "2"); + + resp = Run({"JSON.NUMMULTBY", "json", "$.c[*]", "2"}); + ASSERT_THAT(resp, ArgType(RespExpr::ARRAY)); + EXPECT_THAT(resp.GetVec(), ElementsAre("2", "4")); + + resp = Run({"JSON.NUMMULTBY", "json", "$.d[*]", "2"}); + ASSERT_THAT(resp, ArgType(RespExpr::ARRAY)); + EXPECT_THAT(resp.GetVec(), ElementsAre("2", "4", "6")); + + resp = Run({"JSON.GET", "json", "$.*"}); + EXPECT_EQ(resp, R"([[],[2],[2,4],[2,4,6]])"); + + json = R"( + {"a":{}, "b":{"a":1}, "c":{"a":1, "b":2}, "d":{"a":1, "b":2, "c":3}} + )"; + + resp = Run({"set", "json", json}); + ASSERT_THAT(resp, "OK"); + + resp = Run({"JSON.NUMMULTBY", "json", "$.a.*", "2"}); + EXPECT_THAT(resp, ArgType(RespExpr::NIL_ARRAY)); + + resp = Run({"JSON.NUMMULTBY", "json", "$.b.*", "2"}); + EXPECT_THAT(resp, "2"); + + resp = Run({"JSON.NUMMULTBY", "json", "$.c.*", "2"}); + ASSERT_THAT(resp, ArgType(RespExpr::ARRAY)); + EXPECT_THAT(resp.GetVec(), ElementsAre("2", "4")); + + resp = Run({"JSON.NUMMULTBY", "json", "$.d.*", "2"}); + ASSERT_THAT(resp, ArgType(RespExpr::ARRAY)); + EXPECT_THAT(resp.GetVec(), ElementsAre("2", "4", "6")); + + resp = Run({"JSON.GET", "json", "$.*"}); + EXPECT_EQ(resp, R"([{},{"a":2},{"a":2,"b":4},{"a":2,"b":4,"c":6}])"); + + json = R"( + {"a":{"a":"a"}, "b":{"a":"a", "b":1}, "c":{"a":"a", "b":"b"}, "d":{"a":1, "b":"b", "c":3}} + )"; + + resp = Run({"set", "json", json}); + ASSERT_THAT(resp, "OK"); + + resp = Run({"JSON.NUMMULTBY", "json", "$.a.*", "2"}); + EXPECT_THAT(resp, ArgType(RespExpr::NIL)); + + resp = Run({"JSON.NUMMULTBY", "json", "$.b.*", "2"}); + ASSERT_THAT(resp, ArgType(RespExpr::ARRAY)); + EXPECT_THAT(resp.GetVec(), ElementsAre(ArgType(RespExpr::NIL), "2")); + + resp = Run({"JSON.NUMMULTBY", "json", "$.c.*", "2"}); + ASSERT_THAT(resp, ArgType(RespExpr::ARRAY)); + EXPECT_THAT(resp.GetVec(), ElementsAre(ArgType(RespExpr::NIL), ArgType(RespExpr::NIL))); + + resp = Run({"JSON.NUMMULTBY", "json", "$.d.*", "2"}); + ASSERT_THAT(resp, ArgType(RespExpr::ARRAY)); + EXPECT_THAT(resp.GetVec(), ElementsAre("2", ArgType(RespExpr::NIL), "6")); + + resp = Run({"JSON.GET", "json", "$.*"}); + EXPECT_EQ(resp, R"([{"a":"a"},{"a":"a","b":2},{"a":"a","b":"b"},{"a":2,"b":"b","c":6}])"); +} + } // namespace dfly