Skip to content

Commit

Permalink
chore: move jsoncons path code into dedicated files (#2666)
Browse files Browse the repository at this point in the history
It's only a code move, without functional changes.
This is in preparation to implementing the same path functionality
for flexbuffers objects.

Signed-off-by: Roman Gershman <roman@dragonflydb.io>
  • Loading branch information
romange authored Feb 27, 2024
1 parent 91c299b commit df39d3a
Show file tree
Hide file tree
Showing 6 changed files with 426 additions and 379 deletions.
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

0 comments on commit df39d3a

Please sign in to comment.