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

Integrate experimental Json functionality #259

Merged
merged 8 commits into from
Aug 26, 2022
Merged
13 changes: 13 additions & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,19 @@ add_third_party(
LIB libdouble-conversion.a
)

add_third_party(
jsoncons
URL https://github.com/danielaparker/jsoncons/archive/refs/tags/v0.168.7.tar.gz
CMAKE_PASS_FLAGS "-DJSONCONS_BUILD_TESTS=OFF"

LIB "none"
)

add_library(TRDP::jsoncons INTERFACE IMPORTED)
add_dependencies(TRDP::jsoncons jsoncons_project)
set_target_properties(TRDP::jsoncons PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES "${JSONCONS_INCLUDE_DIR}")

Message(STATUS "THIRD_PARTY_LIB_DIR ${THIRD_PARTY_LIB_DIR}")


Expand Down
1 change: 1 addition & 0 deletions src/core/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@ cxx_test(extent_tree_test dfly_core LABELS DFLY)
cxx_test(external_alloc_test dfly_core LABELS DFLY)
cxx_test(dash_test dfly_core LABELS DFLY)
cxx_test(interpreter_test dfly_core LABELS DFLY)
cxx_test(json_test dfly_core TRDP::jsoncons LABELS DFLY)
119 changes: 119 additions & 0 deletions src/core/json_test.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
// Copyright 2022, Roman Gershman. All rights reserved.
// See LICENSE for licensing terms.
//

#include <jsoncons/json.hpp>
#include <jsoncons_ext/jsonpath/jsonpath.hpp>

#include "base/gtest.h"
#include "base/logging.h"

namespace dfly {
using namespace std;
using namespace jsoncons;
using namespace jsoncons::literals;

class JsonTest : public ::testing::Test {
protected:
JsonTest() {
}
};

TEST_F(JsonTest, Basic) {
string data = R"(
{
"application": "hiking",
"reputons": [
{
"rater": "HikingAsylum",
"assertion": "advanced",
"rated": "Marilyn C",
"rating": 0.90,
"confidence": 0.99
}
]
}
)";

json j = json::parse(data);
EXPECT_TRUE(j.contains("reputons"));
jsonpath::json_replace(j, "$.reputons[*].rating", 1.1);
EXPECT_EQ(1.1, j["reputons"][0]["rating"].as_double());
}

TEST_F(JsonTest, Query) {
json j = R"(
{"a":{}, "b":{"a":1}, "c":{"a":1, "b":2}}
)"_json;

json out = jsonpath::json_query(j, "$..*");
EXPECT_EQ(R"([{},{"a":1},{"a":1,"b":2},1,1,2])"_json, out);

json j2 = R"(
{"firstName":"John","lastName":"Smith","age":27,"weight":135.25,"isAlive":true,"address":{"street":"21 2nd Street","city":"New York","state":"NY","zipcode":"10021-3100"},"phoneNumbers":[{"type":"home","number":"212 555-1234"},{"type":"office","number":"646 555-4567"}],"children":[],"spouse":null}
)"_json;

// json_query always returns arrays.
// See here: https://github.com/danielaparker/jsoncons/issues/82
// Therefore we are going to only support the "extended" semantics
// of json API (as they are called in AWS documentation).
out = jsonpath::json_query(j2, "$.address");
EXPECT_EQ(R"([{"street":"21 2nd Street","city":"New York",
"state":"NY","zipcode":"10021-3100"}])"_json,
out);
}

TEST_F(JsonTest, Errors) {
auto cb = [](json_errc err, const ser_context& contexts) { return false; };

json_decoder<json> decoder;
basic_json_parser<char> parser(basic_json_decode_options<char>{}, cb);

string_view input{"\000bla"};
parser.update(input.data(), input.size());
parser.parse_some(decoder);
parser.finish_parse(decoder);
parser.check_done();
EXPECT_FALSE(decoder.is_valid());
}

TEST_F(JsonTest, Delete) {
json j1 = R"({"c":{"a":1, "b":2}, "d":{"a":1, "b":2, "c":3}, "e": [1,2]})"_json;

auto deleter = [](const json::string_view_type& path, json& val) {
LOG(INFO) << "path: " << path;
// val.evaluate();
// if (val.is_object())
// val.erase(val.object_range().begin(), val.object_range().end());
};
jsonpath::json_replace(j1, "$.d.*", deleter);

auto expr = jsonpath::make_expression<json>("$.d.*");

auto callback = [](const std::string& path, const json& val) {
LOG(INFO) << path << ": " << val << "\n";
};
expr.evaluate(j1, callback, jsonpath::result_options::path);
auto it = j1.find("d");
ASSERT_TRUE(it != j1.object_range().end());

it->value().erase("a");
EXPECT_EQ(R"({"c":{"a":1, "b":2}, "d":{"b":2, "c":3}, "e": [1,2]})"_json, j1);
}

