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

chore: move jsoncons path code into dedicated files #2666

Merged
merged 1 commit into from
Feb 27, 2024
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
2 changes: 1 addition & 1 deletion src/core/dash_internal.h
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2022, DragonflyDB authors. All rights reserved.
// Copyright 2024, DragonflyDB authors. All rights reserved.
// See LICENSE for licensing terms.
//

Expand Down
3 changes: 2 additions & 1 deletion src/core/json/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ gen_bison(jsonpath_grammar)
cur_gen_dir(gen_dir)

add_library(jsonpath lexer_impl.cc driver.cc path.cc
${gen_dir}/jsonpath_lexer.cc ${gen_dir}/jsonpath_grammar.cc json_object.cc)
${gen_dir}/jsonpath_lexer.cc ${gen_dir}/jsonpath_grammar.cc json_object.cc
detail/jsoncons_dfs.cc)
target_link_libraries(jsonpath base absl::strings TRDP::reflex TRDP::jsoncons)

cxx_test(jsonpath_test jsonpath LABELS DFLY)
Expand Down
13 changes: 13 additions & 0 deletions src/core/json/detail/common.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Copyright 2024, DragonflyDB authors. All rights reserved.
// See LICENSE for licensing terms.
//

#pragma once

namespace dfly::json::detail {
enum MatchStatus {
OUT_OF_BOUNDS,
MISMATCH,
};

}
184 changes: 184 additions & 0 deletions src/core/json/detail/jsoncons_dfs.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
// Copyright 2024, DragonflyDB authors. All rights reserved.
// See LICENSE for licensing terms.
//

// clang-format off
#include <glog/logging.h>
// clang-format on

#include "core/json/detail/jsoncons_dfs.h"

namespace dfly::json::detail {

using namespace std;
using nonstd::make_unexpected;

inline bool IsRecursive(jsoncons::json_type type) {
return type == jsoncons::json_type::object_value || type == jsoncons::json_type::array_value;
}

void Dfs::Traverse(absl::Span<const PathSegment> path, const JsonType& root, const Cb& callback) {
DCHECK(!path.empty());
if (path.size() == 1) {
PerformStep(path[0], root, callback);
return;
}

using ConstItem = JsonconsDfsItem<true>;
vector<ConstItem> stack;
stack.emplace_back(&root);

do {
unsigned segment_index = stack.back().segment_idx();
const auto& path_segment = path[segment_index];

// init or advance the current object
ConstItem::AdvanceResult res = stack.back().Advance(path_segment);
if (res && res->first != nullptr) {
const JsonType* next = res->first;
DVLOG(2) << "Handling now " << next->type() << " " << next->to_string();

// We descent only if next is object or an array.
if (IsRecursive(next->type())) {
unsigned next_seg_id = res->second;

if (next_seg_id + 1 < path.size()) {
stack.emplace_back(next, next_seg_id);
} else {
// terminal step
// TODO: to take into account MatchStatus
// for `json.set foo $.a[10]` or for `json.set foo $.*.b`
PerformStep(path[next_seg_id], *next, callback);
}
}
} else {
stack.pop_back();
}
} while (!stack.empty());
}

void Dfs::Mutate(absl::Span<const PathSegment> path, const MutateCallback& callback,
JsonType* json) {
DCHECK(!path.empty());
if (path.size() == 1) {
MutateStep(path[0], callback, json);
return;
}

using Item = detail::JsonconsDfsItem<false>;
vector<Item> stack;
stack.emplace_back(json);

do {
unsigned segment_index = stack.back().segment_idx();
const auto& path_segment = path[segment_index];

// init or advance the current object
Item::AdvanceResult res = stack.back().Advance(path_segment);
if (res && res->first != nullptr) {
JsonType* next = res->first;
DVLOG(2) << "Handling now " << next->type() << " " << next->to_string();

// We descent only if next is object or an array.
if (IsRecursive(next->type())) {
unsigned next_seg_id = res->second;

if (next_seg_id + 1 < path.size()) {
stack.emplace_back(next, next_seg_id);
} else {
MutateStep(path[next_seg_id], callback, next);
}
}
} else {
stack.pop_back();
}
} while (!stack.empty());
}

auto Dfs::PerformStep(const PathSegment& segment, const JsonType& node, const Cb& callback)
-> nonstd::expected<void, MatchStatus> {
switch (segment.type()) {
case SegmentType::IDENTIFIER: {
if (!node.is_object())
return make_unexpected(MISMATCH);

auto it = node.find(segment.identifier());
if (it != node.object_range().end()) {
DoCall(callback, it->key(), it->value());
}
} break;
case SegmentType::INDEX: {
if (!node.is_array())
return make_unexpected(MISMATCH);
if (segment.index() >= node.size()) {
return make_unexpected(OUT_OF_BOUNDS);
}
DoCall(callback, nullopt, node[segment.index()]);
} break;

case SegmentType::DESCENT:
case SegmentType::WILDCARD: {
if (node.is_object()) {
for (const auto& k_v : node.object_range()) {
DoCall(callback, k_v.key(), k_v.value());
}
} else if (node.is_array()) {
for (const auto& val : node.array_range()) {
DoCall(callback, nullopt, val);
}
}
} break;
default:
LOG(DFATAL) << "Unknown segment " << SegmentName(segment.type());
}
return {};
}

auto Dfs::MutateStep(const PathSegment& segment, const MutateCallback& cb, JsonType* node)
-> nonstd::expected<void, MatchStatus> {
switch (segment.type()) {
case SegmentType::IDENTIFIER: {
if (!node->is_object())
return make_unexpected(MISMATCH);

auto it = node->find(segment.identifier());
if (it != node->object_range().end()) {
if (Mutate(cb, it->key(), &it->value())) {
node->erase(it);
}
}
} break;
case SegmentType::INDEX: {
if (!node->is_array())
return make_unexpected(MISMATCH);
if (segment.index() >= node->size()) {
return make_unexpected(OUT_OF_BOUNDS);
}
auto it = node->array_range().begin() + segment.index();
if (Mutate(cb, nullopt, &*it)) {
node->erase(it);
}
} break;

case SegmentType::DESCENT:
case SegmentType::WILDCARD: {
if (node->is_object()) {
auto it = node->object_range().begin();
while (it != node->object_range().end()) {
it = Mutate(cb, it->key(), &it->value()) ? node->erase(it) : it + 1;
}
} else if (node->is_array()) {
auto it = node->array_range().begin();
while (it != node->array_range().end()) {
it = Mutate(cb, nullopt, &*it) ? node->erase(it) : it + 1;
}
}
} break;
case SegmentType::FUNCTION:
LOG(DFATAL) << "Function segment is not supported for mutation";
break;
}
return {};
}

} // namespace dfly::json::detail
Loading
Loading