-
Notifications
You must be signed in to change notification settings - Fork 991
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
chore: move jsoncons path code into dedicated files (#2666)
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
Showing
6 changed files
with
426 additions
and
379 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | ||
}; | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
Oops, something went wrong.