use std::marker::PhantomData;
use crate::traversal::step::{DynStep, Step};
use crate::traversal::traverser::TraversalSource;
pub struct Traversal<In, Out> {
pub(crate) steps: Vec<Box<dyn DynStep>>,
pub(crate) source: Option<TraversalSource>,
pub(crate) _phantom: PhantomData<fn(In) -> Out>,
}
impl<In, Out> Clone for Traversal<In, Out> {
fn clone(&self) -> Self {
Self {
steps: self.steps.iter().map(|s| s.clone_box()).collect(),
source: self.source.clone(),
_phantom: PhantomData,
}
}
}
impl<In, Out> std::fmt::Debug for Traversal<In, Out> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Traversal")
.field("source", &self.source)
.field("steps_count", &self.steps.len())
.field(
"step_names",
&self.steps.iter().map(|s| s.dyn_name()).collect::<Vec<_>>(),
)
.finish()
}
}
impl<In, Out> Default for Traversal<In, Out> {
fn default() -> Self {
Self::new()
}
}
impl<In, Out> Traversal<In, Out> {
pub fn new() -> Self {
Self {
steps: Vec::new(),
source: None,
_phantom: PhantomData,
}
}
pub fn from_steps(steps: Vec<Box<dyn DynStep>>) -> Self {
Self {
steps,
source: None,
_phantom: PhantomData,
}
}
pub(crate) fn with_source(source: TraversalSource) -> Self {
Self {
steps: Vec::new(),
source: Some(source),
_phantom: PhantomData,
}
}
pub fn add_step<NewOut>(mut self, step: impl Step) -> Traversal<In, NewOut> {
self.steps.push(Box::new(step));
Traversal {
steps: self.steps,
source: self.source,
_phantom: PhantomData,
}
}
pub fn append<Mid>(mut self, other: Traversal<Out, Mid>) -> Traversal<In, Mid> {
self.steps.extend(other.steps);
Traversal {
steps: self.steps,
source: self.source,
_phantom: PhantomData,
}
}
#[allow(dead_code)] pub(crate) fn into_steps(self) -> (Option<TraversalSource>, Vec<Box<dyn DynStep>>) {
(self.source, self.steps)
}
#[inline]
pub fn step_count(&self) -> usize {
self.steps.len()
}
#[inline]
pub fn has_source(&self) -> bool {
self.source.is_some()
}
pub fn source(&self) -> Option<&TraversalSource> {
self.source.as_ref()
}
pub fn step_names(&self) -> Vec<&'static str> {
self.steps.iter().map(|s| s.dyn_name()).collect()
}
#[inline]
pub fn has_barrier(&self) -> bool {
self.steps.iter().any(|s| s.is_barrier())
}
#[inline]
pub fn steps(&self) -> &[Box<dyn DynStep>] {
&self.steps
}
pub fn explain(&self) -> crate::traversal::explain::TraversalExplanation {
crate::traversal::explain::TraversalExplanation::from_steps(None, &self.steps, &[], &[])
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::storage::Graph;
use crate::traversal::context::{ExecutionContext, SnapshotLike};
use crate::traversal::step::IdentityStep;
use crate::traversal::traverser::{TraversalSource, Traverser};
use crate::value::{Value, VertexId};
#[test]
fn new_creates_empty_traversal() {
let t: Traversal<Value, Value> = Traversal::new();
assert_eq!(t.step_count(), 0);
assert!(!t.has_source());
assert!(t.source().is_none());
}
#[test]
fn default_creates_empty_traversal() {
let t: Traversal<Value, Value> = Traversal::default();
assert_eq!(t.step_count(), 0);
assert!(!t.has_source());
}
#[test]
fn with_source_creates_sourced_traversal() {
let t: Traversal<(), Value> = Traversal::with_source(TraversalSource::AllVertices);
assert!(t.has_source());
assert!(matches!(t.source(), Some(TraversalSource::AllVertices)));
assert_eq!(t.step_count(), 0);
}
#[test]
fn add_step_increments_count() {
let t: Traversal<Value, Value> = Traversal::new();
assert_eq!(t.step_count(), 0);
let t: Traversal<Value, Value> = t.add_step(IdentityStep::new());
assert_eq!(t.step_count(), 1);
let t: Traversal<Value, Value> = t.add_step(IdentityStep::new());
assert_eq!(t.step_count(), 2);
}
#[test]
fn add_step_preserves_source() {
let t: Traversal<(), Value> = Traversal::with_source(TraversalSource::AllVertices);
let t: Traversal<(), Value> = t.add_step(IdentityStep::new());
assert!(t.has_source());
assert!(matches!(t.source(), Some(TraversalSource::AllVertices)));
}
#[test]
fn step_names_returns_step_names() {
let t: Traversal<Value, Value> =
Traversal::<Value, Value>::new().add_step(IdentityStep::new());
let t: Traversal<Value, Value> = t.add_step(IdentityStep::new());
let names = t.step_names();
assert_eq!(names.len(), 2);
assert_eq!(names[0], "identity");
assert_eq!(names[1], "identity");
}
#[test]
fn append_merges_steps() {
let t1: Traversal<(), Value> =
Traversal::<(), Value>::with_source(TraversalSource::AllVertices)
.add_step(IdentityStep::new());
let t2: Traversal<Value, Value> =
Traversal::<Value, Value>::new().add_step(IdentityStep::new());
let t2: Traversal<Value, Value> = t2.add_step(IdentityStep::new());
let merged = t1.append(t2);
assert_eq!(merged.step_count(), 3);
assert!(merged.has_source());
}
#[test]
fn append_drops_second_source() {
let t1: Traversal<(), Value> = Traversal::with_source(TraversalSource::AllVertices);
let t2: Traversal<Value, Value> = Traversal::with_source(TraversalSource::AllEdges);
let merged = t1.append(t2);
assert!(merged.has_source());
assert!(matches!(
merged.source(),
Some(TraversalSource::AllVertices)
));
}
#[test]
fn clone_creates_independent_copy() {
let t1: Traversal<Value, Value> =
Traversal::<Value, Value>::new().add_step(IdentityStep::new());
let t2 = t1.clone();
assert_eq!(t1.step_count(), t2.step_count());
let t1_modified: Traversal<Value, Value> = t1.add_step(IdentityStep::new());
assert_eq!(t1_modified.step_count(), 2);
assert_eq!(t2.step_count(), 1);
}
#[test]
fn clone_preserves_source() {
let t1: Traversal<(), Value> =
Traversal::with_source(TraversalSource::Vertices(vec![VertexId(1), VertexId(2)]));
let t2 = t1.clone();
assert!(t2.has_source());
match t2.source() {
Some(TraversalSource::Vertices(ids)) => {
assert_eq!(ids.len(), 2);
assert_eq!(ids[0], VertexId(1));
}
_ => panic!("Expected Vertices source"),
}
}
#[test]
fn into_steps_returns_source_and_steps() {
let t: Traversal<(), Value> =
Traversal::<(), Value>::with_source(TraversalSource::AllVertices)
.add_step(IdentityStep::new());
let t: Traversal<(), Value> = t.add_step(IdentityStep::new());
let (source, steps) = t.into_steps();
assert!(source.is_some());
assert!(matches!(source, Some(TraversalSource::AllVertices)));
assert_eq!(steps.len(), 2);
assert_eq!(steps[0].dyn_name(), "identity");
}
#[test]
fn into_steps_returns_none_source_for_anonymous() {
let t: Traversal<Value, Value> =
Traversal::<Value, Value>::new().add_step(IdentityStep::new());
let (source, steps) = t.into_steps();
assert!(source.is_none());
assert_eq!(steps.len(), 1);
}
#[test]
fn debug_format_shows_info() {
let t: Traversal<(), Value> =
Traversal::<(), Value>::with_source(TraversalSource::AllVertices)
.add_step(IdentityStep::new());
let debug_str = format!("{:?}", t);
assert!(debug_str.contains("Traversal"));
assert!(debug_str.contains("steps_count"));
assert!(debug_str.contains("step_names"));
}
#[test]
fn steps_can_be_executed_from_into_steps() {
let graph = Graph::new();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let t: Traversal<Value, Value> =
Traversal::<Value, Value>::new().add_step(IdentityStep::new());
let (_source, steps) = t.into_steps();
let input: Vec<Traverser> =
vec![Traverser::new(Value::Int(1)), Traverser::new(Value::Int(2))];
let mut current: Box<dyn Iterator<Item = Traverser> + '_> = Box::new(input.into_iter());
for step in &steps {
current = step.apply_dyn(&ctx, current);
}
let results: Vec<Traverser> = current.collect();
assert_eq!(results.len(), 2);
assert_eq!(results[0].value, Value::Int(1));
assert_eq!(results[1].value, Value::Int(2));
}
}