use crate::types::{ETypeId, Edge, NodeId, PropValue};
use std::collections::{HashMap, HashSet, VecDeque};
use std::sync::Arc;
pub type EdgeFilter = Arc<dyn Fn(&EdgeInfo) -> bool + Send + Sync>;
pub type NodeFilter = Arc<dyn Fn(&NodeInfo) -> bool + Send + Sync>;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TraversalDirection {
Out,
In,
Both,
}
#[derive(Clone)]
pub struct TraverseOptions {
pub direction: TraversalDirection,
pub min_depth: usize,
pub max_depth: usize,
pub unique: bool,
pub where_edge: Option<EdgeFilter>,
pub where_node: Option<NodeFilter>,
}
impl std::fmt::Debug for TraverseOptions {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("TraverseOptions")
.field("direction", &self.direction)
.field("min_depth", &self.min_depth)
.field("max_depth", &self.max_depth)
.field("unique", &self.unique)
.field("where_edge", &self.where_edge.as_ref().map(|_| "<fn>"))
.field("where_node", &self.where_node.as_ref().map(|_| "<fn>"))
.finish()
}
}
impl Default for TraverseOptions {
fn default() -> Self {
Self {
direction: TraversalDirection::Out,
min_depth: 1,
max_depth: 1,
unique: true,
where_edge: None,
where_node: None,
}
}
}
impl TraverseOptions {
pub fn new(direction: TraversalDirection, max_depth: usize) -> Self {
Self {
direction,
min_depth: 1,
max_depth,
unique: true,
where_edge: None,
where_node: None,
}
}
pub fn with_min_depth(mut self, min_depth: usize) -> Self {
self.min_depth = min_depth;
self
}
pub fn with_unique(mut self, unique: bool) -> Self {
self.unique = unique;
self
}
pub fn with_edge_filter<F>(mut self, predicate: F) -> Self
where
F: Fn(&EdgeInfo) -> bool + Send + Sync + 'static,
{
self.where_edge = Some(Arc::new(predicate));
self
}
pub fn with_node_filter<F>(mut self, predicate: F) -> Self
where
F: Fn(&NodeInfo) -> bool + Send + Sync + 'static,
{
self.where_node = Some(Arc::new(predicate));
self
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct RawEdge {
pub src: NodeId,
pub dst: NodeId,
pub etype: ETypeId,
}
impl From<Edge> for RawEdge {
fn from(edge: Edge) -> Self {
Self {
src: edge.src,
dst: edge.dst,
etype: edge.etype,
}
}
}
#[derive(Debug, Clone)]
pub struct EdgeResult {
pub src: NodeId,
pub dst: NodeId,
pub etype: ETypeId,
pub props: Vec<(String, PropValue)>,
}
#[derive(Debug, Clone)]
pub struct TraversalResult {
pub node_id: NodeId,
pub edge: Option<RawEdge>,
pub depth: usize,
}
#[derive(Debug, Clone)]
pub struct EdgeInfo {
pub src: NodeId,
pub dst: NodeId,
pub etype: ETypeId,
pub props: HashMap<String, PropValue>,
}
impl From<RawEdge> for EdgeInfo {
fn from(edge: RawEdge) -> Self {
Self {
src: edge.src,
dst: edge.dst,
etype: edge.etype,
props: HashMap::new(),
}
}
}
#[derive(Debug, Clone)]
pub struct NodeInfo {
pub id: NodeId,
pub props: HashMap<String, PropValue>,
}
#[derive(Clone)]
pub enum TraversalStep {
SingleHop {
direction: TraversalDirection,
etype: Option<ETypeId>,
edge_filter: Option<EdgeFilter>,
node_filter: Option<NodeFilter>,
},
Traverse {
etype: Option<ETypeId>,
options: TraverseOptions,
},
}
impl std::fmt::Debug for TraversalStep {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::SingleHop {
direction,
etype,
edge_filter,
node_filter,
} => f
.debug_struct("SingleHop")
.field("direction", direction)
.field("etype", etype)
.field("edge_filter", &edge_filter.as_ref().map(|_| "<fn>"))
.field("node_filter", &node_filter.as_ref().map(|_| "<fn>"))
.finish(),
Self::Traverse { etype, options } => f
.debug_struct("Traverse")
.field("etype", etype)
.field("options", options)
.finish(),
}
}
}
#[derive(Clone)]
pub struct TraversalBuilder {
start_nodes: Vec<NodeId>,
steps: Vec<TraversalStep>,
limit: Option<usize>,
unique_nodes: bool,
edge_filter: Option<EdgeFilter>,
node_filter: Option<NodeFilter>,
selected_props: Option<Vec<String>>,
}
impl std::fmt::Debug for TraversalBuilder {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("TraversalBuilder")
.field("start_nodes", &self.start_nodes)
.field("steps", &self.steps)
.field("limit", &self.limit)
.field("unique_nodes", &self.unique_nodes)
.field("edge_filter", &self.edge_filter.as_ref().map(|_| "<fn>"))
.field("node_filter", &self.node_filter.as_ref().map(|_| "<fn>"))
.field("selected_props", &self.selected_props)
.finish()
}
}
impl TraversalBuilder {
pub fn new(start_nodes: Vec<NodeId>) -> Self {
Self {
start_nodes,
steps: Vec::new(),
limit: None,
unique_nodes: true,
edge_filter: None,
node_filter: None,
selected_props: None,
}
}
pub fn from_node(node_id: NodeId) -> Self {
Self::new(vec![node_id])
}
pub(crate) fn push_step(&mut self, step: TraversalStep) {
self.steps.push(step);
}
pub fn out(mut self, etype: Option<ETypeId>) -> Self {
self.steps.push(TraversalStep::SingleHop {
direction: TraversalDirection::Out,
etype,
edge_filter: None,
node_filter: None,
});
self
}
pub fn r#in(mut self, etype: Option<ETypeId>) -> Self {
self.steps.push(TraversalStep::SingleHop {
direction: TraversalDirection::In,
etype,
edge_filter: None,
node_filter: None,
});
self
}
pub fn both(mut self, etype: Option<ETypeId>) -> Self {
self.steps.push(TraversalStep::SingleHop {
direction: TraversalDirection::Both,
etype,
edge_filter: None,
node_filter: None,
});
self
}
pub fn traverse(mut self, etype: Option<ETypeId>, options: TraverseOptions) -> Self {
self.steps.push(TraversalStep::Traverse { etype, options });
self
}
pub fn take(mut self, limit: usize) -> Self {
self.limit = Some(limit);
self
}
pub fn unique(mut self, unique: bool) -> Self {
self.unique_nodes = unique;
self
}
pub fn where_edge<F>(mut self, predicate: F) -> Self
where
F: Fn(&EdgeInfo) -> bool + Send + Sync + 'static,
{
self.edge_filter = Some(Arc::new(predicate));
self
}
pub fn where_node<F>(mut self, predicate: F) -> Self
where
F: Fn(&NodeInfo) -> bool + Send + Sync + 'static,
{
self.node_filter = Some(Arc::new(predicate));
self
}
pub fn select(mut self, props: Vec<String>) -> Self {
self.selected_props = Some(props);
self
}
pub fn select_props(mut self, props: &[&str]) -> Self {
self.selected_props = Some(props.iter().map(|s| s.to_string()).collect());
self
}
pub fn selected_properties(&self) -> Option<&[String]> {
self.selected_props.as_deref()
}
pub fn has_filters(&self) -> bool {
if self.edge_filter.is_some() || self.node_filter.is_some() {
return true;
}
for step in &self.steps {
match step {
TraversalStep::SingleHop {
edge_filter,
node_filter,
..
} => {
if edge_filter.is_some() || node_filter.is_some() {
return true;
}
}
TraversalStep::Traverse { options, .. } => {
if options.where_edge.is_some() || options.where_node.is_some() {
return true;
}
}
}
}
false
}
pub fn collect_options(&self) -> CollectOptions {
let mut opts = CollectOptions::new();
if let Some(ref props) = self.selected_props {
opts = opts.select_node_props(props.clone());
}
opts
}
pub fn execute<F>(self, neighbors: F) -> TraversalIterator<F>
where
F: Fn(NodeId, TraversalDirection, Option<ETypeId>) -> Vec<Edge>,
{
TraversalIterator::new(self, neighbors)
}
pub fn collect_node_ids<F>(self, neighbors: F) -> Vec<NodeId>
where
F: Fn(NodeId, TraversalDirection, Option<ETypeId>) -> Vec<Edge>,
{
self.execute(neighbors).map(|r| r.node_id).collect()
}
pub fn count<F>(self, neighbors: F) -> usize
where
F: Fn(NodeId, TraversalDirection, Option<ETypeId>) -> Vec<Edge>,
{
if self.can_use_fast_count() {
return self.count_fast(&neighbors);
}
self.execute(neighbors).count()
}
fn can_use_fast_count(&self) -> bool {
if self.has_filters() {
return false;
}
for step in &self.steps {
if matches!(step, TraversalStep::Traverse { .. }) {
return false;
}
}
true
}
fn count_fast<F>(&self, neighbors: &F) -> usize
where
F: Fn(NodeId, TraversalDirection, Option<ETypeId>) -> Vec<Edge>,
{
let mut current_nodes: HashSet<NodeId> = self.start_nodes.iter().copied().collect();
for step in &self.steps {
let TraversalStep::SingleHop {
direction, etype, ..
} = step
else {
unreachable!()
};
let mut next_nodes = HashSet::new();
for node_id in current_nodes {
let edges = neighbors(node_id, *direction, *etype);
for edge in edges {
let neighbor = match direction {
TraversalDirection::Out => edge.dst,
TraversalDirection::In => edge.src,
TraversalDirection::Both => {
if edge.src == node_id {
edge.dst
} else {
edge.src
}
}
};
next_nodes.insert(neighbor);
}
}
current_nodes = next_nodes;
}
if let Some(limit) = self.limit {
current_nodes.len().min(limit)
} else {
current_nodes.len()
}
}
pub fn raw_edges<F>(self, neighbors: F) -> RawEdgeIterator<F>
where
F: Fn(NodeId, TraversalDirection, Option<ETypeId>) -> Vec<Edge>,
{
RawEdgeIterator::new(self, neighbors)
}
}
pub struct TraversalIterator<F> {
neighbors: F,
step_index: usize,
steps: Vec<TraversalStep>,
current_frontier: VecDeque<TraversalResult>,
visited: HashSet<NodeId>,
unique_nodes: bool,
limit: Option<usize>,
yielded: usize,
done: bool,
edge_filter: Option<EdgeFilter>,
node_filter: Option<NodeFilter>,
}
impl<F> TraversalIterator<F>
where
F: Fn(NodeId, TraversalDirection, Option<ETypeId>) -> Vec<Edge>,
{
fn new(builder: TraversalBuilder, neighbors: F) -> Self {
let mut frontier = VecDeque::new();
let mut visited = HashSet::new();
for node_id in builder.start_nodes {
if builder.unique_nodes {
visited.insert(node_id);
}
frontier.push_back(TraversalResult {
node_id,
edge: None,
depth: 0,
});
}
Self {
neighbors,
step_index: 0,
steps: builder.steps,
current_frontier: frontier,
visited,
unique_nodes: builder.unique_nodes,
limit: builder.limit,
yielded: 0,
done: false,
edge_filter: builder.edge_filter,
node_filter: builder.node_filter,
}
}
fn passes_filters(&self, result: &TraversalResult) -> bool {
if let Some(ref edge_filter) = self.edge_filter {
if let Some(ref raw_edge) = result.edge {
let edge_info = EdgeInfo::from(*raw_edge);
if !edge_filter(&edge_info) {
return false;
}
}
}
if let Some(ref node_filter) = self.node_filter {
let node_info = NodeInfo {
id: result.node_id,
props: HashMap::new(), };
if !node_filter(&node_info) {
return false;
}
}
true
}
fn process_single_hop(
&mut self,
direction: TraversalDirection,
etype: Option<ETypeId>,
step_edge_filter: &Option<EdgeFilter>,
step_node_filter: &Option<NodeFilter>,
) -> VecDeque<TraversalResult> {
let mut next_frontier = VecDeque::new();
for result in self.current_frontier.drain(..) {
let edges = (self.neighbors)(result.node_id, direction, etype);
for edge in edges {
let neighbor_id = match direction {
TraversalDirection::Out => edge.dst,
TraversalDirection::In => edge.src,
TraversalDirection::Both => {
if edge.src == result.node_id {
edge.dst
} else {
edge.src
}
}
};
if self.unique_nodes && self.visited.contains(&neighbor_id) {
continue;
}
let raw_edge = RawEdge::from(edge);
if let Some(ref edge_filter) = step_edge_filter {
let edge_info = EdgeInfo::from(raw_edge);
if !edge_filter(&edge_info) {
continue;
}
}
if let Some(ref node_filter) = step_node_filter {
let node_info = NodeInfo {
id: neighbor_id,
props: HashMap::new(),
};
if !node_filter(&node_info) {
continue;
}
}
if self.unique_nodes {
self.visited.insert(neighbor_id);
}
next_frontier.push_back(TraversalResult {
node_id: neighbor_id,
edge: Some(raw_edge),
depth: result.depth + 1,
});
}
}
next_frontier
}
fn process_traverse(
&mut self,
etype: Option<ETypeId>,
options: &TraverseOptions,
) -> VecDeque<TraversalResult> {
let mut results = VecDeque::new();
let mut local_visited: HashSet<NodeId> = if options.unique {
self.current_frontier.iter().map(|r| r.node_id).collect()
} else {
HashSet::new()
};
let mut queue: VecDeque<(NodeId, usize)> = self
.current_frontier
.drain(..)
.map(|r| (r.node_id, 0))
.collect();
while let Some((current_id, depth)) = queue.pop_front() {
if depth >= options.max_depth {
continue;
}
let directions = match options.direction {
TraversalDirection::Both => vec![TraversalDirection::Out, TraversalDirection::In],
dir => vec![dir],
};
for dir in directions {
let edges = (self.neighbors)(current_id, dir, etype);
for edge in edges {
let neighbor_id = match dir {
TraversalDirection::Out => edge.dst,
TraversalDirection::In => edge.src,
TraversalDirection::Both => unreachable!(),
};
if options.unique && local_visited.contains(&neighbor_id) {
continue;
}
let raw_edge = RawEdge::from(edge);
if let Some(ref edge_filter) = options.where_edge {
let edge_info = EdgeInfo::from(raw_edge);
if !edge_filter(&edge_info) {
continue;
}
}
if let Some(ref node_filter) = options.where_node {
let node_info = NodeInfo {
id: neighbor_id,
props: HashMap::new(),
};
if !node_filter(&node_info) {
continue;
}
}
if options.unique {
local_visited.insert(neighbor_id);
}
if self.unique_nodes && self.visited.contains(&neighbor_id) {
continue;
}
if self.unique_nodes {
self.visited.insert(neighbor_id);
}
let new_depth = depth + 1;
if new_depth >= options.min_depth {
results.push_back(TraversalResult {
node_id: neighbor_id,
edge: Some(raw_edge),
depth: new_depth,
});
}
if new_depth < options.max_depth {
queue.push_back((neighbor_id, new_depth));
}
}
}
}
results
}
}
impl<F> Iterator for TraversalIterator<F>
where
F: Fn(NodeId, TraversalDirection, Option<ETypeId>) -> Vec<Edge>,
{
type Item = TraversalResult;
fn next(&mut self) -> Option<Self::Item> {
if self.done {
return None;
}
if let Some(limit) = self.limit {
if self.yielded >= limit {
self.done = true;
return None;
}
}
loop {
if !self.current_frontier.is_empty() {
if self.step_index >= self.steps.len() {
let result = self.current_frontier.pop_front()?;
if !self.passes_filters(&result) {
continue;
}
self.yielded += 1;
if let Some(limit) = self.limit {
if self.yielded >= limit {
self.done = true;
}
}
return Some(result);
}
}
if self.step_index < self.steps.len() {
let step = self.steps[self.step_index].clone();
self.step_index += 1;
let next_frontier = match step {
TraversalStep::SingleHop {
direction,
etype,
edge_filter,
node_filter,
} => self.process_single_hop(direction, etype, &edge_filter, &node_filter),
TraversalStep::Traverse { etype, options } => self.process_traverse(etype, &options),
};
self.current_frontier = next_frontier;
} else {
self.done = true;
return None;
}
}
}
}
pub struct RawEdgeIterator<F> {
neighbors: F,
steps: Vec<TraversalStep>,
step_index: usize,
current_nodes: VecDeque<NodeId>,
pending_edges: VecDeque<RawEdge>,
}
impl<F> RawEdgeIterator<F>
where
F: Fn(NodeId, TraversalDirection, Option<ETypeId>) -> Vec<Edge>,
{
fn new(builder: TraversalBuilder, neighbors: F) -> Self {
for step in &builder.steps {
if matches!(step, TraversalStep::Traverse { .. }) {
panic!("raw_edges() does not support variable-depth traverse()");
}
}
let current_nodes = builder.start_nodes.into_iter().collect();
Self {
neighbors,
steps: builder.steps,
step_index: 0,
current_nodes,
pending_edges: VecDeque::new(),
}
}
}
impl<F> Iterator for RawEdgeIterator<F>
where
F: Fn(NodeId, TraversalDirection, Option<ETypeId>) -> Vec<Edge>,
{
type Item = RawEdge;
fn next(&mut self) -> Option<Self::Item> {
loop {
if let Some(edge) = self.pending_edges.pop_front() {
return Some(edge);
}
if let Some(node_id) = self.current_nodes.pop_front() {
if self.step_index < self.steps.len() {
let TraversalStep::SingleHop {
direction, etype, ..
} = &self.steps[self.step_index]
else {
unreachable!()
};
let edges = (self.neighbors)(node_id, *direction, *etype);
for edge in edges {
self.pending_edges.push_back(RawEdge::from(edge));
}
}
} else {
if self.step_index < self.steps.len() {
self.step_index += 1;
if self.step_index < self.steps.len() {
let TraversalStep::SingleHop { .. } = &self.steps[self.step_index - 1] else {
unreachable!()
};
}
}
if self.pending_edges.is_empty() {
return None;
}
}
}
}
}
pub struct TraversalResults<I> {
iter: I,
}
impl<I> TraversalResults<I>
where
I: Iterator<Item = TraversalResult>,
{
pub fn new(iter: I) -> Self {
Self { iter }
}
pub fn nodes(self) -> NodeIdIterator<I> {
NodeIdIterator { inner: self.iter }
}
pub fn edges(self) -> EdgeIterator<I> {
EdgeIterator { inner: self.iter }
}
pub fn full(self) -> I {
self.iter
}
pub fn to_vec(self) -> Vec<NodeId> {
self.iter.map(|r| r.node_id).collect()
}
pub fn first(mut self) -> Option<TraversalResult> {
self.iter.next()
}
pub fn first_node(mut self) -> Option<NodeId> {
self.iter.next().map(|r| r.node_id)
}
pub fn count(self) -> usize {
self.iter.count()
}
}
pub struct NodeIdIterator<I> {
inner: I,
}
impl<I> Iterator for NodeIdIterator<I>
where
I: Iterator<Item = TraversalResult>,
{
type Item = NodeId;
fn next(&mut self) -> Option<Self::Item> {
self.inner.next().map(|r| r.node_id)
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.inner.size_hint()
}
}
pub struct EdgeIterator<I> {
inner: I,
}
impl<I> Iterator for EdgeIterator<I>
where
I: Iterator<Item = TraversalResult>,
{
type Item = RawEdge;
fn next(&mut self) -> Option<Self::Item> {
loop {
match self.inner.next() {
Some(result) => {
if let Some(edge) = result.edge {
return Some(edge);
}
}
None => return None,
}
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
let (_, upper) = self.inner.size_hint();
(0, upper) }
}
impl TraversalBuilder {
pub fn results<F>(self, neighbors: F) -> TraversalResults<TraversalIterator<F>>
where
F: Fn(NodeId, TraversalDirection, Option<ETypeId>) -> Vec<Edge>,
{
TraversalResults::new(self.execute(neighbors))
}
pub fn first<F>(self, neighbors: F) -> Option<TraversalResult>
where
F: Fn(NodeId, TraversalDirection, Option<ETypeId>) -> Vec<Edge>,
{
self.execute(neighbors).next()
}
pub fn first_node<F>(self, neighbors: F) -> Option<NodeId>
where
F: Fn(NodeId, TraversalDirection, Option<ETypeId>) -> Vec<Edge>,
{
self.execute(neighbors).next().map(|r| r.node_id)
}
pub fn to_vec<F>(self, neighbors: F) -> Vec<NodeId>
where
F: Fn(NodeId, TraversalDirection, Option<ETypeId>) -> Vec<Edge>,
{
self.collect_node_ids(neighbors)
}
}
#[derive(Debug, Clone)]
pub struct NodeResult {
pub id: NodeId,
pub key: Option<String>,
pub props: HashMap<String, PropValue>,
}
impl NodeResult {
pub fn new(id: NodeId) -> Self {
Self {
id,
key: None,
props: HashMap::new(),
}
}
pub fn with_key(mut self, key: String) -> Self {
self.key = Some(key);
self
}
pub fn with_props(mut self, props: HashMap<String, PropValue>) -> Self {
self.props = props;
self
}
pub fn get(&self, name: &str) -> Option<&PropValue> {
self.props.get(name)
}
pub fn string(&self, name: &str) -> Option<&str> {
match self.props.get(name) {
Some(PropValue::String(s)) => Some(s),
_ => None,
}
}
pub fn int(&self, name: &str) -> Option<i64> {
match self.props.get(name) {
Some(PropValue::I64(v)) => Some(*v),
_ => None,
}
}
pub fn float(&self, name: &str) -> Option<f64> {
match self.props.get(name) {
Some(PropValue::F64(v)) => Some(*v),
_ => None,
}
}
pub fn bool(&self, name: &str) -> Option<bool> {
match self.props.get(name) {
Some(PropValue::Bool(v)) => Some(*v),
_ => None,
}
}
}
#[derive(Debug, Clone)]
pub struct FullEdgeResult {
pub src: NodeId,
pub dst: NodeId,
pub etype: ETypeId,
pub props: HashMap<String, PropValue>,
}
impl FullEdgeResult {
pub fn from_raw(edge: RawEdge) -> Self {
Self {
src: edge.src,
dst: edge.dst,
etype: edge.etype,
props: HashMap::new(),
}
}
pub fn with_props(mut self, props: HashMap<String, PropValue>) -> Self {
self.props = props;
self
}
pub fn get(&self, name: &str) -> Option<&PropValue> {
self.props.get(name)
}
}
#[derive(Debug, Clone, Default)]
pub struct CollectOptions {
pub node_props: Option<Vec<String>>,
pub edge_props: Option<Vec<String>>,
pub load_keys: bool,
}
impl CollectOptions {
pub fn new() -> Self {
Self::default()
}
pub fn select_node_props(mut self, props: Vec<String>) -> Self {
self.node_props = Some(props);
self
}
pub fn select_edge_props(mut self, props: Vec<String>) -> Self {
self.edge_props = Some(props);
self
}
pub fn with_keys(mut self) -> Self {
self.load_keys = true;
self
}
}
#[cfg(test)]
mod tests {
use super::*;
fn mock_graph() -> impl Fn(NodeId, TraversalDirection, Option<ETypeId>) -> Vec<Edge> {
move |node_id: NodeId, direction: TraversalDirection, etype: Option<ETypeId>| {
let mut edges = Vec::new();
match direction {
TraversalDirection::Out => match node_id {
1 => {
if etype.is_none() || etype == Some(1) {
edges.push(Edge {
src: 1,
etype: 1,
dst: 2,
});
}
if etype.is_none() || etype == Some(2) {
edges.push(Edge {
src: 1,
etype: 2,
dst: 4,
});
}
}
2 => {
if etype.is_none() || etype == Some(1) {
edges.push(Edge {
src: 2,
etype: 1,
dst: 3,
});
}
if etype.is_none() || etype == Some(2) {
edges.push(Edge {
src: 2,
etype: 2,
dst: 5,
});
}
}
_ => {}
},
TraversalDirection::In => match node_id {
2 => {
if etype.is_none() || etype == Some(1) {
edges.push(Edge {
src: 1,
etype: 1,
dst: 2,
});
}
}
3 => {
if etype.is_none() || etype == Some(1) {
edges.push(Edge {
src: 2,
etype: 1,
dst: 3,
});
}
}
4 => {
if etype.is_none() || etype == Some(2) {
edges.push(Edge {
src: 1,
etype: 2,
dst: 4,
});
}
}
5 => {
if etype.is_none() || etype == Some(2) {
edges.push(Edge {
src: 2,
etype: 2,
dst: 5,
});
}
}
_ => {}
},
TraversalDirection::Both => {
let out_edges = mock_graph()(node_id, TraversalDirection::Out, etype);
let in_edges = mock_graph()(node_id, TraversalDirection::In, etype);
edges.extend(out_edges);
edges.extend(in_edges);
}
}
edges
}
}
#[test]
fn test_single_hop_out() {
let neighbors = mock_graph();
let results: Vec<_> = TraversalBuilder::from_node(1)
.out(Some(1)) .execute(&neighbors)
.collect();
assert_eq!(results.len(), 1);
assert_eq!(results[0].node_id, 2);
}
#[test]
fn test_single_hop_all_etypes() {
let neighbors = mock_graph();
let results: Vec<_> = TraversalBuilder::from_node(1)
.out(None) .execute(&neighbors)
.collect();
assert_eq!(results.len(), 2);
let node_ids: HashSet<_> = results.iter().map(|r| r.node_id).collect();
assert!(node_ids.contains(&2));
assert!(node_ids.contains(&4));
}
#[test]
fn test_two_hops() {
let neighbors = mock_graph();
let results: Vec<_> = TraversalBuilder::from_node(1)
.out(Some(1)) .out(Some(1)) .execute(&neighbors)
.collect();
assert_eq!(results.len(), 1);
assert_eq!(results[0].node_id, 3);
}
#[test]
fn test_incoming() {
let neighbors = mock_graph();
let results: Vec<_> = TraversalBuilder::from_node(3)
.r#in(Some(1)) .execute(&neighbors)
.collect();
assert_eq!(results.len(), 1);
assert_eq!(results[0].node_id, 2);
}
#[test]
fn test_take_limit() {
let neighbors = mock_graph();
let results: Vec<_> = TraversalBuilder::from_node(1)
.out(None)
.take(1)
.execute(&neighbors)
.collect();
assert_eq!(results.len(), 1);
}
#[test]
fn test_count() {
let neighbors = mock_graph();
let count = TraversalBuilder::from_node(1).out(None).count(&neighbors);
assert_eq!(count, 2);
}
#[test]
fn test_count_with_limit() {
let neighbors = mock_graph();
let count = TraversalBuilder::from_node(1)
.out(None)
.take(1)
.count(&neighbors);
assert_eq!(count, 1);
}
#[test]
fn test_traverse_variable_depth() {
let neighbors = mock_graph();
let results: Vec<_> = TraversalBuilder::from_node(1)
.traverse(Some(1), TraverseOptions::new(TraversalDirection::Out, 2))
.execute(&neighbors)
.collect();
assert_eq!(results.len(), 2);
let node_ids: HashSet<_> = results.iter().map(|r| r.node_id).collect();
assert!(node_ids.contains(&2));
assert!(node_ids.contains(&3));
}
#[test]
fn test_traverse_min_depth() {
let neighbors = mock_graph();
let options = TraverseOptions::new(TraversalDirection::Out, 2).with_min_depth(2);
let results: Vec<_> = TraversalBuilder::from_node(1)
.traverse(Some(1), options)
.execute(&neighbors)
.collect();
assert_eq!(results.len(), 1);
assert_eq!(results[0].node_id, 3);
}
#[test]
fn test_multiple_start_nodes() {
let neighbors = mock_graph();
let results: Vec<_> = TraversalBuilder::new(vec![1, 2])
.out(Some(1))
.execute(&neighbors)
.collect();
assert_eq!(results.len(), 1);
assert_eq!(results[0].node_id, 3);
}
#[test]
fn test_unique_false() {
let neighbors = mock_graph();
let results: Vec<_> = TraversalBuilder::new(vec![1, 2])
.unique(false)
.out(Some(1))
.execute(&neighbors)
.collect();
assert_eq!(results.len(), 2);
}
#[test]
fn test_collect_node_ids() {
let neighbors = mock_graph();
let node_ids = TraversalBuilder::from_node(1)
.out(Some(1))
.out(Some(1))
.collect_node_ids(&neighbors);
assert_eq!(node_ids, vec![3]);
}
#[test]
fn test_empty_result() {
let neighbors = mock_graph();
let results: Vec<_> = TraversalBuilder::from_node(999)
.out(None)
.execute(&neighbors)
.collect();
assert!(results.is_empty());
}
#[test]
fn test_no_steps() {
let neighbors = mock_graph();
let results: Vec<_> = TraversalBuilder::from_node(1).execute(&neighbors).collect();
assert_eq!(results.len(), 1);
assert_eq!(results[0].node_id, 1);
}
#[test]
fn test_where_edge_filter_by_etype() {
let neighbors = mock_graph();
let results: Vec<_> = TraversalBuilder::from_node(1)
.out(None) .where_edge(|edge| edge.etype == 1)
.execute(&neighbors)
.collect();
assert_eq!(results.len(), 1);
assert_eq!(results[0].node_id, 2);
}
#[test]
fn test_where_edge_filter_by_dst() {
let neighbors = mock_graph();
let results: Vec<_> = TraversalBuilder::from_node(1)
.out(None)
.where_edge(|edge| edge.dst > 3)
.execute(&neighbors)
.collect();
assert_eq!(results.len(), 1);
assert_eq!(results[0].node_id, 4);
}
#[test]
fn test_where_node_filter() {
let neighbors = mock_graph();
let results: Vec<_> = TraversalBuilder::from_node(1)
.out(None)
.where_node(|node| node.id > 3)
.execute(&neighbors)
.collect();
assert_eq!(results.len(), 1);
assert_eq!(results[0].node_id, 4);
}
#[test]
fn test_where_node_filter_excludes_all() {
let neighbors = mock_graph();
let results: Vec<_> = TraversalBuilder::from_node(1)
.out(None)
.where_node(|node| node.id > 100)
.execute(&neighbors)
.collect();
assert!(results.is_empty());
}
#[test]
fn test_combined_edge_and_node_filters() {
let neighbors = mock_graph();
let results: Vec<_> = TraversalBuilder::from_node(1)
.out(None)
.where_edge(|edge| edge.etype == 2) .where_node(|node| node.id >= 4)
.execute(&neighbors)
.collect();
assert_eq!(results.len(), 1);
assert_eq!(results[0].node_id, 4);
}
#[test]
fn test_filter_with_limit() {
let neighbors = mock_graph();
let results: Vec<_> = TraversalBuilder::from_node(2)
.out(None)
.where_edge(|edge| edge.etype == 1)
.take(10)
.execute(&neighbors)
.collect();
assert_eq!(results.len(), 1);
assert_eq!(results[0].node_id, 3);
}
#[test]
fn test_has_filters_detects_global_edge_filter() {
let builder = TraversalBuilder::from_node(1)
.out(None)
.where_edge(|_| true);
assert!(builder.has_filters());
}
#[test]
fn test_has_filters_detects_global_node_filter() {
let builder = TraversalBuilder::from_node(1)
.out(None)
.where_node(|_| true);
assert!(builder.has_filters());
}
#[test]
fn test_has_filters_false_when_no_filters() {
let builder = TraversalBuilder::from_node(1).out(None);
assert!(!builder.has_filters());
}
#[test]
fn test_count_falls_back_to_iteration_with_filters() {
let neighbors = mock_graph();
let builder = TraversalBuilder::from_node(1)
.out(None)
.where_edge(|edge| edge.etype == 1);
assert!(!builder.can_use_fast_count());
let count = builder.count(&neighbors);
assert_eq!(count, 1);
}
#[test]
fn test_traverse_with_edge_filter() {
let neighbors = mock_graph();
let options =
TraverseOptions::new(TraversalDirection::Out, 2).with_edge_filter(|edge| edge.etype == 1);
let results: Vec<_> = TraversalBuilder::from_node(1)
.traverse(Some(1), options)
.execute(&neighbors)
.collect();
assert_eq!(results.len(), 2);
let node_ids: HashSet<_> = results.iter().map(|r| r.node_id).collect();
assert!(node_ids.contains(&2));
assert!(node_ids.contains(&3));
}
#[test]
fn test_traverse_with_node_filter() {
let neighbors = mock_graph();
let options =
TraverseOptions::new(TraversalDirection::Out, 2).with_node_filter(|node| node.id >= 2);
let results: Vec<_> = TraversalBuilder::from_node(1)
.traverse(Some(1), options)
.execute(&neighbors)
.collect();
assert_eq!(results.len(), 2);
let node_ids: HashSet<_> = results.iter().map(|r| r.node_id).collect();
assert!(node_ids.contains(&2));
assert!(node_ids.contains(&3));
}
#[test]
fn test_traverse_with_node_filter_excludes_intermediate() {
let neighbors = mock_graph();
let options =
TraverseOptions::new(TraversalDirection::Out, 3).with_node_filter(|node| node.id >= 3);
let results: Vec<_> = TraversalBuilder::from_node(1)
.traverse(Some(1), options)
.execute(&neighbors)
.collect();
assert!(results.is_empty());
}
#[test]
fn test_traverse_options_with_combined_filters() {
let neighbors = mock_graph();
let options = TraverseOptions::new(TraversalDirection::Out, 3)
.with_edge_filter(|edge| edge.etype == 1)
.with_node_filter(|node| node.id >= 2);
let results: Vec<_> = TraversalBuilder::from_node(1)
.traverse(None, options)
.execute(&neighbors)
.collect();
let node_ids: HashSet<_> = results.iter().map(|r| r.node_id).collect();
assert!(node_ids.contains(&2));
assert!(node_ids.contains(&3));
}
#[test]
fn test_edge_info_from_raw_edge() {
let raw_edge = RawEdge {
src: 1,
dst: 2,
etype: 3,
};
let edge_info = EdgeInfo::from(raw_edge);
assert_eq!(edge_info.src, 1);
assert_eq!(edge_info.dst, 2);
assert_eq!(edge_info.etype, 3);
assert!(edge_info.props.is_empty());
}
#[test]
fn test_results_to_vec() {
let neighbors = mock_graph();
let nodes = TraversalBuilder::from_node(1)
.out(Some(1))
.results(&neighbors)
.to_vec();
assert_eq!(nodes, vec![2]);
}
#[test]
fn test_results_first() {
let neighbors = mock_graph();
let first = TraversalBuilder::from_node(1)
.out(Some(1))
.results(&neighbors)
.first();
assert!(first.is_some());
assert_eq!(first.expect("expected value").node_id, 2);
}
#[test]
fn test_results_first_node() {
let neighbors = mock_graph();
let first = TraversalBuilder::from_node(1)
.out(Some(1))
.results(&neighbors)
.first_node();
assert_eq!(first, Some(2));
}
#[test]
fn test_results_first_empty() {
let neighbors = mock_graph();
let first = TraversalBuilder::from_node(999)
.out(None)
.results(&neighbors)
.first();
assert!(first.is_none());
}
#[test]
fn test_results_count() {
let neighbors = mock_graph();
let count = TraversalBuilder::from_node(1)
.out(None)
.results(&neighbors)
.count();
assert_eq!(count, 2);
}
#[test]
fn test_results_nodes_iterator() {
let neighbors = mock_graph();
let nodes: Vec<_> = TraversalBuilder::from_node(1)
.out(None)
.results(&neighbors)
.nodes()
.collect();
assert_eq!(nodes.len(), 2);
assert!(nodes.contains(&2));
assert!(nodes.contains(&4));
}
#[test]
fn test_results_edges_iterator() {
let neighbors = mock_graph();
let edges: Vec<_> = TraversalBuilder::from_node(1)
.out(None)
.results(&neighbors)
.edges()
.collect();
assert_eq!(edges.len(), 2);
for edge in &edges {
assert_eq!(edge.src, 1);
}
}
#[test]
fn test_results_full_iterator() {
let neighbors = mock_graph();
let results: Vec<_> = TraversalBuilder::from_node(1)
.out(Some(1))
.results(&neighbors)
.full()
.collect();
assert_eq!(results.len(), 1);
assert_eq!(results[0].node_id, 2);
assert!(results[0].edge.is_some());
assert_eq!(results[0].depth, 1);
}
#[test]
fn test_builder_first_method() {
let neighbors = mock_graph();
let first = TraversalBuilder::from_node(1)
.out(Some(1))
.first(&neighbors);
assert!(first.is_some());
assert_eq!(first.expect("expected value").node_id, 2);
}
#[test]
fn test_builder_first_node_method() {
let neighbors = mock_graph();
let first = TraversalBuilder::from_node(1)
.out(Some(1))
.first_node(&neighbors);
assert_eq!(first, Some(2));
}
#[test]
fn test_builder_to_vec_method() {
let neighbors = mock_graph();
let nodes = TraversalBuilder::from_node(1).out(None).to_vec(&neighbors);
assert_eq!(nodes.len(), 2);
assert!(nodes.contains(&2));
assert!(nodes.contains(&4));
}
#[test]
fn test_node_result_accessors() {
let mut props = HashMap::new();
props.insert("name".to_string(), PropValue::String("Alice".to_string()));
props.insert("age".to_string(), PropValue::I64(30));
props.insert("score".to_string(), PropValue::F64(95.5));
props.insert("active".to_string(), PropValue::Bool(true));
let result = NodeResult::new(1)
.with_key("user:alice".to_string())
.with_props(props);
assert_eq!(result.id, 1);
assert_eq!(result.key, Some("user:alice".to_string()));
assert_eq!(result.string("name"), Some("Alice"));
assert_eq!(result.int("age"), Some(30));
assert_eq!(result.float("score"), Some(95.5));
assert_eq!(result.bool("active"), Some(true));
assert_eq!(result.string("missing"), None);
}
#[test]
fn test_full_edge_result() {
let raw = RawEdge {
src: 1,
dst: 2,
etype: 3,
};
let mut props = HashMap::new();
props.insert("weight".to_string(), PropValue::F64(0.5));
let edge = FullEdgeResult::from_raw(raw).with_props(props);
assert_eq!(edge.src, 1);
assert_eq!(edge.dst, 2);
assert_eq!(edge.etype, 3);
assert!(edge.get("weight").is_some());
}
#[test]
fn test_two_hop_results() {
let neighbors = mock_graph();
let results: Vec<_> = TraversalBuilder::from_node(1)
.out(Some(1))
.out(Some(1))
.results(&neighbors)
.full()
.collect();
assert_eq!(results.len(), 1);
assert_eq!(results[0].node_id, 3);
assert_eq!(results[0].depth, 2);
}
#[test]
fn test_collect_options() {
let opts = CollectOptions::new()
.select_node_props(vec!["name".to_string(), "age".to_string()])
.select_edge_props(vec!["weight".to_string()])
.with_keys();
assert!(opts.node_props.is_some());
assert_eq!(opts.node_props.as_ref().expect("expected value").len(), 2);
assert!(opts.edge_props.is_some());
assert!(opts.load_keys);
}
#[test]
fn test_select_stores_properties() {
let builder = TraversalBuilder::from_node(1)
.out(Some(1))
.select(vec!["name".to_string(), "age".to_string()]);
let selected = builder.selected_properties();
assert!(selected.is_some());
let props = selected.expect("expected value");
assert_eq!(props.len(), 2);
assert!(props.contains(&"name".to_string()));
assert!(props.contains(&"age".to_string()));
}
#[test]
fn test_select_props_with_str_slices() {
let builder = TraversalBuilder::from_node(1)
.out(Some(1))
.select_props(&["name", "email"]);
let selected = builder.selected_properties();
assert!(selected.is_some());
let props = selected.expect("expected value");
assert_eq!(props.len(), 2);
assert!(props.contains(&"name".to_string()));
assert!(props.contains(&"email".to_string()));
}
#[test]
fn test_select_no_properties_by_default() {
let builder = TraversalBuilder::from_node(1).out(Some(1));
assert!(builder.selected_properties().is_none());
}
#[test]
fn test_collect_options_from_builder() {
let builder = TraversalBuilder::from_node(1)
.out(Some(1))
.select_props(&["name", "age"]);
let opts = builder.collect_options();
assert!(opts.node_props.is_some());
let props = opts.node_props.expect("expected value");
assert_eq!(props.len(), 2);
assert!(props.contains(&"name".to_string()));
assert!(props.contains(&"age".to_string()));
}
#[test]
fn test_collect_options_empty_without_select() {
let builder = TraversalBuilder::from_node(1).out(Some(1));
let opts = builder.collect_options();
assert!(opts.node_props.is_none());
}
#[test]
fn test_select_does_not_affect_execution() {
let neighbors = mock_graph();
let results: Vec<_> = TraversalBuilder::from_node(1)
.out(Some(1))
.select_props(&["name", "age"])
.execute(&neighbors)
.collect();
assert_eq!(results.len(), 1);
assert_eq!(results[0].node_id, 2);
}
#[test]
fn test_select_with_take() {
let neighbors = mock_graph();
let results: Vec<_> = TraversalBuilder::from_node(1)
.out(None)
.select_props(&["name"])
.take(1)
.execute(&neighbors)
.collect();
assert_eq!(results.len(), 1);
}
#[test]
fn test_select_with_filters() {
let neighbors = mock_graph();
let results: Vec<_> = TraversalBuilder::from_node(1)
.out(None)
.select_props(&["name"])
.where_edge(|e| e.etype == 1)
.execute(&neighbors)
.collect();
assert_eq!(results.len(), 1);
assert_eq!(results[0].node_id, 2);
}
}