mirror of
https://github.com/dragonflydb/dragonfly.git
synced 2025-05-11 10:25:47 +02:00
feat: support subrange expressions in jsonpathv2 (#3036)
Support for expressions like `$.arr[1:-2]`. The change applies to our own implementation of jsonpath, jsoncons already supports this format. Signed-off-by: Roman Gershman <roman@dragonflydb.io>
This commit is contained in:
parent
b7fdc41d2e
commit
a9956e9723
6 changed files with 65 additions and 3 deletions
|
@ -135,6 +135,10 @@ TEST_F(JsonTest, Path) {
|
|||
jsonpath::json_query(j2, "$.field[-1]", [&](const std::string& path, const json& val) {
|
||||
EXPECT_EQ(5, val.as<int>());
|
||||
});
|
||||
|
||||
jsonpath::json_query(j2, "$.field[-6:1]", [&](const std::string& path, const json& val) {
|
||||
EXPECT_EQ(1, val.as<int>());
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(JsonTest, Delete) {
|
||||
|
|
|
@ -47,6 +47,7 @@ using namespace std;
|
|||
%token
|
||||
LBRACKET "["
|
||||
RBRACKET "]"
|
||||
COLON ":"
|
||||
LPARENT "("
|
||||
RPARENT ")"
|
||||
ROOT "$"
|
||||
|
@ -86,6 +87,7 @@ identifier: UNQ_STR
|
|||
|
||||
bracket_index: WILDCARD { $$ = PathSegment{SegmentType::INDEX, IndexExpr::All()}; }
|
||||
| INT { $$ = PathSegment(SegmentType::INDEX, IndexExpr($1, $1)); }
|
||||
| INT COLON INT { $$ = PathSegment(SegmentType::INDEX, IndexExpr::HalfOpen($1, $3)); }
|
||||
|
||||
function_expr: UNQ_STR { driver->AddFunction($1); } LPARENT ROOT relative_location RPARENT
|
||||
%%
|
||||
|
|
|
@ -45,6 +45,7 @@
|
|||
"$" return Parser::make_ROOT(loc());
|
||||
".." return Parser::make_DESCENT(loc());
|
||||
"." return Parser::make_DOT(loc());
|
||||
":" return Parser::make_COLON(loc());
|
||||
"[" return Parser::make_LBRACKET(loc());
|
||||
"]" return Parser::make_RBRACKET(loc());
|
||||
"*" return Parser::make_WILDCARD(loc());
|
||||
|
|
|
@ -479,4 +479,46 @@ TYPED_TEST(JsonPathTest, Mutate) {
|
|||
}
|
||||
}
|
||||
|
||||
TYPED_TEST(JsonPathTest, SubRange) {
|
||||
TypeParam json = ValidJson<TypeParam>(R"({"arr": [1, 2, 3, 4, 5]})");
|
||||
ASSERT_EQ(0, this->Parse("$.arr[1:2]"));
|
||||
Path path = this->driver_.TakePath();
|
||||
ASSERT_EQ(2, path.size());
|
||||
EXPECT_THAT(path[1], SegType(SegmentType::INDEX));
|
||||
|
||||
vector<int> arr;
|
||||
auto cb = [&arr](optional<string_view> key, const TypeParam& val) {
|
||||
ASSERT_FALSE(key);
|
||||
arr.push_back(to_int(val));
|
||||
};
|
||||
|
||||
EvaluatePath(path, json, cb);
|
||||
ASSERT_THAT(arr, ElementsAre(2));
|
||||
arr.clear();
|
||||
|
||||
ASSERT_EQ(0, this->Parse("$.arr[0:2]"));
|
||||
path = this->driver_.TakePath();
|
||||
EvaluatePath(path, json, cb);
|
||||
ASSERT_THAT(arr, ElementsAre(1, 2));
|
||||
arr.clear();
|
||||
|
||||
ASSERT_EQ(0, this->Parse("$.arr[2:-1]"));
|
||||
path = this->driver_.TakePath();
|
||||
EvaluatePath(path, json, cb);
|
||||
ASSERT_THAT(arr, ElementsAre(3, 4));
|
||||
arr.clear();
|
||||
|
||||
ASSERT_EQ(0, this->Parse("$.arr[-2:-1]"));
|
||||
path = this->driver_.TakePath();
|
||||
EvaluatePath(path, json, cb);
|
||||
ASSERT_THAT(arr, ElementsAre(4));
|
||||
arr.clear();
|
||||
|
||||
ASSERT_EQ(0, this->Parse("$.arr[-2:-2]"));
|
||||
path = this->driver_.TakePath();
|
||||
EvaluatePath(path, json, cb);
|
||||
ASSERT_THAT(arr, ElementsAre());
|
||||
arr.clear();
|
||||
}
|
||||
|
||||
} // namespace dfly::json
|
||||
|
|
|
@ -55,14 +55,19 @@ IndexExpr IndexExpr::Normalize(size_t array_len) const {
|
|||
return IndexExpr(1, 0); // empty range.
|
||||
|
||||
IndexExpr res = *this;
|
||||
auto wrap = [=](int negative) {
|
||||
unsigned positive = -negative;
|
||||
return positive > array_len ? 0 : array_len - positive;
|
||||
};
|
||||
|
||||
if (res.second >= int(array_len)) {
|
||||
res.second = array_len - 1;
|
||||
} else if (res.second < 0) {
|
||||
res.second = res.second % array_len;
|
||||
res.second = wrap(res.second);
|
||||
DCHECK_GE(res.second, 0);
|
||||
}
|
||||
if (res.first < 0) {
|
||||
res.first = res.first % array_len;
|
||||
res.first = wrap(res.first);
|
||||
DCHECK_GE(res.first, 0);
|
||||
}
|
||||
return res;
|
||||
|
|
|
@ -55,9 +55,10 @@ class AggFunction {
|
|||
int valid_ = -1;
|
||||
};
|
||||
|
||||
// Bracket index representation
|
||||
// Bracket index representation, IndexExpr is a closed range, i.e. both ends are inclusive.
|
||||
// Single index is: <I, I>, wildcard: <0, INT_MAX>,
|
||||
// [begin:end): <begin, end - 1>
|
||||
// IndexExpr is 0-based, with negative indices referring to the array size of the applied object.
|
||||
struct IndexExpr : public std::pair<int, int> {
|
||||
bool Empty() const {
|
||||
return first > second;
|
||||
|
@ -68,7 +69,14 @@ struct IndexExpr : public std::pair<int, int> {
|
|||
}
|
||||
|
||||
using pair::pair;
|
||||
|
||||
// Returns subrange with length `array_len`.
|
||||
IndexExpr Normalize(size_t array_len) const;
|
||||
|
||||
// Returns IndexExpr representing [left_closed, right_open) range.
|
||||
static IndexExpr HalfOpen(int left_closed, int right_open) {
|
||||
return IndexExpr(left_closed, right_open - 1);
|
||||
}
|
||||
};
|
||||
|
||||
class PathSegment {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue