blob: be9ebff08e8ad3c2b36f653d123f1e7832e2206c [file]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
#include <algorithm>
#include "commander.h"
#include "commands/command_parser.h"
#include "commands/error_constants.h"
#include "error_constants.h"
#include "parse_util.h"
#include "server/redis_reply.h"
#include "server/server.h"
#include "storage/redis_metadata.h"
#include "types/redis_json.h"
namespace redis {
template <typename T, std::enable_if_t<std::is_integral_v<T>, int> = 0>
std::string OptionalsToString(const Connection *conn, Optionals<T> &opts) {
std::string str = MultiLen(opts.size());
for (const auto &opt : opts) {
if (opt.has_value()) {
str += redis::Integer(opt.value());
} else {
str += conn->NilString();
}
}
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(engine::Context &ctx, Server *srv, Connection *conn, std::string *output) override {
redis::Json json(srv->storage, conn->GetNamespace());
auto s = json.Set(ctx, args_[1], args_[2], args_[3]);
if (!s.ok()) return {Status::RedisExecErr, s.ToString()};
*output = redis::RESP_OK;
return Status::OK();
}
};
class CommandJsonGet : public Commander {
public:
Status Parse(const std::vector<std::string> &args) override {
CommandParser parser(args, 2);
while (parser.Good()) {
if (parser.EatEqICase("INDENT")) {
auto indent = GET_OR_RET(parser.TakeStr());
if (std::any_of(indent.begin(), indent.end(), [](char v) { return v != ' '; })) {
return {Status::RedisParseErr, "Currently only all-space INDENT is supported"};
}
indent_size_ = indent.size();
} else if (parser.EatEqICase("NEWLINE")) {
new_line_chars_ = GET_OR_RET(parser.TakeStr());
} else if (parser.EatEqICase("SPACE")) {
auto space = GET_OR_RET(parser.TakeStr());
if (space != "" && space != " ") {
return {Status::RedisParseErr, "Currently only SPACE ' ' is supported"};
}
spaces_after_colon_ = !space.empty();
} else {
break;
}
}
while (parser.Good()) {
paths_.push_back(GET_OR_RET(parser.TakeStr()));
}
return Status::OK();
}
Status Execute(engine::Context &ctx, Server *srv, Connection *conn, std::string *output) override {
redis::Json json(srv->storage, conn->GetNamespace());
JsonValue result;
auto s = json.Get(ctx, args_[1], paths_, &result);
if (s.IsNotFound()) {
*output = conn->NilString();
return Status::OK();
}
if (!s.ok()) return {Status::RedisExecErr, s.ToString()};
*output = redis::BulkString(GET_OR_RET(result.Print(indent_size_, spaces_after_colon_, new_line_chars_)));
return Status::OK();
}
private:
uint8_t indent_size_ = 0;
bool spaces_after_colon_ = false;
std::string new_line_chars_;
std::vector<std::string> paths_;
};
class CommandJsonInfo : public Commander {
public:
Status Execute(engine::Context &ctx, Server *srv, Connection *conn, std::string *output) override {
redis::Json json(srv->storage, conn->GetNamespace());
auto storage_format = JsonStorageFormat::JSON;
auto s = json.Info(ctx, args_[1], &storage_format);
if (!s.ok()) return {Status::RedisExecErr, s.ToString()};
auto format_str = storage_format == JsonStorageFormat::JSON ? "json"
: storage_format == JsonStorageFormat::CBOR ? "cbor"
: "unknown";
output->append(conn->MultiBulkString({"storage_format", format_str}));
return Status::OK();
}
};
class CommandJsonArrAppend : public Commander {
public:
Status Execute(engine::Context &ctx, Server *srv, Connection *conn, std::string *output) override {
redis::Json json(srv->storage, conn->GetNamespace());
Optionals<size_t> results;
auto s = json.ArrAppend(ctx, args_[1], args_[2], {args_.begin() + 3, args_.end()}, &results);
if (!s.ok()) return {Status::RedisExecErr, s.ToString()};
*output = OptionalsToString(conn, results);
return Status::OK();
}
};
class CommandJsonArrInsert : public Commander {
public:
Status Parse(const std::vector<std::string> &args) override {
auto parse_result = ParseInt<int>(args[3], 10);
if (!parse_result) {
return {Status::RedisParseErr, errValueNotInteger};
}
index_ = *parse_result;
return Commander::Parse(args);
}
Status Execute(engine::Context &ctx, Server *srv, Connection *conn, std::string *output) override {
redis::Json json(srv->storage, conn->GetNamespace());
Optionals<uint64_t> results;
auto parse_result = ParseInt<int>(args_[3], 10);
auto s = json.ArrInsert(ctx, args_[1], args_[2], index_, {args_.begin() + 4, args_.end()}, &results);
if (s.IsNotFound()) {
*output = conn->NilString();
return Status::OK();
}
if (!s.ok()) return {Status::RedisExecErr, s.ToString()};
*output = OptionalsToString(conn, results);
return Status::OK();
}
private:
int index_;
};
class CommandJsonType : public Commander {
public:
Status Execute(engine::Context &ctx, Server *srv, Connection *conn, std::string *output) override {
redis::Json json(srv->storage, conn->GetNamespace());
std::vector<std::string> types;
std::string path = "$";
if (args_.size() == 3) {
path = args_[2];
} else if (args_.size() > 3) {
return {Status::RedisExecErr, "The number of arguments is more than expected"};
}
auto s = json.Type(ctx, args_[1], path, &types);
if (!s.ok() && !s.IsNotFound()) return {Status::RedisExecErr, s.ToString()};
if (s.IsNotFound()) {
*output = conn->NilString();
return Status::OK();
}
*output = conn->MultiBulkString(types);
return Status::OK();
}
};
class CommandJsonObjkeys : public Commander {
public:
Status Execute(engine::Context &ctx, Server *srv, Connection *conn, std::string *output) override {
redis::Json json(srv->storage, conn->GetNamespace());
Optionals<std::vector<std::string>> results;
// If path not specified set it to $
std::string path = (args_.size() > 2) ? args_[2] : "$";
auto s = json.ObjKeys(ctx, args_[1], path, &results);
if (!s.ok() && !s.IsNotFound()) return {Status::RedisExecErr, s.ToString()};
if (s.IsNotFound()) {
*output = conn->NilString();
return Status::OK();
}
*output = redis::MultiLen(results.size());
for (const auto &item : results) {
if (item.has_value()) {
*output += ArrayOfBulkStrings(item.value());
} else {
*output += conn->NilString();
}
}
return Status::OK();
}
};
class CommandJsonClear : public Commander {
public:
Status Execute(engine::Context &ctx, Server *svr, Connection *conn, std::string *output) override {
redis::Json json(svr->storage, conn->GetNamespace());
size_t result = 0;
// If path not specified set it to $
std::string path = (args_.size() > 2) ? args_[2] : "$";
auto s = json.Clear(ctx, args_[1], path, &result);
if (s.IsNotFound()) {
*output = conn->NilString();
return Status::OK();
}
if (!s.ok()) return {Status::RedisExecErr, s.ToString()};
*output = redis::Integer(result);
return Status::OK();
}
};
class CommandJsonToggle : public Commander {
public:
Status Execute(engine::Context &ctx, Server *svr, Connection *conn, std::string *output) override {
redis::Json json(svr->storage, conn->GetNamespace());
std::string path = (args_.size() > 2) ? args_[2] : "$";
Optionals<bool> results;
auto s = json.Toggle(ctx, args_[1], path, &results);
if (!s.ok() && !s.IsNotFound()) return {Status::RedisExecErr, s.ToString()};
if (s.IsNotFound()) {
*output = conn->NilString();
return Status::OK();
}
*output = OptionalsToString(conn, results);
return Status::OK();
}
};
class CommandJsonArrLen : public Commander {
public:
Status Execute(engine::Context &ctx, Server *svr, Connection *conn, std::string *output) override {
redis::Json json(svr->storage, conn->GetNamespace());
std::string path = "$";
if (args_.size() == 3) {
path = args_[2];
} else if (args_.size() > 3) {
return {Status::RedisExecErr, "The number of arguments is more than expected"};
}
Optionals<uint64_t> results;
auto s = json.ArrLen(ctx, args_[1], path, &results);
if (s.IsNotFound()) {
*output = conn->NilString();
return Status::OK();
}
if (!s.ok()) return {Status::RedisExecErr, s.ToString()};
*output = OptionalsToString(conn, results);
return Status::OK();
}
};
class CommandJsonMerge : public Commander {
public:
Status Execute(engine::Context &ctx, Server *svr, Connection *conn, std::string *output) override {
redis::Json json(svr->storage, conn->GetNamespace());
std::string key = args_[1];
std::string path = args_[2];
std::string value = args_[3];
bool result = false;
auto s = json.Merge(ctx, key, path, value, result);
if (!s.ok()) {
return {Status::RedisExecErr, s.ToString()};
}
if (!result) {
*output = conn->NilString();
} else {
*output = redis::RESP_OK;
}
return Status::OK();
}
};
class CommandJsonArrPop : public Commander {
public:
Status Parse([[maybe_unused]] const std::vector<std::string> &args) override {
path_ = (args_.size() > 2) ? args_[2] : "$";
if (args_.size() == 4) {
index_ = GET_OR_RET(ParseInt<int64_t>(args_[3], 10));
} else if (args_.size() > 4) {
return {Status::RedisParseErr, errWrongNumOfArguments};
}
return Status::OK();
}
Status Execute(engine::Context &ctx, Server *srv, Connection *conn, std::string *output) override {
redis::Json json(srv->storage, conn->GetNamespace());
Optionals<JsonValue> results;
auto s = json.ArrPop(ctx, args_[1], path_, index_, &results);
if (!s.ok()) return {Status::RedisExecErr, s.ToString()};
*output = redis::MultiLen(results.size());
for (const auto &data : results) {
if (data.has_value()) {
*output += redis::BulkString(GET_OR_RET(data->Print()));
} else {
*output += conn->NilString();
}
}
return Status::OK();
}
private:
std::string path_;
int64_t index_ = -1;
};
class CommandJsonObjLen : public Commander {
public:
Status Execute(engine::Context &ctx, Server *svr, Connection *conn, std::string *output) override {
redis::Json json(svr->storage, conn->GetNamespace());
std::string path = "$";
if (args_.size() == 3) {
path = args_[2];
} else if (args_.size() > 3) {
return {Status::RedisExecErr, "The number of arguments is more than expected"};
}
Optionals<uint64_t> results;
auto s = json.ObjLen(ctx, args_[1], path, &results);
if (s.IsNotFound()) {
if (args_.size() == 2) {
*output = conn->NilString();
return Status::OK();
} else {
return {Status::RedisExecErr, "Path '" + args_[2] + "' does not exist or not an object"};
}
}
if (!s.ok()) return {Status::RedisExecErr, s.ToString()};
*output = OptionalsToString(conn, results);
return Status::OK();
}
};
class CommandJsonArrTrim : public Commander {
public:
Status Parse([[maybe_unused]] const std::vector<std::string> &args) override {
path_ = args_[2];
start_ = GET_OR_RET(ParseInt<int64_t>(args_[3], 10));
stop_ = GET_OR_RET(ParseInt<int64_t>(args_[4], 10));
return Status::OK();
}
Status Execute(engine::Context &ctx, Server *srv, Connection *conn, std::string *output) override {
redis::Json json(srv->storage, conn->GetNamespace());
Optionals<uint64_t> results;
auto s = json.ArrTrim(ctx, args_[1], path_, start_, stop_, &results);
if (s.IsNotFound()) {
return {Status::RedisExecErr, "could not perform this operation on a key that doesn't exist"};
}
if (!s.ok()) return {Status::RedisExecErr, s.ToString()};
*output = OptionalsToString(conn, results);
return Status::OK();
}
private:
std::string path_;
int64_t start_ = 0;
int64_t stop_ = 0;
};
class CommanderJsonArrIndex : public Commander {
public:
Status Parse(const std::vector<std::string> &args) override {
if (args.size() > 6) {
return {Status::RedisExecErr, errWrongNumOfArguments};
}
start_ = 0;
end_ = std::numeric_limits<ssize_t>::max();
if (args.size() > 4) {
start_ = GET_OR_RET(ParseInt<ssize_t>(args[4], 10));
}
if (args.size() > 5) {
end_ = GET_OR_RET(ParseInt<ssize_t>(args[5], 10));
}
return Status::OK();
}
Status Execute(engine::Context &ctx, Server *svr, Connection *conn, std::string *output) override {
redis::Json json(svr->storage, conn->GetNamespace());
Optionals<ssize_t> results;
auto s = json.ArrIndex(ctx, args_[1], args_[2], args_[3], start_, end_, &results);
if (s.IsNotFound()) {
*output = conn->NilString();
return Status::OK();
}
if (!s.ok()) return {Status::RedisExecErr, s.ToString()};
*output = OptionalsToString(conn, results);
return Status::OK();
}
private:
ssize_t start_;
ssize_t end_;
};
class CommandJsonDel : public Commander {
public:
Status Execute(engine::Context &ctx, Server *svr, Connection *conn, std::string *output) override {
redis::Json json(svr->storage, conn->GetNamespace());
size_t result = 0;
std::string path = "$";
if (args_.size() == 3) {
path = args_[2];
} else if (args_.size() > 3) {
return {Status::RedisExecErr, "The number of arguments is more than expected"};
}
auto s = json.Del(ctx, args_[1], path, &result);
if (s.IsNotFound()) {
*output = conn->NilString();
return Status::OK();
}
if (!s.ok()) return {Status::RedisExecErr, s.ToString()};
*output = redis::Integer(result);
return Status::OK();
}
};
class CommandJsonNumIncrBy : public Commander {
public:
Status Execute(engine::Context &ctx, Server *svr, Connection *conn, std::string *output) override {
redis::Json json(svr->storage, conn->GetNamespace());
JsonValue result = JsonValue::FromString("[]").GetValue();
auto s = json.NumIncrBy(ctx, args_[1], args_[2], args_[3], &result);
if (!s.ok()) {
return {Status::RedisExecErr, s.ToString()};
}
*output = redis::BulkString(result.value.to_string());
return Status::OK();
}
};
class CommandJsonNumMultBy : public Commander {
public:
Status Execute(engine::Context &ctx, Server *svr, Connection *conn, std::string *output) override {
redis::Json json(svr->storage, conn->GetNamespace());
JsonValue result = JsonValue::FromString("[]").GetValue();
auto s = json.NumMultBy(ctx, args_[1], args_[2], args_[3], &result);
if (!s.ok()) {
return {Status::RedisExecErr, s.ToString()};
}
*output = redis::BulkString(result.value.to_string());
return Status::OK();
}
};
class CommandJsonStrAppend : public Commander {
public:
Status Execute(engine::Context &ctx, Server *svr, Connection *conn, std::string *output) override {
redis::Json json(svr->storage, conn->GetNamespace());
// path default, if not provided
std::string path = "$";
if (args_.size() == 4) {
path = args_[2];
} else if (args_.size() > 4) {
return {Status::RedisExecErr, "The number of arguments is more than expected"};
}
Optionals<uint64_t> results;
auto s = json.StrAppend(ctx, args_[1], path, args_[3], &results);
if (!s.ok()) return {Status::RedisExecErr, s.ToString()};
*output = OptionalsToString(conn, results);
return Status::OK();
}
};
class CommandJsonStrLen : public Commander {
public:
Status Execute(engine::Context &ctx, Server *svr, Connection *conn, std::string *output) override {
redis::Json json(svr->storage, conn->GetNamespace());
std::string path = "$";
if (args_.size() == 3) {
path = args_[2];
} else if (args_.size() > 3) {
return {Status::RedisExecErr, "The number of arguments is more than expected"};
}
Optionals<uint64_t> results;
auto s = json.StrLen(ctx, args_[1], path, &results);
if (s.IsNotFound()) {
if (args_.size() == 2) {
*output = conn->NilString();
return Status::OK();
} else {
return {Status::RedisExecErr, "could not perform this operation on a key that doesn't exist"};
}
}
if (!s.ok()) return {Status::RedisExecErr, s.ToString()};
*output = OptionalsToString(conn, results);
return Status::OK();
}
};
class CommandJsonMGet : public Commander {
public:
Status Execute(engine::Context &ctx, Server *svr, Connection *conn, std::string *output) override {
redis::Json json(svr->storage, conn->GetNamespace());
std::string path = args_[args_.size() - 1];
std::vector<std::string> user_keys;
for (size_t i = 1; i + 1 < args_.size(); i++) {
user_keys.emplace_back(args_[i]);
}
std::vector<JsonValue> json_values;
auto statuses = json.MGet(ctx, user_keys, path, json_values);
std::vector<std::string> values;
values.resize(user_keys.size());
for (size_t i = 0; i < statuses.size(); i++) {
if (statuses[i].ok()) {
values[i] = json_values[i].value.to_string();
}
}
*output = conn->MultiBulkString(values, statuses);
return Status::OK();
}
};
class CommandJsonMSet : public Commander {
public:
Status Execute(engine::Context &ctx, Server *svr, Connection *conn, std::string *output) override {
if ((args_.size() - 1) % 3 != 0) {
return {Status::RedisExecErr, errWrongNumOfArguments};
}
redis::Json json(svr->storage, conn->GetNamespace());
std::vector<std::string> user_keys;
std::vector<std::string> paths;
std::vector<std::string> values;
for (size_t i = 0; i < (args_.size() - 1) / 3; i++) {
user_keys.emplace_back(args_[i * 3 + 1]);
paths.emplace_back(args_[i * 3 + 2]);
values.emplace_back(args_[i * 3 + 3]);
}
if (auto s = json.MSet(ctx, user_keys, paths, values); !s.ok()) return {Status::RedisExecErr, s.ToString()};
*output = redis::RESP_OK;
return Status::OK();
}
};
class CommandJsonDebug : public Commander {
public:
Status Execute(engine::Context &ctx, 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, "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(ctx, 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();
}
};
class CommandJsonResp : public Commander {
public:
Status Execute([[maybe_unused]] engine::Context &ctx, Server *svr, Connection *conn, std::string *output) override {
redis::Json json(svr->storage, conn->GetNamespace());
std::string path = "$";
if (args_.size() == 3) {
path = args_[2];
} else if (args_.size() > 3) {
return {Status::RedisExecErr, "The number of arguments is more than expected"};
}
std::vector<std::string> results;
auto s = json.Resp(ctx, args_[1], path, &results, conn->GetProtocolVersion());
if (s.IsNotFound()) {
*output = conn->NilString();
return Status::OK();
}
if (!s.ok()) return {Status::RedisExecErr, s.ToString()};
if (args_.size() == 2) {
output->append(results.back());
} else {
output->append(MultiLen(results.size()));
for (const auto &result : results) {
output->append(result);
}
}
return Status::OK();
}
};
REDIS_REGISTER_COMMANDS(JSON, 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),
MakeCmdAttr<CommandJsonType>("json.type", -2, "read-only", 1, 1, 1),
MakeCmdAttr<CommandJsonArrAppend>("json.arrappend", -4, "write", 1, 1, 1),
MakeCmdAttr<CommandJsonArrInsert>("json.arrinsert", -5, "write", 1, 1, 1),
MakeCmdAttr<CommandJsonArrTrim>("json.arrtrim", 5, "write no-dbsize-check", 1, 1, 1),
MakeCmdAttr<CommandJsonClear>("json.clear", -2, "write no-dbsize-check", 1, 1, 1),
MakeCmdAttr<CommandJsonToggle>("json.toggle", -2, "write", 1, 1, 1),
MakeCmdAttr<CommandJsonArrLen>("json.arrlen", -2, "read-only", 1, 1, 1),
MakeCmdAttr<CommandJsonMerge>("json.merge", 4, "write", 1, 1, 1),
MakeCmdAttr<CommandJsonObjkeys>("json.objkeys", -2, "read-only", 1, 1, 1),
MakeCmdAttr<CommandJsonArrPop>("json.arrpop", -2, "write", 1, 1, 1),
MakeCmdAttr<CommanderJsonArrIndex>("json.arrindex", -4, "read-only", 1, 1, 1),
MakeCmdAttr<CommandJsonDel>("json.del", -2, "write no-dbsize-check", 1, 1, 1),
// JSON.FORGET is an alias for JSON.DEL, refer: https://redis.io/commands/json.forget/
MakeCmdAttr<CommandJsonDel>("json.forget", -2, "write", 1, 1, 1),
MakeCmdAttr<CommandJsonNumIncrBy>("json.numincrby", 4, "write", 1, 1, 1),
MakeCmdAttr<CommandJsonNumMultBy>("json.nummultby", 4, "write", 1, 1, 1),
MakeCmdAttr<CommandJsonObjLen>("json.objlen", -2, "read-only", 1, 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<CommandJsonDebug>("json.debug", -3, "read-only", 2, 2, 1),
MakeCmdAttr<CommandJsonResp>("json.resp", -2, "read-only", 1, 1, 1));
} // namespace redis