Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(server): implement JSON.STRLEN & JSON.TYPE commands #225

Merged
merged 1 commit into from
Aug 2, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
196 changes: 185 additions & 11 deletions src/server/json_family.cc
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ using namespace std;
using namespace jsoncons;

using JsonExpression = jsonpath::jsonpath_expression<json>;
using OptSizeT = optional<size_t>;
using CI = CommandId;

namespace {
Expand All @@ -44,22 +45,42 @@ string GetString(EngineShard* shard, const PrimeValue& pv) {
return res;
}

bool JsonErrorHandler(json_errc ec, const ser_context&) {
VLOG(1) << "Error while decode JSON: " << make_error_code(ec).message();
return false;
}

string JsonType(const json& val) {
if (val.is_null()) {
return "null";
} else if (val.is_bool()) {
return "boolean";
} else if (val.is_string()) {
return "string";
} else if (val.is_int64() || val.is_uint64()) {
return "integer";
} else if (val.is_number()) {
return "number";
} else if (val.is_object()) {
return "object";
} else if (val.is_array()) {
return "array";
}

return "";
}

OpResult<string> OpGet(const OpArgs& op_args, string_view key, vector<pair<string_view, JsonExpression>> expressions) {
OpResult<PrimeIterator> it_res = op_args.shard->db_slice().Find(op_args.db_ind, 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);

auto callback = [](json_errc ec, const ser_context&) {
VLOG(1) << "Error while decode JSON: " << make_error_code(ec).message();
return false;
};

error_code ec;
json_decoder<json> decoder;
basic_json_parser<char> parser(basic_json_decode_options<char>{}, callback);
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);

parser.update(val);
parser.finish_parse(decoder, ec);
Expand All @@ -83,8 +104,159 @@ OpResult<string> OpGet(const OpArgs& op_args, string_view key, vector<pair<strin
return out.as<string>();
}

OpResult<vector<string>> OpType(const OpArgs& op_args, string_view key, JsonExpression expression) {
OpResult<PrimeIterator> it_res = op_args.shard->db_slice().Find(op_args.db_ind, key, OBJ_STRING);
if (!it_res.ok())
return it_res.status();

error_code ec;
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);

parser.update(val);
parser.finish_parse(decoder, ec);

if (!decoder.is_valid()) {
return OpStatus::SYNTAX_ERR;
}

vector<string> vec;
auto cb = [&vec](const string_view& path, const json& val) {
vec.emplace_back(JsonType(val));
};

expression.evaluate(decoder.get_result(), cb);
return vec;
}

OpResult<vector<OptSizeT>> OpStrLen(const OpArgs& op_args, string_view key, JsonExpression expression) {
OpResult<PrimeIterator> it_res = op_args.shard->db_slice().Find(op_args.db_ind, key, OBJ_STRING);
if (!it_res.ok())
return it_res.status();

error_code ec;
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);

parser.update(val);
parser.finish_parse(decoder, ec);

if (!decoder.is_valid()) {
return OpStatus::SYNTAX_ERR;
}

vector<OptSizeT> vec;
auto cb = [&vec](const string_view& path, const json& val) {
if (val.is_string()) {
vec.emplace_back(val.as_string_view().size());
} else {
vec.emplace_back(nullopt);
}
};

expression.evaluate(decoder.get_result(), cb);
return vec;
}

} // namespace

void JsonFamily::Type(CmdArgList args, ConnectionContext* cntx) {
string_view key = ArgS(args, 1);
string_view path = ArgS(args, 2);

error_code ec;
JsonExpression expression = jsonpath::make_expression<json>(path, ec);

if (ec) {
VLOG(1) << "Invalid JSONPath syntax: " << ec.message();
(*cntx)->SendError(kSyntaxErr);
return;
}

auto cb = [&](Transaction* t, EngineShard* shard) {
return OpType(t->GetOpArgs(shard), key, move(expression));
};

DVLOG(1) << "Before Get::ScheduleSingleHopT " << key;
Transaction* trans = cntx->transaction;
OpResult<vector<string>> result = trans->ScheduleSingleHopT(move(cb));

if (result) {
DVLOG(1) << "JSON.TYPE " << trans->DebugId() << ": " << key;
if (result->empty()) {
// When vector is empty, the path doesn't exist in the corresponding json.
(*cntx)->SendNull();
} else {
(*cntx)->SendStringArr(*result);
}
} else {
switch (result.status()) {
case OpStatus::SYNTAX_ERR:
(*cntx)->SendError(kSyntaxErr);
break;
case OpStatus::KEY_NOTFOUND:
(*cntx)->SendNullArray();
break;
default:
DVLOG(1) << "JSON.TYPE " << key << " nil";
(*cntx)->SendNull();
}
}
}