TEST_F(JsonTest, DeleteExt) {
jsonpath::detail::static_resources<json, const json&> resources;
jsonpath::jsonpath_expression<json>::evaluator_t eval;
jsonpath::jsonpath_expression<json>::json_selector_t sel = eval.compile(resources, "$.d.*");
json j1 = R"({"c":{"a":1, "b":2}, "d":{"a":1, "b":2, "c":3}, "e": [1,2]})"_json;

jsoncons::jsonpath::detail::dynamic_resources<json, const json&> dyn_res;

auto f = [](const jsonpath::json_location<char>& path, const json& val) {
LOG(INFO) << path.to_string();
};

sel.evaluate(dyn_res, j1, dyn_res.root_path_node(), j1, f, jsonpath::result_options::path);
}

} // namespace dfly
1 change: 1 addition & 0 deletions src/facade/error.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,6 @@ extern const char kSyntaxErrType[];
extern const char kScriptErrType[];
extern const char kIndexOutOfRange[];
extern const char kOutOfMemory[];
extern const char kInvalidNumericResult[];

} // namespace dfly
1 change: 1 addition & 0 deletions src/facade/facade.cc
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ const char kSyntaxErrType[] = "syntax_error";
const char kScriptErrType[] = "script_error";
const char kIndexOutOfRange[] = "index out of range";
const char kOutOfMemory[] = "Out of memory";
const char kInvalidNumericResult[] = "result is not a number";

const char* RespExpr::TypeName(Type t) {
switch (t) {
Expand Down
1 change: 1 addition & 0 deletions src/facade/op_status.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ enum class OpStatus : uint16_t {
BUSY_GROUP,
STREAM_ID_SMALL,
ENTRIES_ADDED_SMALL,
INVALID_NUMERIC_RESULT,
};

class OpResultBase {
Expand Down
4 changes: 3 additions & 1 deletion src/facade/reply_builder.cc
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,9 @@ void RedisReplyBuilder::SendError(OpStatus status) {
case OpStatus::BUSY_GROUP:
SendError("-BUSYGROUP Consumer Group name already exists");
break;
case OpStatus::INVALID_NUMERIC_RESULT:
SendError(kInvalidNumericResult);
break;
default:
LOG(ERROR) << "Unsupported status " << status;
SendError("Internal error");
Expand Down Expand Up @@ -340,7 +343,6 @@ void RedisReplyBuilder::SendStringArr(StrPtr str_ptr, uint32_t len) {
unsigned vec_indx = 1;
string_view src;
for (unsigned i = 0; i < len; ++i) {

if (holds_alternative<const string_view*>(str_ptr)) {
src = get<const string_view*>(str_ptr)[i];
} else {
Expand Down
9 changes: 5 additions & 4 deletions src/server/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@ cxx_link(dfly_transaction uring_fiber_lib dfly_core strings_lib)

add_library(dragonfly_lib channel_slice.cc command_registry.cc
config_flags.cc conn_context.cc debugcmd.cc dflycmd.cc
generic_family.cc hset_family.cc
generic_family.cc hset_family.cc json_family.cc
list_family.cc main_service.cc rdb_load.cc rdb_save.cc replica.cc
snapshot.cc script_mgr.cc server_family.cc
set_family.cc stream_family.cc string_family.cc
zset_family.cc version.cc)

cxx_link(dragonfly_lib dfly_transaction dfly_facade redis_lib strings_lib html_lib)
cxx_link(dragonfly_lib dfly_transaction dfly_facade redis_lib strings_lib html_lib TRDP::jsoncons)

add_library(dfly_test_lib test_utils.cc)
cxx_link(dfly_test_lib dragonfly_lib facade_test gtest_main_ext)
Expand All @@ -31,9 +31,10 @@ cxx_test(rdb_test dfly_test_lib DATA testdata/empty.rdb testdata/redis6_small.rd
cxx_test(zset_family_test dfly_test_lib LABELS DFLY)
cxx_test(blocking_controller_test dragonfly_lib LABELS DFLY)
cxx_test(snapshot_test dragonfly_lib LABELS DFLY)
cxx_test(json_family_test dfly_test_lib LABELS DFLY)


add_custom_target(check_dfly WORKING_DIRECTORY .. COMMAND ctest -L DFLY)
add_dependencies(check_dfly dragonfly_test list_family_test
add_dependencies(check_dfly dragonfly_test json_family_test list_family_test
generic_family_test memcache_parser_test rdb_test
redis_parser_test stream_family_test string_family_test)
redis_parser_test snapshot_test stream_family_test string_family_test)
17 changes: 17 additions & 0 deletions src/server/common.cc
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

#include "server/common.h"

#include <absl/strings/charconv.h>
#include <absl/strings/str_cat.h>
#include <mimalloc.h>

Expand Down Expand Up @@ -163,6 +164,22 @@ bool ParseHumanReadableBytes(std::string_view str, int64_t* num_bytes) {
return true;
}

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;
}

#define ADD(x) (x) += o.x

TieredStats& TieredStats::operator+=(const TieredStats& o) {
Expand Down
1 change: 1 addition & 0 deletions src/server/common.h
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ inline void ToLower(const MutableSlice* val) {
}

bool ParseHumanReadableBytes(std::string_view str, int64_t* num_bytes);
bool ParseDouble(std::string_view src, double* value);
const char* ObjTypeName(int type);

const char* RdbTypeName(unsigned type);
Expand Down
Loading