pub mod aggregation;
pub mod sorting;
use crate::error::Result;
use crate::graph::{Graph, Id, Node};
use serde_json::Value;
pub use aggregation::{
AggregateFunction, AggregateResult, Aggregator, Statistics, StatisticsResult,
};
pub use sorting::{AdvancedSorter, Pagination, SortCriteria, SortDirection, SortedPage, Sorter};
pub struct QueryBuilder<'a> {
graph: &'a Graph,
current_nodes: Vec<Id>,
}
impl<'a> QueryBuilder<'a> {
pub fn new(graph: &'a Graph, start_nodes: Vec<Id>) -> Self {
QueryBuilder {
graph,
current_nodes: start_nodes,
}
}
pub fn from_node(graph: &'a Graph, node_id: Id) -> Self {
Self::new(graph, vec![node_id])
}
pub fn outgoing(mut self, rel_type: &str) -> Result<Self> {
let mut next_nodes = Vec::new();
for &node_id in &self.current_nodes {
let relationships = self.graph.get_relationships_for_node(node_id)?;
for rel in relationships {
if rel.rel_type == rel_type && rel.is_outgoing_from(node_id) {
next_nodes.push(rel.to_id);
}
}
}
self.current_nodes = next_nodes;
Ok(self)
}
pub fn incoming(mut self, rel_type: &str) -> Result<Self> {
let mut next_nodes = Vec::new();
for &node_id in &self.current_nodes {
let relationships = self.graph.get_relationships_for_node(node_id)?;
for rel in relationships {
if rel.rel_type == rel_type && rel.is_incoming_to(node_id) {
next_nodes.push(rel.from_id);
}
}
}
self.current_nodes = next_nodes;
Ok(self)
}
pub fn either(mut self, rel_type: &str) -> Result<Self> {
let mut next_nodes = Vec::new();
for &node_id in &self.current_nodes {
let relationships = self.graph.get_relationships_for_node(node_id)?;
for rel in relationships {
if rel.rel_type == rel_type {
if rel.is_outgoing_from(node_id) {
next_nodes.push(rel.to_id);
} else if rel.is_incoming_to(node_id) {
next_nodes.push(rel.from_id);
}
}
}
}
self.current_nodes = next_nodes;
Ok(self)
}
pub fn filter_by_property(mut self, key: &str, value: &Value) -> Result<Self> {
let mut filtered_nodes = Vec::new();
for &node_id in &self.current_nodes {
if let Some(node) = self.graph.get_node(node_id)? {
if node.get_property(key) == Some(value) {
filtered_nodes.push(node_id);
}
}
}
self.current_nodes = filtered_nodes;
Ok(self)
}
pub fn node_ids(&self) -> &[Id] {
&self.current_nodes
}
pub fn nodes(&self) -> Result<Vec<Node>> {
let mut nodes = Vec::new();
for &node_id in &self.current_nodes {
if let Some(node) = self.graph.get_node(node_id)? {
nodes.push(node);
}
}
Ok(nodes)
}
pub fn count(&self) -> usize {
self.current_nodes.len()
}
pub fn is_empty(&self) -> bool {
self.current_nodes.is_empty()
}
pub fn aggregate(&self, function: AggregateFunction) -> Result<AggregateResult> {
let nodes = self.nodes()?;
Aggregator::aggregate(&nodes, &function)
}
pub fn sort(mut self, criteria: Vec<SortCriteria>) -> Result<Self> {
let nodes = self.nodes()?;
let sorted_nodes = Sorter::sort_nodes(nodes, &criteria);
self.current_nodes = sorted_nodes.into_iter().map(|node| node.id).collect();
Ok(self)
}
pub fn paginate(mut self, pagination: Pagination) -> Result<Self> {
let paginated_ids = pagination.apply(self.current_nodes);
self.current_nodes = paginated_ids;
Ok(self)
}
pub fn sorted_page(
&self,
criteria: Vec<SortCriteria>,
pagination: Pagination,
) -> Result<SortedPage<Node>> {
let nodes = self.nodes()?;
Ok(AdvancedSorter::sort_nodes_paginated(
nodes, &criteria, pagination,
))
}
pub fn statistics(&self, property: &str) -> Result<StatisticsResult> {
let nodes = self.nodes()?;
Statistics::calculate(&nodes, property)
}
pub fn group_by_sorted(
&self,
group_property: &str,
sort_criteria: Vec<SortCriteria>,
) -> Result<std::collections::BTreeMap<String, Vec<Node>>> {
let nodes = self.nodes()?;
Ok(AdvancedSorter::sort_and_group_nodes(
nodes,
group_property,
&sort_criteria,
))
}
}
pub struct PathFinder<'a> {
graph: &'a Graph,
}
impl<'a> PathFinder<'a> {
pub fn new(graph: &'a Graph) -> Self {
PathFinder { graph }
}
pub fn shortest_path(&self, from_id: Id, to_id: Id) -> Result<Option<Vec<Id>>> {
if from_id == to_id {
return Ok(Some(vec![from_id]));
}
let mut queue = std::collections::VecDeque::new();
let mut visited = std::collections::HashSet::new();
let mut parent = std::collections::HashMap::new();
queue.push_back(from_id);
visited.insert(from_id);
while let Some(current_id) = queue.pop_front() {
let relationships = self.graph.get_relationships_for_node(current_id)?;
for rel in relationships {
let next_id = if rel.is_outgoing_from(current_id) {
rel.to_id
} else if rel.is_incoming_to(current_id) {
rel.from_id
} else {
continue;
};
if next_id == to_id {
let mut path = vec![to_id, current_id];
let mut current = current_id;
while let Some(&prev) = parent.get(¤t) {
path.push(prev);
current = prev;
}
path.reverse();
return Ok(Some(path));
}
if !visited.contains(&next_id) {
visited.insert(next_id);
parent.insert(next_id, current_id);
queue.push_back(next_id);
}
}
}
Ok(None)
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
use std::collections::HashMap;
#[test]
fn test_query_builder() {
let mut graph = Graph::new().unwrap();
let node1_id = graph
.create_node([("name".to_string(), json!("Alice"))].into())
.unwrap();
let node2_id = graph
.create_node([("name".to_string(), json!("Bob"))].into())
.unwrap();
let node3_id = graph
.create_node([("name".to_string(), json!("Charlie"))].into())
.unwrap();
graph
.create_relationship(node1_id, node2_id, "KNOWS".to_string(), HashMap::new())
.unwrap();
graph
.create_relationship(node2_id, node3_id, "KNOWS".to_string(), HashMap::new())
.unwrap();
let query_result = QueryBuilder::from_node(&graph, node1_id)
.outgoing("KNOWS")
.unwrap();
let result = query_result.node_ids();
assert_eq!(result.len(), 1);
assert_eq!(result[0], node2_id);
let query_result = QueryBuilder::new(&graph, vec![node1_id, node2_id, node3_id])
.outgoing("KNOWS")
.unwrap()
.filter_by_property("name", &json!("Charlie"))
.unwrap();
let result = query_result.count();
assert_eq!(result, 1);
}
#[test]
fn test_path_finder() {
let mut graph = Graph::new().unwrap();
let node1_id = graph.create_node(HashMap::new()).unwrap();
let node2_id = graph.create_node(HashMap::new()).unwrap();
let node3_id = graph.create_node(HashMap::new()).unwrap();
graph
.create_relationship(node1_id, node2_id, "CONNECTS".to_string(), HashMap::new())
.unwrap();
graph
.create_relationship(node2_id, node3_id, "CONNECTS".to_string(), HashMap::new())
.unwrap();
let path_finder = PathFinder::new(&graph);
let path = path_finder.shortest_path(node1_id, node3_id).unwrap();
assert_eq!(path, Some(vec![node1_id, node2_id, node3_id]));
}
}