use crate::traversal::{ExecutionContext, Traverser};
use crate::value::Value;
#[derive(Clone, Debug)]
pub struct ConstantStep {
value: Value,
}
impl ConstantStep {
pub fn new(value: impl Into<Value>) -> Self {
Self {
value: value.into(),
}
}
#[inline]
pub fn value(&self) -> &Value {
&self.value
}
}
impl crate::traversal::step::Step for ConstantStep {
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> {
let value = self.value.clone();
input.map(move |t| t.with_value(value.clone()))
}
fn name(&self) -> &'static str {
"constant"
}
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 value = self.value.clone();
Box::new(std::iter::once(input.with_value(value)))
}
}
#[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();
let v0 = 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
});
let v1 = graph.add_vertex("software", {
let mut props = HashMap::new();
props.insert("name".to_string(), Value::String("Ripple".to_string()));
props.insert("lang".to_string(), Value::String("Java".to_string()));
props
});
graph
.add_edge(v0, v1, "created", {
let mut props = HashMap::new();
props.insert("weight".to_string(), Value::Float(1.0));
props
})
.unwrap();
graph
}
mod constant_step_transform_tests {
use super::*;
#[test]
fn replaces_single_value_with_constant() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = ConstantStep::new("replaced");
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);
assert_eq!(output[0].value, Value::String("replaced".to_string()));
}
#[test]
fn replaces_multiple_values_with_same_constant() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = ConstantStep::new(100i64);
let input = vec![
Traverser::new(Value::Int(1)),
Traverser::new(Value::String("hello".to_string())),
Traverser::new(Value::Bool(true)),
];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 3);
assert_eq!(output[0].value, Value::Int(100));
assert_eq!(output[1].value, Value::Int(100));
assert_eq!(output[2].value, Value::Int(100));
}
#[test]
fn replaces_vertex_values_with_constant() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = ConstantStep::new("vertex_found");
let input = vec![
Traverser::from_vertex(VertexId(0)),
Traverser::from_vertex(VertexId(1)),
];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 2);
assert_eq!(output[0].value, Value::String("vertex_found".to_string()));
assert_eq!(output[1].value, Value::String("vertex_found".to_string()));
}
#[test]
fn replaces_edge_values_with_constant() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = ConstantStep::new("edge_found");
let input = vec![Traverser::from_edge(EdgeId(0))];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 1);
assert_eq!(output[0].value, Value::String("edge_found".to_string()));
}
#[test]
fn works_with_null_constant() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = ConstantStep::new(Value::Null);
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);
assert_eq!(output[0].value, Value::Null);
}
}
mod constant_step_metadata_tests {
use super::*;
#[test]
fn preserves_path() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = ConstantStep::new("constant");
let mut traverser = Traverser::new(Value::Int(42));
traverser.extend_path_labeled("start");
traverser.extend_path_labeled("middle");
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("middle"));
assert_eq!(output[0].path.len(), 2);
}
#[test]
fn preserves_loops_count() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = ConstantStep::new("constant");
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 = ConstantStep::new("constant");
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);
}
#[test]
fn preserves_all_metadata() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = ConstantStep::new("constant");
let mut traverser = Traverser::new(Value::Int(42));
traverser.extend_path_labeled("labeled");
traverser.loops = 3;
traverser.bulk = 7;
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("labeled"));
assert_eq!(output[0].loops, 3);
assert_eq!(output[0].bulk, 7);
assert_eq!(output[0].value, Value::String("constant".to_string()));
}
}
mod constant_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 = ConstantStep::new("constant");
let input: Vec<Traverser> = vec![];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert!(output.is_empty());
}
}
}