Skip to content

Commit

Permalink
[query] Add ability to choose search algorithm #608 (#614)
Browse files Browse the repository at this point in the history
* add algorithm choice

* update docs
  • Loading branch information
michaelvlach authored Jul 2, 2023
1 parent 634bd50 commit 0a22ed3
Show file tree
Hide file tree
Showing 10 changed files with 355 additions and 38 deletions.
13 changes: 11 additions & 2 deletions docs/queries.md
Original file line number Diff line number Diff line change
Expand Up @@ -516,10 +516,11 @@ The result will contain:

## Search

There is only a single search query that provides the ability to search the graph examining connected elements and their properties. While it is possible to construct the search queries manually, specifying conditions manually in particular can be excessively difficult and therefore **using the builder pattern is recommended**.
There is only a single search query that provides the ability to search the graph examining connected elements and their properties. While it is possible to construct the search queries manually, specifying conditions manually in particular can be excessively difficult and therefore **using the builder pattern is recommended**. The default search algorithm is `breadth first` however you can choose `depth first` as well.

```Rust
pub struct SearchQuery {
pub algorithm: SearchQueryAlgorithm,
pub origin: QueryId,
pub destination: QueryId,
pub limit: u64,
Expand All @@ -528,6 +529,11 @@ pub struct SearchQuery {
pub conditions: Vec<QueryCondition>,
}

pub enum SearchQueryAlgorithm {
BreadthFirst,
DepthFirst,
}

pub enum DbKeyOrder {
Asc(DbKey),
Desc(DbKey),
Expand All @@ -541,6 +547,9 @@ QueryBuilder::search().from("a").query();
QueryBuilder::search().to(1).query(); //reverse search
QueryBuilder::search().from("a").to("b").query(); //path search, A*

QueryBuilder::search().breadth_first().from("a").query(); //breadth first is the default and can be omitted however
QueryBuilder::search().depth_first().from("a").query();

//limit, offset and order_by can be applied similarly to all the search variants
QueryBuilder::search().from(1).order_by(vec![DbKeyOrder::Desc("age".into()), DbKeyOrder::Asc("name".into())]).query()
QueryBuilder::search().from(1).offset(10).query();
Expand All @@ -551,7 +560,7 @@ QueryBuilder::search().from(1).order_by(vec![DbKeyOrder::Desc("k".into())]).offs
QueryBuilder::search().from(1).offset(10).limit(5).query();
```

The search query is made up of the `origin` and `destination` of the search. Specifying only `origin` (from) will result in breadth first search along `from->to` edges. Specifying only `destination` will result in the reverse breadth first search along the `to<-from` edges. When both `origin` and `destination` are specified the search algorithm becomes a path search and the algorithm is switched to `A*`. Optionally you can specify a `limit` (0 = unlimited) and `offset` (0 = no offset) to the returned list of graph element ids. If specified (!= 0) the `origin` and the `destination` must exist in the database, otherwise an error will be returned. The elements can be optionally ordered with `order_by` list of keys allowing ascending/descending ordering based on multiple properties.
The search query is made up of the `origin` and `destination` of the search and the algorithm. Specifying only `origin` (from) will result in a search along `from->to` edges. Specifying only `destination` (to) will result in the reverse search along the `to<-from` edges. When both `origin` and `destination` are specified the search algorithm becomes a path search and the algorithm used will be `A*`. Optionally you can specify a `limit` (0 = unlimited) and `offset` (0 = no offset) to the returned list of graph element ids. If specified (!= 0) the `origin` and the `destination` must exist in the database, otherwise an error will be returned. The elements can be optionally ordered with `order_by` list of keys allowing ascending/descending ordering based on multiple properties.

Finally the list of `conditions` that each examined graph element must satisfy to be included in the result (and subjected to the `limit` and `offset`).