void JsonFamily::StrLen(CmdArgList args, ConnectionContext* cntx) {
string_view key = ArgS(args, 1);
string_view path = ArgS(args, 2);

error_code ec;
JsonExpression expression = jsonpath::make_expression<json>(path, ec);

if (ec) {
VLOG(1) << "Invalid JSONPath syntax: " << ec.message();
(*cntx)->SendError(kSyntaxErr);
return;
}

auto cb = [&](Transaction* t, EngineShard* shard) {
return OpStrLen(t->GetOpArgs(shard), key, move(expression));
};

DVLOG(1) << "Before Get::ScheduleSingleHopT " << key;
Transaction* trans = cntx->transaction;
OpResult<vector<OptSizeT>> result = trans->ScheduleSingleHopT(move(cb));

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();
}
}
}
} else {
switch (result.status()) {
case OpStatus::SYNTAX_ERR:
(*cntx)->SendError(kSyntaxErr);
break;
default:
DVLOG(1) << "JSON.STRLEN " << key << " nil";
(*cntx)->SendNull();
}
}
}

void JsonFamily::Get(CmdArgList args, ConnectionContext* cntx) {
DCHECK_GE(args.size(), 3U);
string_view key = ArgS(args, 1);
Expand All @@ -97,8 +269,8 @@ void JsonFamily::Get(CmdArgList args, ConnectionContext* cntx) {
JsonExpression expr = jsonpath::make_expression<json>(path, ec);

if (ec) {
VLOG(1) << "Invalid JSONPath: " << ec.message();
(*cntx)->SendError(kWrongTypeErr);
VLOG(1) << "Invalid JSONPath syntax: " << ec.message();
(*cntx)->SendError(kSyntaxErr);
return;
}

Expand Down Expand Up @@ -135,6 +307,8 @@ void JsonFamily::Get(CmdArgList args, ConnectionContext* cntx) {

void JsonFamily::Register(CommandRegistry* registry) {
*registry << CI{"JSON.GET", CO::READONLY | CO::FAST, -3, 1, 1, 1}.HFUNC(Get);
*registry << CI{"JSON.TYPE", CO::READONLY | CO::FAST, 3, 1, 1, 1}.HFUNC(Type);
*registry << CI{"JSON.STRLEN", CO::READONLY | CO::FAST, 3, 1, 1, 1}.HFUNC(StrLen);
}

} // namespace dfly
2 changes: 2 additions & 0 deletions src/server/json_family.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ class JsonFamily {

private:
static void Get(CmdArgList args, ConnectionContext* cntx);
static void Type(CmdArgList args, ConnectionContext* cntx);
static void StrLen(CmdArgList args, ConnectionContext* cntx);
};

} // namespace dfly
37 changes: 37 additions & 0 deletions src/server/json_family_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -122,4 +122,41 @@ TEST_F(JsonFamilyTest, SetGetFromPhonebook) {
EXPECT_EQ(resp, string_view(R"(["646 555-4567","office"])", 25));
}

TEST_F(JsonFamilyTest, Type) {
string json = R"(
[1, 2.3, "foo", true, null, {}, []]
)";

auto resp = Run({"set", "json", json});
ASSERT_THAT(resp, "OK");

resp = Run({"JSON.TYPE", "json", "$[*]"});
ASSERT_EQ(RespExpr::ARRAY, resp.type);
EXPECT_THAT(resp.GetVec(), ElementsAre("integer", "number", "string", "boolean", "null", "object", "array"));

resp = Run({"JSON.TYPE", "json", "$[10]"});
EXPECT_THAT(resp, ArgType(RespExpr::NIL));

resp = Run({"JSON.TYPE", "not_exist_key", "$[10]"});
EXPECT_THAT(resp, ArgType(RespExpr::NIL_ARRAY));
}

TEST_F(JsonFamilyTest, StrLen) {
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({"set", "json", json});
ASSERT_THAT(resp, "OK");

resp = Run({"JSON.STRLEN", "json", "$.a.a"});
EXPECT_THAT(resp, IntArg(1));

resp = Run({"JSON.STRLEN", "json", "$.a.*"});
EXPECT_THAT(resp, IntArg(1));

resp = Run({"JSON.STRLEN", "json", "$.c.b"});
EXPECT_THAT(resp, IntArg(2));
}

} // namespace dfly