blob: 4763f5032a640b6cdd5a114d6d3aa85e1e2c3254 [file] [log] [blame]
/*
* 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.
*
*/
#pragma once
#include <algorithm>
#include <cmath>
#include <cstddef>
#include <jsoncons/json.hpp>
#include <jsoncons/json_error.hpp>
#include <jsoncons/json_options.hpp>
#include <jsoncons_ext/cbor/cbor.hpp>
#include <jsoncons_ext/cbor/cbor_encoder.hpp>
#include <jsoncons_ext/cbor/cbor_options.hpp>
#include <jsoncons_ext/jsonpath/flatten.hpp>
#include <jsoncons_ext/jsonpath/json_query.hpp>
#include <jsoncons_ext/jsonpath/jsonpath_error.hpp>
#include <jsoncons_ext/mergepatch/mergepatch.hpp>
#include <limits>
#include <string>
#include "common/string_util.h"
#include "jsoncons_ext/jsonpath/jsonpath_error.hpp"
#include "server/redis_reply.h"
#include "status.h"
#include "storage/redis_metadata.h"
template <class T>
using Optionals = std::vector<std::optional<T>>;
struct JsonValue {
enum class NumOpEnum : uint8_t {
Incr = 1,
Mul = 2,
};
static const size_t default_max_nesting_depth = 1024;
JsonValue() = default;
explicit JsonValue(jsoncons::basic_json<char> value) : value(std::move(value)) {}
static StatusOr<JsonValue> FromString(std::string_view str, int max_nesting_depth = default_max_nesting_depth) {
jsoncons::json val;
jsoncons::json_options options;
options.max_nesting_depth(max_nesting_depth);
try {
val = jsoncons::json::parse(str, options);
} catch (const jsoncons::ser_error &e) {
return {Status::NotOK, e.what()};
}
return JsonValue(std::move(val));
}
static StatusOr<JsonValue> FromCBOR(std::string_view str, int max_nesting_depth = default_max_nesting_depth) {
jsoncons::json val;
jsoncons::cbor::cbor_options options;
options.max_nesting_depth(max_nesting_depth);
try {
val = jsoncons::cbor::decode_cbor<jsoncons::json>(str, options);
} catch (const jsoncons::ser_error &e) {
return {Status::NotOK, e.what()};
}
return JsonValue(std::move(val));
}
StatusOr<std::string> Dump(int max_nesting_depth = default_max_nesting_depth) const {
std::string res;
GET_OR_RET(Dump(&res, max_nesting_depth));
return res;
}
Status Dump(std::string *buffer, int max_nesting_depth = default_max_nesting_depth) const {
jsoncons::json_options options;
options.max_nesting_depth(max_nesting_depth);
jsoncons::compact_json_string_encoder encoder{*buffer, options};
std::error_code ec;
value.dump(encoder, ec);
if (ec) {
return {Status::NotOK, ec.message()};
}
return Status::OK();
}
StatusOr<std::string> DumpCBOR(int max_nesting_depth = default_max_nesting_depth) const {
std::string res;
GET_OR_RET(DumpCBOR(&res, max_nesting_depth));
return res;
}
Status DumpCBOR(std::string *buffer, int max_nesting_depth = default_max_nesting_depth) const {
jsoncons::cbor::cbor_options options;
options.max_nesting_depth(max_nesting_depth);
jsoncons::cbor::basic_cbor_encoder<jsoncons::string_sink<std::string>> encoder{*buffer, options};
std::error_code ec;
value.dump(encoder, ec);
if (ec) {
return {Status::NotOK, ec.message()};
}
return Status::OK();
}
StatusOr<std::string> Print(uint8_t indent_size = 0, bool spaces_after_colon = false,
const std::string &new_line_chars = "") const {
std::string res;
GET_OR_RET(Print(&res, indent_size, spaces_after_colon, new_line_chars));
return res;
}
Status Print(std::string *buffer, uint8_t indent_size = 0, bool spaces_after_colon = false,
const std::string &new_line_chars = "") const {
jsoncons::json_options options;
options.indent_size(indent_size);
options.spaces_around_colon(spaces_after_colon ? jsoncons::spaces_option::space_after
: jsoncons::spaces_option::no_spaces);
options.spaces_around_comma(jsoncons::spaces_option::no_spaces);
options.new_line_chars(new_line_chars);
jsoncons::json_string_encoder encoder{*buffer, options};
std::error_code ec;
value.dump(encoder, ec);
if (ec) {
return {Status::NotOK, ec.message()};
}
return Status::OK();
}
Status Set(std::string_view path, JsonValue &&new_value) {
try {
bool is_set = false;
jsoncons::jsonpath::json_replace(value, path,
[&new_value, &is_set](const std::string & /*path*/, jsoncons::json &origin) {
origin = new_value.value;
is_set = true;
});
if (!is_set) {
// NOTE: this is a workaround since jsonpath doesn't support replace for nonexistent paths in jsoncons
// and in this workaround we can only accept normalized path
// refer to https://github.com/danielaparker/jsoncons/issues/496
jsoncons::jsonpath::json_location location = jsoncons::jsonpath::json_location::parse(path);
jsoncons::jsonpath::replace(value, location, new_value.value, true);
}
} catch (const jsoncons::jsonpath::jsonpath_error &e) {
return {Status::NotOK, e.what()};
}
return Status::OK();
}
StatusOr<Optionals<uint64_t>> StrAppend(std::string_view path, const std::string &append_value) {
Optionals<uint64_t> results;
try {
std::string append_str;
jsoncons::json append_json = jsoncons::json::parse(append_value);
if (append_json.is_string()) {
append_str = append_json.as_string();
} else {
return {Status::NotOK, "STRAPPEND need input a string to append"};
}
jsoncons::jsonpath::json_replace(value, path,
[&append_str, &results](const std::string & /*path*/, jsoncons::json &origin) {
if (origin.is_string()) {
auto origin_str = origin.as_string();
results.emplace_back(origin_str.length() + append_str.length());
origin = origin_str + append_str;
} else {
results.emplace_back(std::nullopt);
}
});
} catch (const jsoncons::jsonpath::jsonpath_error &e) {
return {Status::NotOK, e.what()};
}
std::reverse(results.begin(), results.end());
return results;
}
StatusOr<Optionals<uint64_t>> StrLen(std::string_view path) const {
Optionals<uint64_t> results;
try {
jsoncons::jsonpath::json_query(value, path,
[&results](const std::string & /*path*/, const jsoncons::json &origin) {
if (origin.is_string()) {
results.emplace_back(origin.as_string().length());
} else {
results.emplace_back(std::nullopt);
}
});
} catch (const jsoncons::jsonpath::jsonpath_error &e) {
return {Status::NotOK, e.what()};
}
return results;
}
StatusOr<std::vector<size_t>> GetBytes(std::string_view path, JsonStorageFormat format,
int max_nesting_depth = default_max_nesting_depth) 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 s;
return results;
}
StatusOr<JsonValue> Get(std::string_view path) const {
try {
return jsoncons::jsonpath::json_query(value, path);
} catch (const jsoncons::jsonpath::jsonpath_error &e) {
return {Status::NotOK, e.what()};
}
}
StatusOr<Optionals<size_t>> ArrAppend(std::string_view path, const std::vector<jsoncons::json> &append_values) {
Optionals<size_t> results;
try {
jsoncons::jsonpath::json_replace(
value, path, [&append_values, &results](const std::string & /*path*/, jsoncons::json &val) {
if (val.is_array()) {
val.insert(val.array_range().end(), append_values.begin(), append_values.end());
results.emplace_back(val.size());
} else {
results.emplace_back(std::nullopt);
}
});
} catch (const jsoncons::jsonpath::jsonpath_error &e) {
return {Status::NotOK, e.what()};
}
return results;
}
StatusOr<Optionals<uint64_t>> ArrInsert(std::string_view path, const int64_t &index,
const std::vector<jsoncons::json> &insert_values) {
Optionals<uint64_t> results;
try {
jsoncons::jsonpath::json_replace(
value, path, [&insert_values, &results, index](const std::string & /*path*/, jsoncons::json &val) {
if (val.is_array()) {
auto len = static_cast<int64_t>(val.size());
// When index >= 0, we need index <= len (allow insertion at end)
// when index < 0, we need index >= -len.
if (index > len || index < -len) {
results.emplace_back(std::nullopt);
return;
}
auto base_iter = index >= 0 ? val.array_range().begin() : val.array_range().end();
val.insert(base_iter + index, insert_values.begin(), insert_values.end());
results.emplace_back(val.size());
} else {
results.emplace_back(std::nullopt);
}
});
} catch (const jsoncons::jsonpath::jsonpath_error &e) {
return {Status::NotOK, e.what()};
}
return results;
}
static std::pair<ssize_t, ssize_t> NormalizeArrIndices(ssize_t start, ssize_t end, ssize_t len) {
if (start < 0) {
start = std::max<ssize_t>(0, len + start);
} else {
start = std::min<ssize_t>(start, len - 1);
}
if (end == 0) {
end = len;
} else if (end < 0) {
end = std::max<ssize_t>(0, len + end);
}
end = std::min<ssize_t>(end, len);
return {start, end};
}
StatusOr<Optionals<ssize_t>> ArrIndex(std::string_view path, const jsoncons::json &needle, ssize_t start,
ssize_t end) const {
Optionals<ssize_t> results;
try {
jsoncons::jsonpath::json_query(value, path, [&](const std::string & /*path*/, const jsoncons::json &val) {
if (!val.is_array()) {
results.emplace_back(std::nullopt);
return;
}
auto [pstart, pend] = NormalizeArrIndices(start, end, static_cast<ssize_t>(val.size()));
auto arr_begin = val.array_range().begin();
auto begin_it = arr_begin + pstart;
auto end_it = arr_begin + pend;
auto it = std::find(begin_it, end_it, needle);
if (it != end_it) {
results.emplace_back(it - arr_begin);
return;
}
// index not found
results.emplace_back(-1);
});
} catch (const jsoncons::jsonpath::jsonpath_error &e) {
return {Status::NotOK, e.what()};
}
return results;
}
StatusOr<std::vector<std::string>> Type(std::string_view path) const {
std::vector<std::string> types;
try {
jsoncons::jsonpath::json_query(value, path, [&types](const std::string & /*path*/, const jsoncons::json &val) {
switch (val.type()) {
case jsoncons::json_type::null_value:
types.emplace_back("null");
break;
case jsoncons::json_type::bool_value:
types.emplace_back("boolean");
break;
case jsoncons::json_type::int64_value:
case jsoncons::json_type::uint64_value:
types.emplace_back("integer");
break;
case jsoncons::json_type::double_value:
types.emplace_back("number");
break;
case jsoncons::json_type::string_value:
types.emplace_back("string");
break;
case jsoncons::json_type::array_value:
types.emplace_back("array");
break;
case jsoncons::json_type::object_value:
types.emplace_back("object");
break;
default:
types.emplace_back("unknown");
break;
}
});
} catch (const jsoncons::jsonpath::jsonpath_error &e) {
return {Status::NotOK, e.what()};
}
return types;
}
StatusOr<Optionals<bool>> Toggle(std::string_view path) {
Optionals<bool> results;
try {
jsoncons::jsonpath::json_replace(value, path, [&results](const std::string & /*path*/, jsoncons::json &val) {
if (val.is_bool()) {
val = !val.as_bool();
results.emplace_back(val.as_bool());
} else {
results.emplace_back(std::nullopt);
}
});
} catch (const jsoncons::jsonpath::jsonpath_error &e) {
return {Status::NotOK, e.what()};
}
std::reverse(results.begin(), results.end());
return results;
}
StatusOr<size_t> Clear(std::string_view path) {
size_t count = 0;
try {
jsoncons::jsonpath::json_replace(value, path, [&count](const std::string & /*path*/, jsoncons::json &val) {
bool is_array = val.is_array() && !val.empty();
bool is_object = val.is_object() && !val.empty();
bool is_number = val.is_number() && val.as<double>() != 0;
if (is_array)
val = jsoncons::json::array();
else if (is_object)
val = jsoncons::json::object();
else if (is_number)
val = 0;
else
return;
count++;
});
} catch (const jsoncons::jsonpath::jsonpath_error &e) {
return {Status::NotOK, e.what()};
}
return count;
}
StatusOr<Optionals<uint64_t>> ArrLen(std::string_view path) const {
Optionals<uint64_t> results;
try {
jsoncons::jsonpath::json_query(value, path,
[&results](const std::string & /*path*/, const jsoncons::json &basic_json) {
if (basic_json.is_array()) {
results.emplace_back(static_cast<uint64_t>(basic_json.size()));
} else {
results.emplace_back(std::nullopt);
}
});
} catch (const jsoncons::jsonpath::jsonpath_error &e) {
return {Status::NotOK, e.what()};
}
return results;
}
StatusOr<bool> Merge(const std::string_view path, const std::string &merge_value) {
bool is_updated = false;
const std::string json_root_path = "$";
try {
jsoncons::json patch_value = jsoncons::json::parse(merge_value);
bool not_exists = jsoncons::jsonpath::json_query(value, path).empty();
if (not_exists) {
// NOTE: this is a workaround since jsonpath doesn't support replace for nonexistent paths in jsoncons
// and in this workaround we can only accept normalized path
// refer to https://github.com/danielaparker/jsoncons/issues/496
jsoncons::jsonpath::json_location location = jsoncons::jsonpath::json_location::parse(path);
jsoncons::jsonpath::replace(value, location, patch_value, true);
is_updated = true;
} else if (path == json_root_path) {
// Merge with the root. Patch function complies with RFC7396 Json Merge Patch
jsoncons::mergepatch::apply_merge_patch(value, patch_value);
is_updated = true;
} else if (!patch_value.is_null()) {
// Replace value by path
jsoncons::jsonpath::json_replace(
value, path, [&patch_value, &is_updated](const std::string & /*path*/, jsoncons::json &target) {
jsoncons::mergepatch::apply_merge_patch(target, patch_value);
is_updated = true;
});
} else {
// Handle null case
jsoncons::jsonpath::remove(value, path);
is_updated = true;
}
} catch (const jsoncons::jsonpath::jsonpath_error &e) {
return {Status::NotOK, e.what()};
} catch (const jsoncons::ser_error &e) {
return {Status::NotOK, e.what()};
}
return is_updated;
}
StatusOr<Optionals<std::vector<std::string>>> ObjKeys(std::string_view path) const {
Optionals<std::vector<std::string>> keys;
try {
jsoncons::jsonpath::json_query(value, path,
[&keys](const std::string & /*path*/, const jsoncons::json &basic_json) {
if (basic_json.is_object()) {
std::vector<std::string> ret;
for (const auto &member : basic_json.object_range()) {
ret.push_back(member.key());
}
keys.emplace_back(ret);
} else {
keys.emplace_back(std::nullopt);
}
});
} catch (const jsoncons::jsonpath::jsonpath_error &e) {
return {Status::NotOK, e.what()};
}
return keys;
}
StatusOr<Optionals<uint64_t>> ObjLen(std::string_view path) const {
Optionals<uint64_t> obj_lens;
try {
jsoncons::jsonpath::json_query(value, path,
[&obj_lens](const std::string & /*path*/, const jsoncons::json &basic_json) {
if (basic_json.is_object()) {
obj_lens.emplace_back(static_cast<uint64_t>(basic_json.size()));
} else {
obj_lens.emplace_back(std::nullopt);
}
});
} catch (const jsoncons::jsonpath::jsonpath_error &e) {
return {Status::NotOK, e.what()};
}
return obj_lens;
}
StatusOr<Optionals<JsonValue>> ArrPop(std::string_view path, int64_t index = -1) {
Optionals<JsonValue> popped_values;
try {
jsoncons::jsonpath::json_replace(value, path,
[&popped_values, index](const std::string & /*path*/, jsoncons::json &val) {
if (val.is_array() && !val.empty()) {
auto len = static_cast<int64_t>(val.size());
auto popped_iter = val.array_range().begin();
if (index < 0) {
popped_iter += len - std::min(len, -index);
} else if (index > 0) {
popped_iter += std::min(len - 1, index);
}
popped_values.emplace_back(*popped_iter);
val.erase(popped_iter);
} else {
popped_values.emplace_back(std::nullopt);
}
});
} catch (const jsoncons::jsonpath::jsonpath_error &e) {
return {Status::NotOK, e.what()};
}
return popped_values;
}
StatusOr<Optionals<uint64_t>> ArrTrim(std::string_view path, int64_t start, int64_t stop) {
Optionals<uint64_t> results;
try {
jsoncons::jsonpath::json_replace(
value, path, [&results, start, stop](const std::string & /*path*/, jsoncons::json &val) {
if (val.is_array()) {
auto len = static_cast<int64_t>(val.size());
auto begin_index = start < 0 ? std::max(len + start, static_cast<int64_t>(0)) : start;
auto end_index = std::min(stop < 0 ? std::max(len + stop, static_cast<int64_t>(0)) : stop, len - 1);
if (begin_index >= len || begin_index > end_index) {
val = jsoncons::json::array();
results.emplace_back(0);
return;
}
auto n_val = jsoncons::json::array();
auto begin_iter = val.array_range().begin();
n_val.insert(n_val.end(), begin_iter + begin_index, begin_iter + end_index + 1);
val = n_val;
results.emplace_back(static_cast<int64_t>(n_val.size()));
} else {
results.emplace_back(std::nullopt);
}
});
} catch (const jsoncons::jsonpath::jsonpath_error &e) {
return {Status::NotOK, e.what()};
}
return results;
}
StatusOr<size_t> Del(const std::string &path) {
size_t count = 0;
try {
count = jsoncons::jsonpath::remove(value, path);
} catch (const jsoncons::jsonpath::jsonpath_error &e) {
return {Status::NotOK, e.what()};
}
return count;
}
Status NumOp(std::string_view path, const JsonValue &number, NumOpEnum op, JsonValue *result) {
Status status = Status::OK();
try {
jsoncons::jsonpath::json_replace(value, path, [&](const std::string & /*path*/, jsoncons::json &origin) {
if (!status.IsOK()) {
return;
}
// is_number() will return true
// if it's actually a string but can convert to a number
// so here we should exclude such case
if (!origin.is_number() || origin.is_string()) {
result->value.push_back(jsoncons::json::null());
return;
}
double v = 0;
if (op == NumOpEnum::Incr) {
v = origin.as_double() + number.value.as_double();
} else if (op == NumOpEnum::Mul) {
v = origin.as_double() * number.value.as_double();
}
if (std::isinf(v)) {
status = {Status::RedisExecErr, "the result is an infinite number"};
return;
}
double v_int = 0;
if (std::modf(v, &v_int) == 0 && double(std::numeric_limits<int64_t>::min()) < v &&
v < double(std::numeric_limits<int64_t>::max())) {
origin = int64_t(v);
} else {
origin = v;
}
result->value.push_back(origin);
});
} catch (const jsoncons::jsonpath::jsonpath_error &e) {
return {Status::NotOK, e.what()};
}
return status;
}
static void TransformResp(const jsoncons::json &origin, std::string &json_resp, redis::RESP resp) {
if (origin.is_object()) {
json_resp += redis::MultiLen(origin.size() * 2 + 1);
json_resp += redis::SimpleString("{");
for (const auto &json_kv : origin.object_range()) {
json_resp += redis::BulkString(json_kv.key());
TransformResp(json_kv.value(), json_resp, resp);
}
} else if (origin.is_int64() || origin.is_uint64()) {
json_resp += redis::Integer(origin.as_integer<int64_t>());
} else if (origin.is_string() || origin.is_double()) {
json_resp += redis::BulkString(origin.as_string());
} else if (origin.is_bool()) {
json_resp += redis::SimpleString(origin.as_bool() ? "true" : "false");
} else if (origin.is_null()) {
json_resp += redis::NilString(resp);
} else if (origin.is_array()) {
json_resp += redis::MultiLen(origin.size() + 1);
json_resp += redis::SimpleString("[");
for (const auto &json_array_value : origin.array_range()) {
TransformResp(json_array_value, json_resp, resp);
}
}
}
StatusOr<std::vector<std::string>> ConvertToResp(std::string_view path, redis::RESP resp) const {
std::vector<std::string> json_resps;
try {
jsoncons::jsonpath::json_query(value, path, [&](const std::string & /*path*/, const jsoncons::json &origin) {
std::string json_resp;
TransformResp(origin, json_resp, resp);
json_resps.emplace_back(json_resp);
});
} catch (const jsoncons::jsonpath::jsonpath_error &e) {
return {Status::NotOK, e.what()};
}
return json_resps;
}
JsonValue(const JsonValue &) = default;
JsonValue(JsonValue &&) = default;
JsonValue &operator=(const JsonValue &) = default;
JsonValue &operator=(JsonValue &&) = default;
~JsonValue() = default;
jsoncons::json value;
};