Skip to content

Commit

Permalink
[query] Add contains to Comparison #643 (#648)
Browse files Browse the repository at this point in the history
* implement contains

* Update query_condition.rs

* Update query_condition.rs

* Update queries.md
  • Loading branch information
michaelvlach authored Jul 23, 2023
1 parent 8434b39 commit 392d666
Show file tree
Hide file tree
Showing 3 changed files with 101 additions and 2 deletions.
5 changes: 4 additions & 1 deletion docs/queries.md
Original file line number Diff line number Diff line change
Expand Up @@ -650,6 +650,7 @@ pub enum Comparison {
LessThan(DbValue),
LessThanOrEqual(DbValue),
NotEqual(DbValue),
Contains(DbValue),
}
```

Expand All @@ -674,11 +675,13 @@ QueryBuilder::search().from(1).where_().not_beyond().ids("a").query();
QueryBuilder::search().from(1).where_().node().or().edge().query();
QueryBuilder::search().from(1).where_().node().and().distance().query(CountComparison::GreaterThanOrEqual(3)).query();
QueryBuilder::search().from(1).where_().node().or().where_().edge().and().key("k").value(Comparison::Equal(1.into())).end_where().query();
QueryBuilder::search().from(1).where_().node().or().where_().edge().and().key("k").value(Comparison::Contains(1.into())).end_where().query();
QueryBuilder::search().from(1).where_().node().or().where_().edge().and().key("k").value(Comparison::Contains(vec![1, 2].into())).end_where().query();
```

NOTE: The use of `where_` with an underscore as the method name is necessary to avoid conflict with the Rust keyword.

The conditions are applied one at a time to each visited element and chained using logic operators `AND` and `OR`. They can be nested using `where_` and `end_where` (in place of brackets). The condition evaluator supports short-circuiting not evaluating conditions further if the logical outcome cannot change.
The conditions are applied one at a time to each visited element and chained using logic operators `AND` and `OR`. They can be nested using `where_` and `end_where` (in place of brackets). The condition evaluator supports short-circuiting not evaluating conditions further if the logical outcome cannot change. The condition comparators are type strict meaning that they do not perform type conversions nor coercion (e.g. `Comparison::Equal(1_i64).compare(1_u64)` will evaluate to `false`). Slight exception to this rule is the `Comparison::Contains` as it allows vectorized version of the base type (e.g. `Comparison::Contains(vec!["bc", "ef"]).compare("abcdefg")` will evaluate to `true`).

The condition `Distance` and the condition modifiers `Beyond` and `NotBeyond` are particularly important because they can directly influence the search. The former (`Distance`) can limit the depth of the search and can help with constructing more elaborate queries (or sequence thereof) extracting only fine grained elements (e.g. nodes whose edges have particular properties or are connected to other nodes with some properties). The latter (`Beyond` and `NotBeyond`) can limit search to only certain areas of an otherwise larger graph. Its most basic usage would be with condition `ids` to flat out stop the search at certain elements or continue only beyond certain elements.

