Skip to content

Commit

Permalink
implement .**
Browse files Browse the repository at this point in the history
Signed-off-by: Runji Wang <wangrunji0408@163.com>
  • Loading branch information
wangrunji0408 committed Nov 23, 2023
1 parent 3f2489d commit 95a06aa
Show file tree
Hide file tree
Showing 5 changed files with 77 additions and 8 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@ See [PostgreSQL documentation](https://www.postgresql.org/docs/16/functions-json
- [x] `.name`: An object member by name
- [x] `[*]`: Any array element
- [x] `.*`: Any object member
- [ ] `.**`: Any descendant object member (Postgres extension)
- [x] `?(expr)`: Filter expression
- [x] `.**`: Any descendant object member (Postgres extension)
- [x] `?(predicate)`: Filter expression
- [x] `==`, `!=` / `<>`, `<`, `<=`, `>`, `>=`: Comparison
- [x] `&&`, `||`, `!`: Logical operators
- [x] `is unknown`: Check if the value is unknown
Expand Down
38 changes: 36 additions & 2 deletions src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ pub enum AccessorOp {
/// `.*` represents selecting all elements in an object.
MemberWildcard,
/// `.**` represents selecting all elements in an object and its sub-objects.
RecursiveMemberWildcard(LevelRange),
DescendantMemberWildcard(LevelRange),
/// `[*]` represents selecting all elements in an array.
ElementWildcard,
/// `.<name>` represents selecting element that matched the name in an object, like `$.event`.
Expand Down Expand Up @@ -240,6 +240,40 @@ impl PathPrimary {
}
}

impl LevelRange {
/// Returns true if the given level is exceeded by this range.
pub(crate) fn exceeded(&self, level: u32) -> bool {
match self {
Self::All => false,
Self::One(Level::N(n)) => level > *n,
Self::Range(_, Level::N(end)) => level > *end,
_ => false,
}
}

/// Resolve the range.
pub(crate) fn to_range(&self, last: usize) -> std::ops::Range<usize> {
match self {
Self::All => 0..last + 1,
Self::One(level) => {
level.to_usize(last).min(last + 1)..level.to_usize(last).min(last) + 1
}
Self::Range(start, end) => {
start.to_usize(last).min(last + 1)..end.to_usize(last).min(last) + 1
}
}
}
}

impl Level {
fn to_usize(&self, last: usize) -> usize {
match self {
Self::N(n) => *n as usize,
Self::Last => last,
}
}
}

impl Display for JsonPath {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
if self.mode == Mode::Strict {
Expand Down Expand Up @@ -358,7 +392,7 @@ impl Display for AccessorOp {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Self::MemberWildcard => write!(f, ".*"),
Self::RecursiveMemberWildcard(level) => write!(f, ".**{level}"),
Self::DescendantMemberWildcard(level) => write!(f, ".**{level}"),
Self::ElementWildcard => write!(f, "[*]"),
Self::Member(field) => write!(f, ".\"{field}\""),
Self::Element(indices) => {
Expand Down
38 changes: 37 additions & 1 deletion src/eval.rs
Original file line number Diff line number Diff line change
Expand Up @@ -559,7 +559,9 @@ impl<'a, T: Json> Evaluator<'a, T> {
fn eval_accessor_op(&self, op: &AccessorOp) -> Result<Vec<Cow<'a, T>>> {
match op {
AccessorOp::MemberWildcard => self.eval_member_wildcard(),
AccessorOp::RecursiveMemberWildcard(_) => todo!("evaluate .**"),
AccessorOp::DescendantMemberWildcard(levels) => {
self.eval_descendant_member_wildcard(levels)
}
AccessorOp::ElementWildcard => self.eval_element_wildcard(),
AccessorOp::Member(name) => self.eval_member(name),
AccessorOp::Element(indices) => self.eval_element_accessor(indices),
Expand All @@ -583,6 +585,40 @@ impl<'a, T: Json> Evaluator<'a, T> {
Ok(new_set)
}

fn eval_descendant_member_wildcard(&self, levels: &LevelRange) -> Result<Vec<Cow<'a, T>>> {
let set = match self.current.as_array() {
Some(array) if self.is_lax() => array.list(),
_ => vec![self.current],
};
// expand all levels
let mut level_sets = vec![set];
for level in 1.. {
// early exit if the level is out of range
if levels.exceeded(level) {
break;
}
let mut new_set = vec![];
for v in level_sets.last().unwrap().iter() {
if let Some(object) = v.as_object() {
new_set.extend(object.list_value());
}
}
if new_set.is_empty() {
break;
}
level_sets.push(new_set);
}
// flatten the level sets in range
let last_level = level_sets.len() - 1;
let new_set = level_sets[levels.to_range(last_level)]
.iter()
.flatten()
.cloned()
.map(Cow::Borrowed)
.collect();
Ok(new_set)
}

fn eval_element_wildcard(&self) -> Result<Vec<Cow<'a, T>>> {
if !self.current.is_array() && self.is_lax() {
// wrap the current value into an array
Expand Down
4 changes: 2 additions & 2 deletions src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,7 @@ fn accessor_op(input: &str) -> IResult<&str, AccessorOp> {
alt((
map(
preceded(tag(".**"), level_range),
AccessorOp::RecursiveMemberWildcard,
AccessorOp::DescendantMemberWildcard,
),
value(AccessorOp::MemberWildcard, tag(".*")),
value(AccessorOp::ElementWildcard, element_wildcard),
Expand Down Expand Up @@ -543,7 +543,7 @@ impl Checker {
fn visit_accessor_op(&self, accessor_op: &AccessorOp) -> Result<(), &'static str> {
match accessor_op {
AccessorOp::ElementWildcard | AccessorOp::MemberWildcard => Ok(()),
AccessorOp::RecursiveMemberWildcard(_) => Ok(()),
AccessorOp::DescendantMemberWildcard(_) => Ok(()),
AccessorOp::Member(_) | AccessorOp::Method(_) => Ok(()),
AccessorOp::FilterExpr(pred) => Self {
non_root: true,
Expand Down
1 change: 0 additions & 1 deletion tests/pg_jsonb_jsonpath.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@ fn parse_script(script: &'static str) -> Vec<Trial> {
// not supported
let ignored = sql.contains(".datetime")
|| sql.contains("_tz")
|| sql.contains("**")
// invalid serde_json::Value
|| sql.contains("1e1000");

Expand Down

0 comments on commit 95a06aa

Please sign in to comment.