use crate::traversal::{ExecutionContext, Traverser};
use crate::value::Value;
#[derive(Clone, Copy, Debug, Default)]
pub struct PathStep;
impl PathStep {
pub fn new() -> Self {
Self
}
}
impl crate::traversal::step::Step for PathStep {
type Iter<'a>
= impl Iterator<Item = crate::traversal::Traverser> + 'a
where
Self: 'a;
fn apply<'a>(
&'a self,
_ctx: &'a ExecutionContext<'a>,
input: Box<dyn Iterator<Item = Traverser> + 'a>,
) -> Self::Iter<'a> {
input.map(|t| {
let path_values = t.path.to_list();
t.with_value(path_values)
})
}
fn name(&self) -> &'static str {
"path"
}
fn category(&self) -> crate::traversal::explain::StepCategory {
crate::traversal::explain::StepCategory::Transform
}
fn apply_streaming(
&self,
_ctx: crate::traversal::context::StreamingContext,
input: Traverser,
) -> Box<dyn Iterator<Item = Traverser> + Send + 'static> {
let path_values = input.path.to_list();
Box::new(std::iter::once(input.with_value(path_values)))
}
}
#[derive(Clone, Debug)]
pub struct AsStep {
label: String,
}
impl AsStep {
pub fn new(label: impl Into<String>) -> Self {
Self {
label: label.into(),
}
}
#[inline]
pub fn label(&self) -> &str {
&self.label
}
}
impl crate::traversal::step::Step for AsStep {
type Iter<'a>
= impl Iterator<Item = crate::traversal::Traverser> + 'a
where
Self: 'a;
fn apply<'a>(
&'a self,
_ctx: &'a ExecutionContext<'a>,
input: Box<dyn Iterator<Item = Traverser> + 'a>,
) -> Self::Iter<'a> {
let label = self.label.clone();
input.map(move |mut t| {
t.label_path_position(&label);
t
})
}
fn name(&self) -> &'static str {
"as"
}
fn category(&self) -> crate::traversal::explain::StepCategory {
crate::traversal::explain::StepCategory::Modulator
}
fn apply_streaming(
&self,
_ctx: crate::traversal::context::StreamingContext,
mut input: Traverser,
) -> Box<dyn Iterator<Item = Traverser> + Send + 'static> {
input.label_path_position(&self.label);
Box::new(std::iter::once(input))
}
}
#[derive(Clone, Debug)]
pub struct SelectStep {
labels: Vec<String>,
}
impl SelectStep {
pub fn new(labels: impl IntoIterator<Item = impl Into<String>>) -> Self {
Self {
labels: labels.into_iter().map(Into::into).collect(),
}
}
pub fn single(label: impl Into<String>) -> Self {
Self {
labels: vec![label.into()],
}
}
}
impl crate::traversal::step::Step for SelectStep {
type Iter<'a>
= impl Iterator<Item = crate::traversal::Traverser> + 'a
where
Self: 'a;
fn apply<'a>(
&'a self,
_ctx: &'a ExecutionContext<'a>,
input: Box<dyn Iterator<Item = Traverser> + 'a>,
) -> Self::Iter<'a> {
let labels = self.labels.clone();
input.filter_map(move |t| {
if labels.len() == 1 {
let val = t
.path
.get(&labels[0])
.and_then(|values| values.last().cloned())
.map(|pv| pv.to_value());
val.map(|v| t.with_value(v))
} else {
let mut map = std::collections::HashMap::new();
let mut found_any = false;
let mut missing_any = false;
for label in &labels {
if let Some(values) = t.path.get(label) {
if let Some(last_val) = values.last() {
map.insert(label.clone(), last_val.to_value());
found_any = true;
} else {
missing_any = true;
}
} else {
missing_any = true;
}
}
if !missing_any && found_any {
Some(t.with_value(Value::Map(map.into_iter().collect())))
} else {
None
}
}
})
}
fn name(&self) -> &'static str {
"select"
}
fn category(&self) -> crate::traversal::explain::StepCategory {
crate::traversal::explain::StepCategory::Transform
}
fn apply_streaming(
&self,
_ctx: crate::traversal::context::StreamingContext,
input: Traverser,
) -> Box<dyn Iterator<Item = Traverser> + Send + 'static> {
if self.labels.len() == 1 {
let val = input
.path
.get(&self.labels[0])
.and_then(|values| values.last().cloned())
.map(|pv| pv.to_value());
match val {
Some(v) => Box::new(std::iter::once(input.with_value(v))),
None => Box::new(std::iter::empty()),
}
} else {
let mut map = std::collections::HashMap::new();
let mut found_any = false;
let mut missing_any = false;
for label in &self.labels {
if let Some(values) = input.path.get(label) {
if let Some(last_val) = values.last() {
map.insert(label.clone(), last_val.to_value());
found_any = true;
} else {
missing_any = true;
}
} else {
missing_any = true;
}
}
if !missing_any && found_any {
Box::new(std::iter::once(
input.with_value(Value::Map(map.into_iter().collect())),
))
} else {
Box::new(std::iter::empty())
}
}
}
}
#[derive(Clone, Copy, Debug, Default)]
pub struct SelectKeysStep;
impl SelectKeysStep {
pub fn new() -> Self {
Self
}
}
impl crate::traversal::step::Step for SelectKeysStep {
type Iter<'a>
= impl Iterator<Item = Traverser> + 'a
where
Self: 'a;
fn apply<'a>(
&'a self,
_ctx: &'a ExecutionContext<'a>,
input: Box<dyn Iterator<Item = Traverser> + 'a>,
) -> Self::Iter<'a> {
input.filter_map(|t| match &t.value {
Value::Map(map) => {
let keys: Vec<Value> = map.keys().map(|k| Value::String(k.clone())).collect();
if keys.len() == 1 {
Some(t.with_value(keys.into_iter().next().unwrap()))
} else {
Some(t.with_value(Value::List(keys)))
}
}
_ => None, })
}
fn name(&self) -> &'static str {
"select_keys"
}
fn category(&self) -> crate::traversal::explain::StepCategory {
crate::traversal::explain::StepCategory::Transform
}
fn apply_streaming(
&self,
_ctx: crate::traversal::context::StreamingContext,
input: Traverser,
) -> Box<dyn Iterator<Item = Traverser> + Send + 'static> {
match &input.value {
Value::Map(map) => {
let keys: Vec<Value> = map.keys().map(|k| Value::String(k.clone())).collect();
if keys.len() == 1 {
Box::new(std::iter::once(
input.with_value(keys.into_iter().next().unwrap()),
))
} else {
Box::new(std::iter::once(input.with_value(Value::List(keys))))
}
}
_ => Box::new(std::iter::empty()),
}
}
}
#[derive(Clone, Copy, Debug, Default)]
pub struct SelectValuesStep;
impl SelectValuesStep {
pub fn new() -> Self {
Self
}
}
impl crate::traversal::step::Step for SelectValuesStep {
type Iter<'a>
= impl Iterator<Item = Traverser> + 'a
where
Self: 'a;
fn apply<'a>(
&'a self,
_ctx: &'a ExecutionContext<'a>,
input: Box<dyn Iterator<Item = Traverser> + 'a>,
) -> Self::Iter<'a> {
input.filter_map(|t| match &t.value {
Value::Map(map) => {
let values: Vec<Value> = map.values().cloned().collect();
if values.len() == 1 {
Some(t.with_value(values.into_iter().next().unwrap()))
} else {
Some(t.with_value(Value::List(values)))
}
}
_ => None, })
}
fn name(&self) -> &'static str {
"select_values"
}
fn category(&self) -> crate::traversal::explain::StepCategory {
crate::traversal::explain::StepCategory::Transform
}
fn apply_streaming(
&self,
_ctx: crate::traversal::context::StreamingContext,
input: Traverser,
) -> Box<dyn Iterator<Item = Traverser> + Send + 'static> {
match &input.value {
Value::Map(map) => {
let values: Vec<Value> = map.values().cloned().collect();
if values.len() == 1 {
Box::new(std::iter::once(
input.with_value(values.into_iter().next().unwrap()),
))
} else {
Box::new(std::iter::once(input.with_value(Value::List(values))))
}
}
_ => Box::new(std::iter::empty()),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::storage::Graph;
use crate::traversal::step::Step;
use crate::traversal::SnapshotLike;
use crate::value::{EdgeId, VertexId};
use std::collections::HashMap;
fn create_test_graph() -> Graph {
let graph = Graph::new();
graph.add_vertex("person", {
let mut props = HashMap::new();
props.insert("name".to_string(), Value::String("Alice".to_string()));
props.insert("age".to_string(), Value::Int(30));
props
});
graph
}
mod path_step_construction {
use super::*;
#[test]
fn test_new() {
let step = PathStep::new();
assert_eq!(step.name(), "path");
}
}
mod path_step_empty_path_tests {
use super::*;
#[test]
fn empty_path_returns_empty_list() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = PathStep::new();
let input = vec![Traverser::new(Value::Int(42))];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 1);
if let Value::List(elements) = &output[0].value {
assert!(elements.is_empty());
} else {
panic!("Expected Value::List");
}
}
}
mod path_step_with_elements_tests {
use super::*;
use crate::traversal::PathValue;
#[test]
fn path_with_vertices_and_edges() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = PathStep::new();
let mut traverser = Traverser::from_vertex(VertexId(0));
traverser.path.push(PathValue::Vertex(VertexId(0)), &[]);
traverser.path.push(PathValue::Edge(EdgeId(0)), &[]);
traverser.path.push(PathValue::Vertex(VertexId(1)), &[]);
let input = vec![traverser];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 1);
if let Value::List(elements) = &output[0].value {
assert_eq!(elements.len(), 3);
assert_eq!(elements[0], Value::Vertex(VertexId(0)));
assert_eq!(elements[1], Value::Edge(EdgeId(0)));
assert_eq!(elements[2], Value::Vertex(VertexId(1)));
} else {
panic!("Expected Value::List");
}
}
#[test]
fn path_with_values() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = PathStep::new();
let mut traverser = Traverser::new(Value::Int(1));
traverser.path.push(PathValue::Property(Value::Int(1)), &[]);
traverser
.path
.push(PathValue::Property(Value::String("step2".to_string())), &[]);
traverser
.path
.push(PathValue::Property(Value::Bool(true)), &[]);
let input = vec![traverser];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 1);
if let Value::List(elements) = &output[0].value {
assert_eq!(elements.len(), 3);
assert_eq!(elements[0], Value::Int(1));
assert_eq!(elements[1], Value::String("step2".to_string()));
assert_eq!(elements[2], Value::Bool(true));
} else {
panic!("Expected Value::List");
}
}
}
mod path_step_with_labels_tests {
use super::*;
use crate::traversal::PathValue;
#[test]
fn path_preserves_labels_in_traverser() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = PathStep::new();
let mut traverser = Traverser::from_vertex(VertexId(1));
traverser
.path
.push_labeled(PathValue::Vertex(VertexId(0)), "start");
traverser
.path
.push_labeled(PathValue::Vertex(VertexId(1)), "end");
let input = vec![traverser];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 1);
assert!(output[0].path.has_label("start"));
assert!(output[0].path.has_label("end"));
if let Value::List(elements) = &output[0].value {
assert_eq!(elements.len(), 2);
} else {
panic!("Expected Value::List");
}
}
#[test]
fn path_with_multiple_labels_on_same_element() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = PathStep::new();
let mut traverser = Traverser::from_vertex(VertexId(0));
traverser.path.push(
PathValue::Vertex(VertexId(0)),
&["a".to_string(), "b".to_string()],
);
let input = vec![traverser];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 1);
assert!(output[0].path.has_label("a"));
assert!(output[0].path.has_label("b"));
}
}
mod path_step_metadata_tests {
use super::*;
use crate::traversal::PathValue;
#[test]
fn preserves_path_structure() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = PathStep::new();
let mut traverser = Traverser::new(Value::Int(42));
traverser
.path
.push_labeled(PathValue::Vertex(VertexId(0)), "start");
let input = vec![traverser];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 1);
assert!(output[0].path.has_label("start"));
assert_eq!(output[0].path.len(), 1);
}
#[test]
fn preserves_loops_count() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = PathStep::new();
let mut traverser = Traverser::new(Value::Int(42));
traverser.loops = 5;
let input = vec![traverser];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 1);
assert_eq!(output[0].loops, 5);
}
#[test]
fn preserves_bulk_count() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = PathStep::new();
let mut traverser = Traverser::new(Value::Int(42));
traverser.bulk = 10;
let input = vec![traverser];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 1);
assert_eq!(output[0].bulk, 10);
}
}
mod path_step_empty_tests {
use super::*;
#[test]
fn empty_input_returns_empty_output() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = PathStep::new();
let input: Vec<Traverser> = vec![];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert!(output.is_empty());
}
}
mod path_step_multiple_traversers_tests {
use super::*;
use crate::traversal::PathValue;
#[test]
fn handles_multiple_traversers() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = PathStep::new();
let mut t1 = Traverser::from_vertex(VertexId(0));
t1.path.push_labeled(PathValue::Vertex(VertexId(0)), "a");
let mut t2 = Traverser::from_vertex(VertexId(1));
t2.path.push_labeled(PathValue::Vertex(VertexId(1)), "b");
let input = vec![t1, t2];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 2);
if let Value::List(elements) = &output[0].value {
assert_eq!(elements[0], Value::Vertex(VertexId(0)));
} else {
panic!("Expected Value::List");
}
if let Value::List(elements) = &output[1].value {
assert_eq!(elements[0], Value::Vertex(VertexId(1)));
} else {
panic!("Expected Value::List");
}
}
}
}