Expand Down
84 changes: 83 additions & 1 deletion src/agdb/query/query_condition.rs
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,12 @@ pub enum CountComparison {

/// Comparison of database values (`DbValue`) used
/// by `key()` condition. Supports
/// the usual set of named comparisons: `==, !=, <, <=, >, =>`.
/// the usual set of named comparisons: `==, !=, <, <=, >, =>`
/// plus `contains()`. The comparisons are type
/// strict except for the `contains` comparison
/// which allows vectorized version of the base type. Notably
/// however it does not support the `bytes` and integral types
/// where the "contains" makes little sense (i.e. does 3 contain 1?).
#[derive(Debug, Clone, PartialEq)]
pub enum Comparison {
/// property == this
Expand All @@ -141,6 +146,9 @@ pub enum Comparison {

/// property != this
NotEqual(DbValue),

/// property.contains(this)
Contains(DbValue),
}

impl CountComparison {
Expand Down Expand Up @@ -204,6 +212,29 @@ impl Comparison {
Comparison::LessThan(right) => left < right,
Comparison::LessThanOrEqual(right) => left <= right,
Comparison::NotEqual(right) => left != right,
Comparison::Contains(right) => match (left, right) {
(DbValue::String(left), DbValue::String(right)) => left.contains(right),
(DbValue::String(left), DbValue::VecString(right)) => {
right.iter().all(|x| left.contains(x))
}
(DbValue::VecInt(left), DbValue::Int(right)) => left.contains(right),
(DbValue::VecInt(left), DbValue::VecInt(right)) => {
right.iter().all(|x| left.contains(x))
}
(DbValue::VecUint(left), DbValue::Uint(right)) => left.contains(right),
(DbValue::VecUint(left), DbValue::VecUint(right)) => {
right.iter().all(|x| left.contains(x))
}
(DbValue::VecFloat(left), DbValue::Float(right)) => left.contains(right),
(DbValue::VecFloat(left), DbValue::VecFloat(right)) => {
right.iter().all(|x| left.contains(x))
}
(DbValue::VecString(left), DbValue::String(right)) => left.contains(right),
(DbValue::VecString(left), DbValue::VecString(right)) => {
right.iter().all(|x| left.contains(x))
}
_ => false,
},
}
}
}
Expand Down Expand Up @@ -301,4 +332,55 @@ mod tests {
assert_eq!(LessThanOrEqual(2).compare_distance(2), Stop(true));
assert_eq!(LessThanOrEqual(2).compare_distance(1), Continue(true));
}

#[test]
fn contains() {
assert!(Comparison::Contains("abc".into()).compare(&"0abc123".into()));
assert!(!Comparison::Contains("abcd".into()).compare(&"0abc123".into()));

assert!(
Comparison::Contains(vec!["ab".to_string(), "23".to_string()].into())
.compare(&"0abc123".into())
);
assert!(
!Comparison::Contains(vec!["abcd".to_string(), "23".to_string()].into())
.compare(&"0abc123".into())
);

assert!(Comparison::Contains(1.into()).compare(&vec![2, 1, 3].into()));
assert!(!Comparison::Contains(4.into()).compare(&vec![2, 1, 3].into()));

assert!(Comparison::Contains(vec![2, 3].into()).compare(&vec![2, 1, 3].into()));
assert!(!Comparison::Contains(vec![2, 4].into()).compare(&vec![2, 1, 3].into()));

assert!(Comparison::Contains(1_u64.into()).compare(&vec![2_u64, 1_u64, 3_u64].into()));
assert!(!Comparison::Contains(4_u64.into()).compare(&vec![2_u64, 1_u64, 3_u64].into()));

assert!(Comparison::Contains(vec![2_u64, 3_u64].into())
.compare(&vec![2_u64, 1_u64, 3_u64].into()));
assert!(!Comparison::Contains(vec![2_u64, 4_u64].into())
.compare(&vec![2_u64, 1_u64, 3_u64].into()));

assert!(Comparison::Contains(1.1.into()).compare(&vec![2.1, 1.1, 3.3].into()));
assert!(!Comparison::Contains(4.2.into()).compare(&vec![2.1, 1.1, 3.3].into()));

assert!(Comparison::Contains(vec![2.2, 3.3].into()).compare(&vec![2.2, 1.1, 3.3].into()));
assert!(!Comparison::Contains(vec![2.2, 4.4].into()).compare(&vec![2.2, 1.1, 3.3].into()));

assert!(Comparison::Contains("abc".into())
.compare(&vec!["0".to_string(), "abc".to_string(), "123".to_string()].into()));
assert!(!Comparison::Contains("abcd".into())
.compare(&vec!["0".to_string(), "abc".to_string(), "123".to_string()].into()));

assert!(
Comparison::Contains(vec!["abc".to_string(), "123".to_string()].into())
.compare(&vec!["0".to_string(), "abc".to_string(), "123".to_string()].into())
);
assert!(
!Comparison::Contains(vec!["abcd".to_string(), "123".to_string()].into())
.compare(&vec!["0".to_string(), "abc".to_string(), "123".to_string()].into())
);

assert!(!Comparison::Contains("abc".into()).compare(&1.into()));
}
}
14 changes: 14 additions & 0 deletions tests/search_where_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -506,3 +506,17 @@ fn search_from_to_where_filter() {
&[-4],
);
}

#[test]
fn search_from_where_key_value_contains() {
let db = create_db();
db.exec_ids(
QueryBuilder::search()
.from("docs")
.where_()
.key("content")
.value(Comparison::Contains("apples".into()))
.query(),
&[8],
);
}

0 comments on commit 392d666

Please sign in to comment.