Skip to content

Commit

Permalink
Merge branch 'fix-json-numop' of github.com:PragmaTwice/kvrocks into …
Browse files Browse the repository at this point in the history
…fix-json-numop
  • Loading branch information
PragmaTwice committed May 31, 2024
2 parents a2b8cc7 + 60f5358 commit a192218
Show file tree
Hide file tree
Showing 5 changed files with 112 additions and 1 deletion.
45 changes: 44 additions & 1 deletion src/commands/cmd_json.cc
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,14 @@ std::string OptionalsToString(const Connection *conn, Optionals<T> &opts) {
return str;
}

std::string SizeToString(const std::vector<std::size_t> &elems) {
std::string result = MultiLen(elems.size());
for (const auto &elem : elems) {
result += redis::Integer(elem);
}
return result;
}

class CommandJsonSet : public Commander {
public:
Status Execute(Server *srv, Connection *conn, std::string *output) override {
Expand Down Expand Up @@ -632,6 +640,40 @@ class CommandJsonMSet : public Commander {
}
};

class CommandJsonDebug : public Commander {
public:
Status Execute(Server *svr, Connection *conn, std::string *output) override {
redis::Json json(svr->storage, conn->GetNamespace());

std::string path = "$";

if (!util::EqualICase(args_[1], "memory")) {
return {Status::RedisExecErr, "ERR wrong number of arguments for 'json.debug' command"};
}

if (args_.size() == 4) {
path = args_[3];
} else if (args_.size() > 4) {
return {Status::RedisExecErr, "The number of arguments is more than expected"};
}

std::vector<std::size_t> results;
auto s = json.DebugMemory(args_[2], path, &results);

if (s.IsNotFound()) {
if (args_.size() == 3) {
*output = redis::Integer(0);
} else {
*output = SizeToString(results);
}
return Status::OK();
}
if (!s.ok()) return {Status::RedisExecErr, s.ToString()};

*output = SizeToString(results);
return Status::OK();
}
};
REDIS_REGISTER_COMMANDS(MakeCmdAttr<CommandJsonSet>("json.set", 4, "write", 1, 1, 1),
MakeCmdAttr<CommandJsonGet>("json.get", -2, "read-only", 1, 1, 1),
MakeCmdAttr<CommandJsonInfo>("json.info", 2, "read-only", 1, 1, 1),
Expand All @@ -655,6 +697,7 @@ REDIS_REGISTER_COMMANDS(MakeCmdAttr<CommandJsonSet>("json.set", 4, "write", 1, 1
MakeCmdAttr<CommandJsonStrAppend>("json.strappend", -3, "write", 1, 1, 1),
MakeCmdAttr<CommandJsonStrLen>("json.strlen", -2, "read-only", 1, 1, 1),
MakeCmdAttr<CommandJsonMGet>("json.mget", -3, "read-only", 1, -2, 1),
MakeCmdAttr<CommandJsonMSet>("json.mset", -4, "write", 1, -3, 3), );
MakeCmdAttr<CommandJsonMSet>("json.mset", -4, "write", 1, -3, 3),
MakeCmdAttr<CommandJsonDebug>("json.debug", -3, "read-only", 2, 2, 1));

} // namespace redis
24 changes: 24 additions & 0 deletions src/types/json.h
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,30 @@ struct JsonValue {
return results;
}

StatusOr<std::vector<size_t>> GetBytes(std::string_view path, JsonStorageFormat format,
int max_nesting_depth = std::numeric_limits<int>::max()) const {
std::vector<size_t> results;
Status s;
try {
jsoncons::jsonpath::json_query(value, path, [&](const std::string & /*path*/, const jsoncons::json &origin) {
if (!s) return;
std::string buffer;
JsonValue query_value(origin);
if (format == JsonStorageFormat::JSON) {
s = query_value.Dump(&buffer, max_nesting_depth);
} else if (format == JsonStorageFormat::CBOR) {
s = query_value.DumpCBOR(&buffer, max_nesting_depth);
}
results.emplace_back(buffer.size());
});
} catch (const jsoncons::jsonpath::jsonpath_error &e) {
return {Status::NotOK, e.what()};
}
if (!s) return {Status::NotOK, s.Msg()};

return results;
}

StatusOr<JsonValue> Get(std::string_view path) const {
try {
return jsoncons::jsonpath::json_query(value, path);
Expand Down
20 changes: 20 additions & 0 deletions src/types/redis_json.cc
Original file line number Diff line number Diff line change
Expand Up @@ -626,4 +626,24 @@ std::vector<rocksdb::Status> Json::readMulti(const std::vector<Slice> &ns_keys,
return statuses;
}

rocksdb::Status Json::DebugMemory(const std::string &user_key, const std::string &path, std::vector<size_t> *results) {
auto ns_key = AppendNamespacePrefix(user_key);
JsonMetadata metadata;
if (path == "$") {
std::string bytes;
Slice rest;
auto s = GetMetadata(GetOptions{}, {kRedisJson}, ns_key, &bytes, &metadata, &rest);
if (!s.ok()) return s;
results->emplace_back(rest.size());
} else {
JsonValue json_val;
auto s = read(ns_key, &metadata, &json_val);
if (!s.ok()) return s;
auto str_bytes = json_val.GetBytes(path, metadata.format, storage_->GetConfig()->json_max_nesting_depth);
if (!str_bytes) return rocksdb::Status::InvalidArgument(str_bytes.Msg());
*results = std::move(*str_bytes);
}
return rocksdb::Status::OK();
}

} // namespace redis
1 change: 1 addition & 0 deletions src/types/redis_json.h
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ class Json : public Database {
std::vector<JsonValue> &results);
rocksdb::Status MSet(const std::vector<std::string> &user_keys, const std::vector<std::string> &paths,
const std::vector<std::string> &values);
rocksdb::Status DebugMemory(const std::string &user_key, const std::string &path, std::vector<size_t> *results);

private:
rocksdb::Status write(Slice ns_key, JsonMetadata *metadata, const JsonValue &json_val);
Expand Down
23 changes: 23 additions & 0 deletions tests/gocase/unit/type/json/json_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -634,6 +634,29 @@ func TestJson(t *testing.T) {
EqualJSON(t, `[{"a": 4, "b": 5, "nested": {"a": 6}, "c": null}]`, rdb.Do(ctx, "JSON.GET", "a1", "$").Val())
EqualJSON(t, `[4]`, rdb.Do(ctx, "JSON.GET", "a1", "$.a").Val())
})

t.Run("JSON.DEBUG MEMORY basics", func(t *testing.T) {
require.NoError(t, rdb.Do(ctx, "JSON.SET", "a", "$", `{"b":true,"x":1, "y":1.2, "z": {"x":[1,2,3], "y": null}, "v":{"x":"y"},"f":{"x":[]}}`).Err())
//object
var result1 = make([]interface{}, 0)
result1 = append(result1, int64(43))
require.Equal(t, result1, rdb.Do(ctx, "JSON.DEBUG", "MEMORY", "a", "$").Val())
//integer string array empty_array
var result2 = make([]interface{}, 0)
result2 = append(result2, int64(1), int64(1), int64(2), int64(4))
require.Equal(t, result2, rdb.Do(ctx, "JSON.DEBUG", "MEMORY", "a", "$..x").Val())
//null object
var result3 = make([]interface{}, 0)
result3 = append(result3, int64(9), int64(1))
require.Equal(t, result3, rdb.Do(ctx, "JSON.DEBUG", "MEMORY", "a", "$..y").Val())
//no no_exists
require.Equal(t, []interface{}{}, rdb.Do(ctx, "JSON.DEBUG", "MEMORY", "a", "$..no_exists").Val())
//no key no path
require.Equal(t, rdb.Do(ctx, "JSON.DEBUG", "MEMORY", "not_exists").Val(), int64(0))
//no key have path
require.Equal(t, []interface{}{}, rdb.Do(ctx, "JSON.DEBUG", "MEMORY", "not_exists", "$").Val())

})
}

func EqualJSON(t *testing.T, expected string, actual interface{}) {
Expand Down

0 comments on commit a192218

Please sign in to comment.