Expand Down
119 changes: 89 additions & 30 deletions src/agdb/db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ use crate::DbKeyValue;
use crate::DbValue;
use crate::QueryError;
use crate::QueryResult;
use crate::SearchQueryAlgorithm;
use crate::Transaction;
use std::cell::RefCell;
use std::rc::Rc;
Expand Down Expand Up @@ -312,27 +313,57 @@ impl Db {
pub(crate) fn search_from(
&self,
from: DbId,
algorithm: SearchQueryAlgorithm,
limit: u64,
offset: u64,
conditions: &Vec<QueryCondition>,
) -> Result<Vec<DbId>, QueryError> {
let search = GraphSearch::from(&self.graph);

let indexes = match (limit, offset) {
(0, 0) => search
.breadth_first_search(GraphIndex(from.0), DefaultHandler::new(self, conditions))?,
(_, 0) => search.breadth_first_search(
GraphIndex(from.0),
LimitHandler::new(limit, self, conditions),
)?,
(0, _) => search.breadth_first_search(
GraphIndex(from.0),
OffsetHandler::new(offset, self, conditions),
)?,
(_, _) => search.breadth_first_search(
GraphIndex(from.0),
LimitOffsetHandler::new(limit, offset, self, conditions),
)?,
(0, 0) => match algorithm {
SearchQueryAlgorithm::BreadthFirst => search.breadth_first_search(
GraphIndex(from.0),
DefaultHandler::new(self, conditions),
)?,
SearchQueryAlgorithm::DepthFirst => search.depth_first_search(
GraphIndex(from.0),
DefaultHandler::new(self, conditions),
)?,
},

(_, 0) => match algorithm {
SearchQueryAlgorithm::BreadthFirst => search.breadth_first_search(
GraphIndex(from.0),
LimitHandler::new(limit, self, conditions),
)?,
SearchQueryAlgorithm::DepthFirst => search.depth_first_search(
GraphIndex(from.0),
LimitHandler::new(limit, self, conditions),
)?,
},

(0, _) => match algorithm {
SearchQueryAlgorithm::BreadthFirst => search.breadth_first_search(
GraphIndex(from.0),
OffsetHandler::new(offset, self, conditions),
)?,
SearchQueryAlgorithm::DepthFirst => search.depth_first_search(
GraphIndex(from.0),
OffsetHandler::new(offset, self, conditions),
)?,
},

(_, _) => match algorithm {
SearchQueryAlgorithm::BreadthFirst => search.breadth_first_search(
GraphIndex(from.0),
LimitOffsetHandler::new(limit, offset, self, conditions),
)?,
SearchQueryAlgorithm::DepthFirst => search.depth_first_search(
GraphIndex(from.0),
LimitOffsetHandler::new(limit, offset, self, conditions),
)?,
},
};

Ok(indexes.iter().map(|index| DbId(index.0)).collect())
Expand All @@ -341,29 +372,57 @@ impl Db {
pub(crate) fn search_to(
&self,
to: DbId,
algorithm: SearchQueryAlgorithm,
limit: u64,
offset: u64,
conditions: &Vec<QueryCondition>,
) -> Result<Vec<DbId>, QueryError> {
let search = GraphSearch::from(&self.graph);

let indexes = match (limit, offset) {
(0, 0) => search.breadth_first_search_reverse(
GraphIndex(to.0),
DefaultHandler::new(self, conditions),
)?,
(_, 0) => search.breadth_first_search_reverse(
GraphIndex(to.0),
LimitHandler::new(limit, self, conditions),
)?,
(0, _) => search.breadth_first_search_reverse(
GraphIndex(to.0),
OffsetHandler::new(offset, self, conditions),
)?,
(_, _) => search.breadth_first_search_reverse(
GraphIndex(to.0),
LimitOffsetHandler::new(limit, offset, self, conditions),
)?,
(0, 0) => match algorithm {
SearchQueryAlgorithm::BreadthFirst => search.breadth_first_search_reverse(
GraphIndex(to.0),
DefaultHandler::new(self, conditions),
)?,
SearchQueryAlgorithm::DepthFirst => search.depth_first_search_reverse(
GraphIndex(to.0),
DefaultHandler::new(self, conditions),
)?,
},

(_, 0) => match algorithm {
SearchQueryAlgorithm::BreadthFirst => search.breadth_first_search_reverse(
GraphIndex(to.0),
LimitHandler::new(limit, self, conditions),
)?,
SearchQueryAlgorithm::DepthFirst => search.depth_first_search_reverse(
GraphIndex(to.0),
LimitHandler::new(limit, self, conditions),
)?,
},

(0, _) => match algorithm {
SearchQueryAlgorithm::BreadthFirst => search.breadth_first_search_reverse(
GraphIndex(to.0),
OffsetHandler::new(offset, self, conditions),
)?,
SearchQueryAlgorithm::DepthFirst => search.depth_first_search_reverse(
GraphIndex(to.0),
OffsetHandler::new(offset, self, conditions),
)?,
},

(_, _) => match algorithm {
SearchQueryAlgorithm::BreadthFirst => search.breadth_first_search_reverse(
GraphIndex(to.0),
LimitOffsetHandler::new(limit, offset, self, conditions),
)?,
SearchQueryAlgorithm::DepthFirst => search.depth_first_search_reverse(
GraphIndex(to.0),
LimitOffsetHandler::new(limit, offset, self, conditions),
)?,
},
};

Ok(indexes.iter().map(|index| DbId(index.0)).collect())
Expand Down
2 changes: 0 additions & 2 deletions src/agdb/graph_search.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,6 @@ where
}
}

#[allow(dead_code)]
pub fn depth_first_search<Handler: SearchHandler>(
&self,
index: GraphIndex,
Expand All @@ -78,7 +77,6 @@ where
}
}

#[allow(dead_code)]
pub fn depth_first_search_reverse<Handler: SearchHandler>(
&self,
index: GraphIndex,
Expand Down
1 change: 1 addition & 0 deletions src/agdb/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ pub use query::remove_aliases_query::RemoveAliasesQuery;
pub use query::remove_query::RemoveQuery;
pub use query::remove_values_query::RemoveValuesQuery;
pub use query::search_query::SearchQuery;
pub use query::search_query::SearchQueryAlgorithm;
pub use query::select_all_aliases_query::SelectAllAliases;
pub use query::select_key_count_query::SelectKeyCountQuery;
pub use query::select_keys_query::SelectKeysQuery;
Expand Down
2 changes: 2 additions & 0 deletions src/agdb/query/insert_aliases_query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ mod tests {
use crate::query::search_query::SearchQuery;
use crate::test_utilities::test_file::TestFile;
use crate::DbId;
use crate::SearchQueryAlgorithm;

#[test]
fn invalid_query() {
Expand All @@ -48,6 +49,7 @@ mod tests {
let mut result = QueryResult::default();
let query = InsertAliasesQuery {
ids: QueryIds::Search(SearchQuery {
algorithm: SearchQueryAlgorithm::BreadthFirst,
origin: QueryId::Id(DbId(0)),
destination: QueryId::Id(DbId(0)),
limit: 0,
Expand Down
2 changes: 2 additions & 0 deletions src/agdb/query/query_ids.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ impl From<String> for QueryIds {
#[cfg(test)]
mod tests {
use super::*;
use crate::query::search_query::SearchQueryAlgorithm;

#[test]
#[allow(clippy::redundant_clone)]
Expand All @@ -99,6 +100,7 @@ mod tests {
#[test]
fn get_ids_from_search() {
let ids = QueryIds::Search(SearchQuery {
algorithm: SearchQueryAlgorithm::BreadthFirst,
origin: QueryId::Id(DbId(0)),
destination: QueryId::Id(DbId(0)),
limit: 0,
Expand Down
31 changes: 27 additions & 4 deletions src/agdb/query/search_query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,15 @@ use crate::QueryError;
use crate::QueryResult;
use std::cmp::Ordering;

#[derive(Debug, Clone, Copy, PartialEq)]
pub enum SearchQueryAlgorithm {
BreadthFirst,
DepthFirst,
}

#[derive(Debug, Clone, PartialEq)]
pub struct SearchQuery {
pub algorithm: SearchQueryAlgorithm,
pub origin: QueryId,
pub destination: QueryId,
pub limit: u64,
Expand All @@ -38,19 +45,31 @@ impl SearchQuery {
let origin = db.db_id(&self.origin)?;

if self.order_by.is_empty() {
db.search_from(origin, self.limit, self.offset, &self.conditions)
db.search_from(
origin,
self.algorithm,
self.limit,
self.offset,
&self.conditions,
)
} else {
let mut ids = db.search_from(origin, 0, 0, &self.conditions)?;
let mut ids = db.search_from(origin, self.algorithm, 0, 0, &self.conditions)?;
self.sort(&mut ids, db)?;
self.slice(ids)
}
} else if self.origin == QueryId::Id(DbId(0)) {
let destination = db.db_id(&self.destination)?;

if self.order_by.is_empty() {
db.search_to(destination, self.limit, self.offset, &self.conditions)
db.search_to(
destination,
self.algorithm,
self.limit,
self.offset,
&self.conditions,
)
} else {
let mut ids = db.search_to(destination, 0, 0, &self.conditions)?;
let mut ids = db.search_to(destination, self.algorithm, 0, 0, &self.conditions)?;
self.sort(&mut ids, db)?;
self.slice(ids)
}
Expand Down Expand Up @@ -123,6 +142,7 @@ mod tests {
format!(
"{:?}",
SearchQuery {
algorithm: SearchQueryAlgorithm::BreadthFirst,
origin: QueryId::from(0),
destination: QueryId::from(0),
limit: 0,
Expand All @@ -137,6 +157,7 @@ mod tests {
#[allow(clippy::redundant_clone)]
fn derived_from_clone() {
let left = SearchQuery {
algorithm: SearchQueryAlgorithm::BreadthFirst,
origin: QueryId::from(0),
destination: QueryId::from(0),
limit: 0,
Expand All @@ -152,6 +173,7 @@ mod tests {
fn derived_from_partial_eq() {
assert_eq!(
SearchQuery {
algorithm: SearchQueryAlgorithm::BreadthFirst,
origin: QueryId::from(0),
destination: QueryId::from(0),
limit: 0,
Expand All @@ -160,6 +182,7 @@ mod tests {
conditions: vec![]
},
SearchQuery {
algorithm: SearchQueryAlgorithm::BreadthFirst,
origin: QueryId::from(0),
destination: QueryId::from(0),
limit: 0,
Expand Down
Loading

0 comments on commit 0a22ed3

Please sign in to comment.