use std::collections::{HashSet, VecDeque};
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
use parking_lot::RwLock;
use crate::impl_filter_step;
use crate::traversal::step::Step;
use crate::traversal::{ExecutionContext, StreamingContext, Traverser};
use crate::value::{EdgeId, Value, VertexId};
#[derive(Clone, Debug)]
pub struct HasLabelStep {
labels: Vec<String>,
}
impl HasLabelStep {
pub fn new(labels: Vec<String>) -> Self {
Self { labels }
}
pub fn single(label: impl Into<String>) -> Self {
Self {
labels: vec![label.into()],
}
}
pub fn any<I, S>(labels: I) -> Self
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
Self {
labels: labels.into_iter().map(Into::into).collect(),
}
}
fn matches(&self, ctx: &ExecutionContext, traverser: &Traverser) -> bool {
match &traverser.value {
Value::Vertex(id) => {
if let Some(vertex) = ctx.storage().get_vertex(*id) {
self.labels.iter().any(|l| l == &vertex.label)
} else {
false
}
}
Value::Edge(id) => {
if let Some(edge) = ctx.storage().get_edge(*id) {
self.labels.iter().any(|l| l == &edge.label)
} else {
false
}
}
_ => false,
}
}
fn matches_streaming(&self, ctx: &StreamingContext, traverser: &Traverser) -> bool {
match &traverser.value {
Value::Vertex(id) => {
if let Some(vertex) = ctx.storage().get_vertex(*id) {
self.labels.iter().any(|l| l == &vertex.label)
} else {
false
}
}
Value::Edge(id) => {
if let Some(edge) = ctx.storage().get_edge(*id) {
self.labels.iter().any(|l| l == &edge.label)
} else {
false
}
}
_ => false,
}
}
}
impl_filter_step!(HasLabelStep, "hasLabel", category = crate::traversal::explain::StepCategory::Filter, describe = |s: &HasLabelStep| Some(s.labels.iter().map(|l| format!("\"{l}\"")).collect::<Vec<_>>().join(", ")));
#[cfg(feature = "reactive")]
impl crate::traversal::reactive::StepIntrospect for HasLabelStep {
fn label_constraints(&self) -> Option<Vec<String>> {
Some(self.labels.clone())
}
}
#[derive(Clone, Debug)]
pub struct HasStep {
key: String,
}
impl HasStep {
pub fn new(key: impl Into<String>) -> Self {
Self { key: key.into() }
}
fn matches(&self, ctx: &ExecutionContext, traverser: &Traverser) -> bool {
match &traverser.value {
Value::Vertex(id) => {
ctx.storage()
.get_vertex(*id)
.map(|v| v.properties.contains_key(&self.key))
.unwrap_or(false)
}
Value::Edge(id) => {
ctx.storage()
.get_edge(*id)
.map(|e| e.properties.contains_key(&self.key))
.unwrap_or(false)
}
_ => false,
}
}
fn matches_streaming(&self, ctx: &StreamingContext, traverser: &Traverser) -> bool {
match &traverser.value {
Value::Vertex(id) => ctx
.storage()
.get_vertex(*id)
.map(|v| v.properties.contains_key(&self.key))
.unwrap_or(false),
Value::Edge(id) => ctx
.storage()
.get_edge(*id)
.map(|e| e.properties.contains_key(&self.key))
.unwrap_or(false),
_ => false,
}
}
}
impl_filter_step!(HasStep, "has", category = crate::traversal::explain::StepCategory::Filter, describe = |s: &HasStep| Some(format!("\"{}\"", s.key)), filter_key = |s: &HasStep| Some(s.key.clone()));
#[cfg(feature = "reactive")]
impl crate::traversal::reactive::StepIntrospect for HasStep {
fn property_constraints(&self) -> Option<Vec<String>> {
Some(vec![self.key.clone()])
}
}
#[derive(Clone, Debug)]
pub struct HasNotStep {
key: String,
}
impl HasNotStep {
pub fn new(key: impl Into<String>) -> Self {
Self { key: key.into() }
}
fn matches(&self, ctx: &ExecutionContext, traverser: &Traverser) -> bool {
match &traverser.value {
Value::Vertex(id) => {
ctx.storage()
.get_vertex(*id)
.map(|v| !v.properties.contains_key(&self.key))
.unwrap_or(true) }
Value::Edge(id) => {
ctx.storage()
.get_edge(*id)
.map(|e| !e.properties.contains_key(&self.key))
.unwrap_or(true) }
_ => true,
}
}
fn matches_streaming(&self, ctx: &StreamingContext, traverser: &Traverser) -> bool {
match &traverser.value {
Value::Vertex(id) => ctx
.storage()
.get_vertex(*id)
.map(|v| !v.properties.contains_key(&self.key))
.unwrap_or(true),
Value::Edge(id) => ctx
.storage()
.get_edge(*id)
.map(|e| !e.properties.contains_key(&self.key))
.unwrap_or(true),
_ => true,
}
}
}
impl_filter_step!(HasNotStep, "hasNot", category = crate::traversal::explain::StepCategory::Filter, describe = |s: &HasNotStep| Some(format!("\"{}\"", s.key)), filter_key = |s: &HasNotStep| Some(s.key.clone()));
#[derive(Clone, Debug)]
pub struct HasValueStep {
key: String,
value: Value,
}
impl HasValueStep {
pub fn new(key: impl Into<String>, value: impl Into<Value>) -> Self {
Self {
key: key.into(),
value: value.into(),
}
}
fn matches(&self, ctx: &ExecutionContext, traverser: &Traverser) -> bool {
match &traverser.value {
Value::Vertex(id) => {
if let Some(vertex) = ctx.storage().get_vertex(*id) {
vertex
.properties
.get(&self.key)
.map(|pv| pv == &self.value)
.unwrap_or(false)
} else {
false
}
}
Value::Edge(id) => {
if let Some(edge) = ctx.storage().get_edge(*id) {
edge.properties
.get(&self.key)
.map(|pv| pv == &self.value)
.unwrap_or(false)
} else {
false
}
}
_ => false,
}
}
fn matches_streaming(&self, ctx: &StreamingContext, traverser: &Traverser) -> bool {
match &traverser.value {
Value::Vertex(id) => ctx
.storage()
.get_vertex(*id)
.and_then(|v| v.properties.get(&self.key).cloned())
.map(|pv| pv == self.value)
.unwrap_or(false),
Value::Edge(id) => ctx
.storage()
.get_edge(*id)
.and_then(|e| e.properties.get(&self.key).cloned())
.map(|pv| pv == self.value)
.unwrap_or(false),
_ => false,
}
}
}
impl_filter_step!(HasValueStep, "has", category = crate::traversal::explain::StepCategory::Filter, describe = |s: &HasValueStep| {
use crate::traversal::explain::format_value;
Some(format!("\"{}\" = {}", s.key, format_value(&s.value)))
}, filter_key = |s: &HasValueStep| Some(s.key.clone()));
#[cfg(feature = "reactive")]
impl crate::traversal::reactive::StepIntrospect for HasValueStep {
fn property_constraints(&self) -> Option<Vec<String>> {
Some(vec![self.key.clone()])
}
}
#[derive(Clone)]
pub struct FilterStep<F>
where
F: Fn(&ExecutionContext, &Value) -> bool + Clone + Send + Sync,
{
predicate: F,
}
impl<F> FilterStep<F>
where
F: Fn(&ExecutionContext, &Value) -> bool + Clone + Send + Sync,
{
pub fn new(predicate: F) -> Self {
Self { predicate }
}
}
impl<F> std::fmt::Debug for FilterStep<F>
where
F: Fn(&ExecutionContext, &Value) -> bool + Clone + Send + Sync,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("FilterStep")
.field("predicate", &"<closure>")
.finish()
}
}
impl<F> Step for FilterStep<F>
where
F: Fn(&ExecutionContext, &Value) -> bool + Clone + Send + Sync + 'static,
{
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 predicate = self.predicate.clone();
input.filter(move |t| predicate(ctx, &t.value))
}
fn name(&self) -> &'static str {
"filter"
}
fn category(&self) -> crate::traversal::explain::StepCategory {
crate::traversal::explain::StepCategory::Filter
}
fn apply_streaming(
&self,
_ctx: crate::traversal::context::StreamingContext,
input: Traverser,
) -> Box<dyn Iterator<Item = Traverser> + Send + 'static> {
Box::new(std::iter::once(input))
}
}
#[derive(Clone, Debug)]
pub struct DedupStep {
seen: Arc<RwLock<HashSet<Value>>>,
}
impl DedupStep {
pub fn new() -> Self {
Self {
seen: Arc::new(RwLock::new(HashSet::new())),
}
}
}
impl Default for DedupStep {
fn default() -> Self {
Self::new()
}
}
impl Step for DedupStep {
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 mut seen = std::collections::HashSet::new();
input.filter(move |t| {
if seen.contains(&t.value) {
false
} else {
seen.insert(t.value.clone());
true
}
})
}
fn name(&self) -> &'static str {
"dedup"
}
fn is_barrier(&self) -> bool {
true
}
fn category(&self) -> crate::traversal::explain::StepCategory {
crate::traversal::explain::StepCategory::Filter
}
fn apply_streaming(
&self,
_ctx: crate::traversal::context::StreamingContext,
input: Traverser,
) -> Box<dyn Iterator<Item = Traverser> + Send + 'static> {
let mut seen = self.seen.write();
if seen.contains(&input.value) {
Box::new(std::iter::empty())
} else {
seen.insert(input.value.clone());
Box::new(std::iter::once(input))
}
}
}
#[derive(Clone, Debug)]
pub struct DedupByKeyStep {
key: String,
seen: Arc<RwLock<HashSet<Value>>>,
}
impl DedupByKeyStep {
pub fn new(key: impl Into<String>) -> Self {
Self {
key: key.into(),
seen: Arc::new(RwLock::new(HashSet::new())),
}
}
fn extract_key(&self, ctx: &ExecutionContext, traverser: &Traverser) -> Value {
match &traverser.value {
Value::Vertex(id) => ctx
.storage()
.get_vertex(*id)
.and_then(|v| v.properties.get(&self.key).cloned())
.unwrap_or(Value::Null),
Value::Edge(id) => ctx
.storage()
.get_edge(*id)
.and_then(|e| e.properties.get(&self.key).cloned())
.unwrap_or(Value::Null),
_ => Value::Null,
}
}
fn extract_key_streaming(&self, ctx: &StreamingContext, traverser: &Traverser) -> Value {
match &traverser.value {
Value::Vertex(id) => ctx
.storage()
.get_vertex(*id)
.and_then(|v| v.properties.get(&self.key).cloned())
.unwrap_or(Value::Null),
Value::Edge(id) => ctx
.storage()
.get_edge(*id)
.and_then(|e| e.properties.get(&self.key).cloned())
.unwrap_or(Value::Null),
_ => Value::Null,
}
}
}
impl Step for DedupByKeyStep {
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 mut seen = std::collections::HashSet::new();
input.filter(move |t| {
let key = self.extract_key(ctx, t);
seen.insert(key)
})
}
fn name(&self) -> &'static str {
"dedup"
}
fn is_barrier(&self) -> bool {
true
}
fn category(&self) -> crate::traversal::explain::StepCategory {
crate::traversal::explain::StepCategory::Filter
}
fn apply_streaming(
&self,
ctx: crate::traversal::context::StreamingContext,
input: Traverser,
) -> Box<dyn Iterator<Item = Traverser> + Send + 'static> {
let key = self.extract_key_streaming(&ctx, &input);
let mut seen = self.seen.write();
if seen.contains(&key) {
Box::new(std::iter::empty())
} else {
seen.insert(key);
Box::new(std::iter::once(input))
}
}
}
#[derive(Clone, Debug)]
pub struct DedupByLabelStep {
seen: Arc<RwLock<HashSet<String>>>,
}
impl DedupByLabelStep {
pub fn new() -> Self {
Self {
seen: Arc::new(RwLock::new(HashSet::new())),
}
}
fn extract_label(&self, ctx: &ExecutionContext, traverser: &Traverser) -> String {
match &traverser.value {
Value::Vertex(id) => ctx
.storage()
.get_vertex(*id)
.map(|v| v.label.clone())
.unwrap_or_default(),
Value::Edge(id) => ctx
.storage()
.get_edge(*id)
.map(|e| e.label.clone())
.unwrap_or_default(),
_ => String::new(),
}
}
fn extract_label_streaming(&self, ctx: &StreamingContext, traverser: &Traverser) -> String {
match &traverser.value {
Value::Vertex(id) => ctx
.storage()
.get_vertex(*id)
.map(|v| v.label.clone())
.unwrap_or_default(),
Value::Edge(id) => ctx
.storage()
.get_edge(*id)
.map(|e| e.label.clone())
.unwrap_or_default(),
_ => String::new(),
}
}
}
impl Default for DedupByLabelStep {
fn default() -> Self {
Self::new()
}
}
impl Step for DedupByLabelStep {
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 mut seen = std::collections::HashSet::new();
input.filter(move |t| {
let label = self.extract_label(ctx, t);
seen.insert(label)
})
}
fn name(&self) -> &'static str {
"dedup"
}
fn is_barrier(&self) -> bool {
true
}
fn category(&self) -> crate::traversal::explain::StepCategory {
crate::traversal::explain::StepCategory::Filter
}
fn apply_streaming(
&self,
ctx: crate::traversal::context::StreamingContext,
input: Traverser,
) -> Box<dyn Iterator<Item = Traverser> + Send + 'static> {
let label = self.extract_label_streaming(&ctx, &input);
let mut seen = self.seen.write();
if seen.contains(&label) {
Box::new(std::iter::empty())
} else {
seen.insert(label);
Box::new(std::iter::once(input))
}
}
}
#[derive(Clone)]
pub struct DedupByTraversalStep {
sub: crate::traversal::Traversal<Value, Value>,
seen: Arc<RwLock<HashSet<Value>>>,
}
impl DedupByTraversalStep {
pub fn new(sub: crate::traversal::Traversal<Value, Value>) -> Self {
Self {
sub,
seen: Arc::new(RwLock::new(HashSet::new())),
}
}
fn extract_key(&self, ctx: &ExecutionContext, traverser: &Traverser) -> Value {
use crate::traversal::step::execute_traversal_from;
let sub_input = Box::new(std::iter::once(traverser.clone()));
let mut results = execute_traversal_from(ctx, &self.sub, sub_input);
results.next().map(|t| t.value).unwrap_or(Value::Null)
}
fn extract_key_streaming(&self, ctx: &StreamingContext, traverser: &Traverser) -> Value {
use crate::traversal::step::execute_traversal_streaming;
let mut results = execute_traversal_streaming(ctx, &self.sub, traverser.clone());
results.next().map(|t| t.value).unwrap_or(Value::Null)
}
}
impl std::fmt::Debug for DedupByTraversalStep {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("DedupByTraversalStep")
.field("sub", &"<traversal>")
.finish()
}
}
impl Step for DedupByTraversalStep {
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 mut seen = std::collections::HashSet::new();
input.filter(move |t| {
let key = self.extract_key(ctx, t);
seen.insert(key)
})
}
fn name(&self) -> &'static str {
"dedup"
}
fn is_barrier(&self) -> bool {
true
}
fn category(&self) -> crate::traversal::explain::StepCategory {
crate::traversal::explain::StepCategory::Filter
}
fn apply_streaming(
&self,
ctx: crate::traversal::context::StreamingContext,
input: Traverser,
) -> Box<dyn Iterator<Item = Traverser> + Send + 'static> {
let key = self.extract_key_streaming(&ctx, &input);
let mut seen = self.seen.write();
if seen.contains(&key) {
Box::new(std::iter::empty())
} else {
seen.insert(key);
Box::new(std::iter::once(input))
}
}
}
#[derive(Clone, Debug)]
pub struct LimitStep {
limit: usize,
seen: Arc<AtomicUsize>,
}
impl LimitStep {
pub fn new(limit: usize) -> Self {
Self {
limit,
seen: Arc::new(AtomicUsize::new(0)),
}
}
}
impl Step for LimitStep {
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.take(self.limit)
}
fn name(&self) -> &'static str {
"limit"
}
fn category(&self) -> crate::traversal::explain::StepCategory {
crate::traversal::explain::StepCategory::Filter
}
fn describe(&self) -> Option<String> {
Some(format!("{}", self.limit))
}
fn apply_streaming(
&self,
_ctx: crate::traversal::context::StreamingContext,
input: Traverser,
) -> Box<dyn Iterator<Item = Traverser> + Send + 'static> {
let count = self.seen.fetch_add(1, Ordering::SeqCst);
if count < self.limit {
Box::new(std::iter::once(input))
} else {
Box::new(std::iter::empty())
}
}
}
#[derive(Clone, Debug)]
pub struct SkipStep {
count: usize,
seen: Arc<AtomicUsize>,
}
impl SkipStep {
pub fn new(count: usize) -> Self {
Self {
count,
seen: Arc::new(AtomicUsize::new(0)),
}
}
}
impl Step for SkipStep {
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.skip(self.count)
}
fn name(&self) -> &'static str {
"skip"
}
fn category(&self) -> crate::traversal::explain::StepCategory {
crate::traversal::explain::StepCategory::Filter
}
fn describe(&self) -> Option<String> {
Some(format!("{}", self.count))
}
fn apply_streaming(
&self,
_ctx: crate::traversal::context::StreamingContext,
input: Traverser,
) -> Box<dyn Iterator<Item = Traverser> + Send + 'static> {
let index = self.seen.fetch_add(1, Ordering::SeqCst);
if index < self.count {
Box::new(std::iter::empty())
} else {
Box::new(std::iter::once(input))
}
}
}
#[derive(Clone, Debug)]
pub struct RangeStep {
start: usize,
end: usize,
seen: Arc<AtomicUsize>,
}
impl RangeStep {
pub fn new(start: usize, end: usize) -> Self {
Self {
start,
end,
seen: Arc::new(AtomicUsize::new(0)),
}
}
}
impl Step for RangeStep {
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 take_count = self.end.saturating_sub(self.start);
input.skip(self.start).take(take_count)
}
fn name(&self) -> &'static str {
"range"
}
fn category(&self) -> crate::traversal::explain::StepCategory {
crate::traversal::explain::StepCategory::Filter
}
fn describe(&self) -> Option<String> {
Some(format!("{}..{}", self.start, self.end))
}
fn apply_streaming(
&self,
_ctx: crate::traversal::context::StreamingContext,
input: Traverser,
) -> Box<dyn Iterator<Item = Traverser> + Send + 'static> {
let index = self.seen.fetch_add(1, Ordering::SeqCst);
if index >= self.start && index < self.end {
Box::new(std::iter::once(input))
} else {
Box::new(std::iter::empty())
}
}
}
#[derive(Clone, Debug)]
pub struct HasIdStep {
ids: Vec<Value>,
}
impl HasIdStep {
pub fn vertex(id: VertexId) -> Self {
Self {
ids: vec![Value::Vertex(id)],
}
}
pub fn vertices(ids: Vec<VertexId>) -> Self {
Self {
ids: ids.into_iter().map(Value::Vertex).collect(),
}
}
pub fn edge(id: EdgeId) -> Self {
Self {
ids: vec![Value::Edge(id)],
}
}
pub fn edges(ids: Vec<EdgeId>) -> Self {
Self {
ids: ids.into_iter().map(Value::Edge).collect(),
}
}
pub fn from_value(value: impl Into<Value>) -> Self {
Self {
ids: vec![value.into()],
}
}
pub fn from_values(values: Vec<Value>) -> Self {
Self { ids: values }
}
fn matches(&self, _ctx: &ExecutionContext, traverser: &Traverser) -> bool {
match &traverser.value {
Value::Vertex(id) => {
self.ids.iter().any(|target| match target {
Value::Vertex(target_id) => target_id == id,
_ => false,
})
}
Value::Edge(id) => {
self.ids.iter().any(|target| match target {
Value::Edge(target_id) => target_id == id,
_ => false,
})
}
_ => false,
}
}
fn matches_streaming(&self, _ctx: &StreamingContext, traverser: &Traverser) -> bool {
match &traverser.value {
Value::Vertex(id) => self.ids.iter().any(|target| match target {
Value::Vertex(target_id) => target_id == id,
_ => false,
}),
Value::Edge(id) => self.ids.iter().any(|target| match target {
Value::Edge(target_id) => target_id == id,
_ => false,
}),
_ => false,
}
}
}
impl_filter_step!(HasIdStep, "hasId", category = crate::traversal::explain::StepCategory::Filter, describe = |s: &HasIdStep| {
use crate::traversal::explain::format_value;
let ids: Vec<String> = s.ids.iter().map(format_value).collect();
Some(ids.join(", "))
});
#[derive(Clone)]
pub struct HasWhereStep {
key: String,
predicate: Box<dyn crate::traversal::predicate::Predicate>,
}
impl HasWhereStep {
pub fn new(
key: impl Into<String>,
predicate: impl crate::traversal::predicate::Predicate + 'static,
) -> Self {
Self {
key: key.into(),
predicate: Box::new(predicate),
}
}
fn matches(&self, ctx: &ExecutionContext, traverser: &Traverser) -> bool {
match &traverser.value {
Value::Vertex(id) => {
if let Some(vertex) = ctx.storage().get_vertex(*id) {
vertex
.properties
.get(&self.key)
.map(|prop_value| self.predicate.test(prop_value))
.unwrap_or(false)
} else {
false
}
}
Value::Edge(id) => {
if let Some(edge) = ctx.storage().get_edge(*id) {
edge.properties
.get(&self.key)
.map(|prop_value| self.predicate.test(prop_value))
.unwrap_or(false)
} else {
false
}
}
_ => false,
}
}
fn matches_streaming(&self, ctx: &StreamingContext, traverser: &Traverser) -> bool {
match &traverser.value {
Value::Vertex(id) => ctx
.storage()
.get_vertex(*id)
.and_then(|v| v.properties.get(&self.key).cloned())
.map(|prop_value| self.predicate.test(&prop_value))
.unwrap_or(false),
Value::Edge(id) => ctx
.storage()
.get_edge(*id)
.and_then(|e| e.properties.get(&self.key).cloned())
.map(|prop_value| self.predicate.test(&prop_value))
.unwrap_or(false),
_ => false,
}
}
}
impl std::fmt::Debug for HasWhereStep {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("HasWhereStep")
.field("key", &self.key)
.field("predicate", &"<predicate>")
.finish()
}
}
impl_filter_step!(HasWhereStep, "has", category = crate::traversal::explain::StepCategory::Filter, describe = |s: &HasWhereStep| Some(format!("\"{}\"", s.key)), filter_key = |s: &HasWhereStep| Some(s.key.clone()));
#[cfg(feature = "reactive")]
impl crate::traversal::reactive::StepIntrospect for HasWhereStep {
fn property_constraints(&self) -> Option<Vec<String>> {
Some(vec![self.key.clone()])
}
}
#[derive(Clone)]
pub struct IsStep {
predicate: Box<dyn crate::traversal::predicate::Predicate>,
}
impl IsStep {
pub fn new(predicate: impl crate::traversal::predicate::Predicate + 'static) -> Self {
Self {
predicate: Box::new(predicate),
}
}
pub fn eq(value: impl Into<Value>) -> Self {
Self::new(crate::traversal::predicate::p::eq(value))
}
fn matches(&self, _ctx: &ExecutionContext, traverser: &Traverser) -> bool {
self.predicate.test(&traverser.value)
}
fn matches_streaming(&self, _ctx: &StreamingContext, traverser: &Traverser) -> bool {
self.predicate.test(&traverser.value)
}
}
impl std::fmt::Debug for IsStep {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("IsStep")
.field("predicate", &"<predicate>")
.finish()
}
}
impl_filter_step!(IsStep, "is", category = crate::traversal::explain::StepCategory::Filter);
#[derive(Clone, Debug, Copy)]
pub struct SimplePathStep;
impl SimplePathStep {
pub fn new() -> Self {
Self
}
fn matches(&self, _ctx: &ExecutionContext, traverser: &Traverser) -> bool {
use std::collections::HashSet;
let mut seen = HashSet::new();
for element in traverser.path.elements() {
if !seen.insert(&element.value) {
return false; }
}
true }
fn matches_streaming(&self, _ctx: &StreamingContext, traverser: &Traverser) -> bool {
use std::collections::HashSet;
let mut seen = HashSet::new();
for element in traverser.path.elements() {
if !seen.insert(&element.value) {
return false;
}
}
true
}
}
impl Default for SimplePathStep {
fn default() -> Self {
Self::new()
}
}
impl_filter_step!(SimplePathStep, "simplePath", category = crate::traversal::explain::StepCategory::Filter);
#[derive(Clone, Debug, Copy)]
pub struct CyclicPathStep;
impl CyclicPathStep {
pub fn new() -> Self {
Self
}
fn matches(&self, _ctx: &ExecutionContext, traverser: &Traverser) -> bool {
use std::collections::HashSet;
let mut seen = HashSet::new();
for element in traverser.path.elements() {
if !seen.insert(&element.value) {
return true; }
}
false }
fn matches_streaming(&self, _ctx: &StreamingContext, traverser: &Traverser) -> bool {
use std::collections::HashSet;
let mut seen = HashSet::new();
for element in traverser.path.elements() {
if !seen.insert(&element.value) {
return true;
}
}
false
}
}
impl Default for CyclicPathStep {
fn default() -> Self {
Self::new()
}
}
impl_filter_step!(CyclicPathStep, "cyclicPath", category = crate::traversal::explain::StepCategory::Filter);
#[derive(Clone, Debug, Copy)]
pub struct TailStep {
count: usize,
}
impl TailStep {
pub fn new(count: usize) -> Self {
Self { count }
}
pub fn last() -> Self {
Self::new(1)
}
}
impl Default for TailStep {
fn default() -> Self {
Self::last()
}
}
impl Step for TailStep {
type Iter<'a>
= Box<dyn 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> {
if self.count == 0 {
return Box::new(std::iter::empty());
}
let mut ring_buffer: VecDeque<Traverser> = VecDeque::with_capacity(self.count);
for traverser in input {
if ring_buffer.len() == self.count {
ring_buffer.pop_front();
}
ring_buffer.push_back(traverser);
}
Box::new(ring_buffer.into_iter())
}
fn name(&self) -> &'static str {
"tail"
}
fn is_barrier(&self) -> bool {
true
}
fn category(&self) -> crate::traversal::explain::StepCategory {
crate::traversal::explain::StepCategory::Filter
}
fn describe(&self) -> Option<String> {
Some(format!("tail: {}", self.count))
}
fn apply_streaming(
&self,
_ctx: crate::traversal::context::StreamingContext,
input: Traverser,
) -> Box<dyn Iterator<Item = Traverser> + Send + 'static> {
Box::new(std::iter::once(input))
}
}
#[derive(Clone, Debug, Copy)]
pub struct CoinStep {
probability: f64,
}
impl CoinStep {
pub fn new(probability: f64) -> Self {
let probability = probability.clamp(0.0, 1.0);
Self { probability }
}
pub fn always() -> Self {
Self::new(1.0)
}
pub fn never() -> Self {
Self::new(0.0)
}
pub fn probability(&self) -> f64 {
self.probability
}
}
impl Default for CoinStep {
fn default() -> Self {
Self::always()
}
}
impl Step for CoinStep {
type Iter<'a>
= Box<dyn 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> {
use rand::Rng;
let probability = self.probability;
if probability <= 0.0 {
return Box::new(std::iter::empty());
}
if probability >= 1.0 {
return input;
}
Box::new(input.filter(move |_| rand::thread_rng().gen::<f64>() < probability))
}
fn name(&self) -> &'static str {
"coin"
}
fn category(&self) -> crate::traversal::explain::StepCategory {
crate::traversal::explain::StepCategory::Filter
}
fn describe(&self) -> Option<String> {
Some(format!("{}", self.probability))
}
fn apply_streaming(
&self,
_ctx: crate::traversal::context::StreamingContext,
input: Traverser,
) -> Box<dyn Iterator<Item = Traverser> + Send + 'static> {
use rand::Rng;
let probability = self.probability;
if probability <= 0.0 {
return Box::new(std::iter::empty());
}
if probability >= 1.0 {
return Box::new(std::iter::once(input));
}
if rand::thread_rng().gen::<f64>() < probability {
Box::new(std::iter::once(input))
} else {
Box::new(std::iter::empty())
}
}
}
#[derive(Clone, Debug, Copy)]
pub struct SampleStep {
count: usize,
}
impl SampleStep {
pub fn new(count: usize) -> Self {
Self { count }
}
pub fn count(&self) -> usize {
self.count
}
}
impl Default for SampleStep {
fn default() -> Self {
Self::new(1)
}
}
impl Step for SampleStep {
type Iter<'a>
= Box<dyn 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> {
use rand::Rng;
let count = self.count;
if count == 0 {
return Box::new(std::iter::empty());
}
let mut reservoir: Vec<Traverser> = Vec::with_capacity(count);
let mut rng = rand::thread_rng();
for (k, item) in input.enumerate() {
if k < count {
reservoir.push(item);
} else {
let j = rng.gen_range(0..=k);
if j < count {
reservoir[j] = item;
}
}
}
Box::new(reservoir.into_iter())
}
fn name(&self) -> &'static str {
"sample"
}
fn is_barrier(&self) -> bool {
true
}
fn category(&self) -> crate::traversal::explain::StepCategory {
crate::traversal::explain::StepCategory::Filter
}
fn describe(&self) -> Option<String> {
Some(format!("{}", self.count))
}
fn apply_streaming(
&self,
_ctx: crate::traversal::context::StreamingContext,
input: Traverser,
) -> Box<dyn Iterator<Item = Traverser> + Send + 'static> {
Box::new(std::iter::once(input))
}
}
#[derive(Clone, Debug)]
pub struct HasKeyStep {
keys: Vec<String>,
}
impl HasKeyStep {
pub fn new(key: impl Into<String>) -> Self {
Self {
keys: vec![key.into()],
}
}
pub fn any<I, S>(keys: I) -> Self
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
Self {
keys: keys.into_iter().map(Into::into).collect(),
}
}
pub fn keys(&self) -> &[String] {
&self.keys
}
fn matches(&self, _ctx: &ExecutionContext, traverser: &Traverser) -> bool {
match &traverser.value {
Value::Map(map) => {
if let Some(Value::String(key)) = map.get("key") {
self.keys.iter().any(|k| k == key)
} else {
false
}
}
_ => false,
}
}
fn matches_streaming(&self, _ctx: &StreamingContext, traverser: &Traverser) -> bool {
match &traverser.value {
Value::Map(map) => {
if let Some(Value::String(key)) = map.get("key") {
self.keys.iter().any(|k| k == key)
} else {
false
}
}
_ => false,
}
}
}
impl_filter_step!(HasKeyStep, "hasKey", category = crate::traversal::explain::StepCategory::Filter);
#[derive(Clone, Debug)]
pub struct HasPropValueStep {
values: Vec<Value>,
}
impl HasPropValueStep {
pub fn new(value: impl Into<Value>) -> Self {
Self {
values: vec![value.into()],
}
}
pub fn any<I, V>(values: I) -> Self
where
I: IntoIterator<Item = V>,
V: Into<Value>,
{
Self {
values: values.into_iter().map(Into::into).collect(),
}
}
pub fn values(&self) -> &[Value] {
&self.values
}
fn matches(&self, _ctx: &ExecutionContext, traverser: &Traverser) -> bool {
match &traverser.value {
Value::Map(map) => {
if let Some(value) = map.get("value") {
self.values.iter().any(|v| v == value)
} else {
false
}
}
_ => false,
}
}
fn matches_streaming(&self, _ctx: &StreamingContext, traverser: &Traverser) -> bool {
match &traverser.value {
Value::Map(map) => {
if let Some(value) = map.get("value") {
self.values.iter().any(|v| v == value)
} else {
false
}
}
_ => false,
}
}
}
impl_filter_step!(HasPropValueStep, "hasValue", category = crate::traversal::explain::StepCategory::Filter);
#[derive(Clone)]
pub struct WherePStep {
predicate: Box<dyn crate::traversal::predicate::Predicate>,
}
impl WherePStep {
pub fn new(predicate: impl crate::traversal::predicate::Predicate + 'static) -> Self {
Self {
predicate: Box::new(predicate),
}
}
fn matches(&self, _ctx: &ExecutionContext, traverser: &Traverser) -> bool {
self.predicate.test(&traverser.value)
}
fn matches_streaming(&self, _ctx: &StreamingContext, traverser: &Traverser) -> bool {
self.predicate.test(&traverser.value)
}
}
impl std::fmt::Debug for WherePStep {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("WherePStep")
.field("predicate", &"<predicate>")
.finish()
}
}
impl_filter_step!(WherePStep, "where", category = crate::traversal::explain::StepCategory::Filter);
#[cfg(test)]
mod tests {
use super::*;
use crate::storage::Graph;
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
});
let v1 = graph.add_vertex("person", {
let mut props = HashMap::new();
props.insert("name".to_string(), Value::String("Bob".to_string()));
props
});
let v2 = graph.add_vertex("software", {
let mut props = HashMap::new();
props.insert("name".to_string(), Value::String("Graph DB".to_string()));
props
});
let v3 = graph.add_vertex("company", {
let mut props = HashMap::new();
props.insert("name".to_string(), Value::String("TechCorp".to_string()));
props
});
graph.add_edge(v0, v1, "knows", HashMap::new()).unwrap();
graph.add_edge(v1, v2, "uses", HashMap::new()).unwrap();
graph.add_edge(v0, v3, "works_at", HashMap::new()).unwrap();
graph
}
mod has_label_step_tests {
use super::*;
use crate::traversal::step::DynStep;
#[test]
fn single_creates_single_label_step() {
let step = HasLabelStep::single("person");
assert_eq!(step.labels, vec!["person".to_string()]);
}
#[test]
fn new_creates_multi_label_step() {
let step = HasLabelStep::new(vec!["person".to_string(), "company".to_string()]);
assert_eq!(step.labels.len(), 2);
}
#[test]
fn any_creates_multi_label_step() {
let step = HasLabelStep::any(["person", "company", "software"]);
assert_eq!(step.labels.len(), 3);
}
#[test]
fn name_returns_has_label() {
let step = HasLabelStep::single("person");
assert_eq!(step.name(), "hasLabel");
}
#[test]
fn clone_box_works() {
let step = HasLabelStep::single("person");
let cloned = DynStep::clone_box(&step);
assert_eq!(cloned.dyn_name(), "hasLabel");
}
#[test]
fn filters_vertices_by_single_label() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = HasLabelStep::single("person");
let input: Vec<Traverser> = vec![
Traverser::from_vertex(VertexId(0)), Traverser::from_vertex(VertexId(1)), Traverser::from_vertex(VertexId(2)), Traverser::from_vertex(VertexId(3)), ];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 2);
assert_eq!(output[0].as_vertex_id(), Some(VertexId(0)));
assert_eq!(output[1].as_vertex_id(), Some(VertexId(1)));
}
#[test]
fn filters_vertices_by_multiple_labels() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = HasLabelStep::any(["person", "company"]);
let input: Vec<Traverser> = vec![
Traverser::from_vertex(VertexId(0)), Traverser::from_vertex(VertexId(1)), Traverser::from_vertex(VertexId(2)), Traverser::from_vertex(VertexId(3)), ];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 3);
}
#[test]
fn filters_edges_by_single_label() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = HasLabelStep::single("knows");
let input: Vec<Traverser> = vec![
Traverser::from_edge(EdgeId(0)), Traverser::from_edge(EdgeId(1)), Traverser::from_edge(EdgeId(2)), ];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 1);
assert_eq!(output[0].as_edge_id(), Some(EdgeId(0)));
}
#[test]
fn filters_edges_by_multiple_labels() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = HasLabelStep::any(["knows", "uses"]);
let input: Vec<Traverser> = vec![
Traverser::from_edge(EdgeId(0)), Traverser::from_edge(EdgeId(1)), Traverser::from_edge(EdgeId(2)), ];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 2);
}
#[test]
fn filters_out_non_element_values() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = HasLabelStep::single("person");
let input: Vec<Traverser> = vec![
Traverser::from_vertex(VertexId(0)), Traverser::new(Value::Int(42)), Traverser::new(Value::String("hello".to_string())), Traverser::new(Value::Bool(true)), Traverser::new(Value::Null), ];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 1);
assert_eq!(output[0].as_vertex_id(), Some(VertexId(0)));
}
#[test]
fn filters_out_nonexistent_vertices() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = HasLabelStep::single("person");
let input: Vec<Traverser> = vec![
Traverser::from_vertex(VertexId(0)), Traverser::from_vertex(VertexId(999)), ];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 1);
assert_eq!(output[0].as_vertex_id(), Some(VertexId(0)));
}
#[test]
fn filters_out_nonexistent_edges() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = HasLabelStep::single("knows");
let input: Vec<Traverser> = vec![
Traverser::from_edge(EdgeId(0)), Traverser::from_edge(EdgeId(999)), ];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 1);
assert_eq!(output[0].as_edge_id(), Some(EdgeId(0)));
}
#[test]
fn returns_empty_for_nonexistent_label() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = HasLabelStep::single("nonexistent_label");
let input: Vec<Traverser> = vec![
Traverser::from_vertex(VertexId(0)),
Traverser::from_vertex(VertexId(1)),
Traverser::from_vertex(VertexId(2)),
];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert!(output.is_empty());
}
#[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 = HasLabelStep::single("person");
let input: Vec<Traverser> = vec![];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert!(output.is_empty());
}
#[test]
fn preserves_traverser_metadata() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = HasLabelStep::single("person");
let mut traverser = Traverser::from_vertex(VertexId(0));
traverser.extend_path_labeled("start");
traverser.loops = 5;
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!(output[0].path.has_label("start"));
assert_eq!(output[0].loops, 5);
assert_eq!(output[0].bulk, 10);
}
#[test]
fn mixed_vertices_and_edges() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = HasLabelStep::single("person");
let input: Vec<Traverser> = vec![
Traverser::from_vertex(VertexId(0)), Traverser::from_edge(EdgeId(0)), Traverser::from_vertex(VertexId(2)), ];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 1);
assert_eq!(output[0].as_vertex_id(), Some(VertexId(0)));
}
#[test]
fn debug_format() {
let step = HasLabelStep::any(["person", "company"]);
let debug_str = format!("{:?}", step);
assert!(debug_str.contains("HasLabelStep"));
assert!(debug_str.contains("person"));
assert!(debug_str.contains("company"));
}
}
mod has_step_tests {
use super::*;
use crate::traversal::step::DynStep;
fn create_graph_with_properties() -> 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("person", {
let mut props = HashMap::new();
props.insert("name".to_string(), Value::String("Bob".to_string()));
props
});
let v2 = graph.add_vertex("software", {
let mut props = HashMap::new();
props.insert("name".to_string(), Value::String("Graph DB".to_string()));
props.insert("version".to_string(), Value::Float(1.0));
props
});
graph.add_vertex("company", HashMap::new());
graph
.add_edge(v0, v1, "knows", {
let mut props = HashMap::new();
props.insert("since".to_string(), Value::Int(2020));
props
})
.unwrap();
graph.add_edge(v1, v2, "uses", HashMap::new()).unwrap();
graph
}
#[test]
fn new_creates_step_with_key() {
let step = HasStep::new("age");
assert_eq!(step.key, "age");
}
#[test]
fn name_returns_has() {
let step = HasStep::new("age");
assert_eq!(step.name(), "has");
}
#[test]
fn clone_box_works() {
let step = HasStep::new("age");
let cloned = DynStep::clone_box(&step);
assert_eq!(cloned.dyn_name(), "has");
}
#[test]
fn filters_vertices_with_property() {
let graph = create_graph_with_properties();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = HasStep::new("age");
let input: Vec<Traverser> = vec![
Traverser::from_vertex(VertexId(0)), Traverser::from_vertex(VertexId(1)), Traverser::from_vertex(VertexId(2)), Traverser::from_vertex(VertexId(3)), ];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 1);
assert_eq!(output[0].as_vertex_id(), Some(VertexId(0)));
}
#[test]
fn filters_vertices_by_name_property() {
let graph = create_graph_with_properties();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = HasStep::new("name");
let input: Vec<Traverser> = vec![
Traverser::from_vertex(VertexId(0)), Traverser::from_vertex(VertexId(1)), Traverser::from_vertex(VertexId(2)), Traverser::from_vertex(VertexId(3)), ];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 3);
}
#[test]
fn filters_edges_with_property() {
let graph = create_graph_with_properties();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = HasStep::new("since");
let input: Vec<Traverser> = vec![
Traverser::from_edge(EdgeId(0)), Traverser::from_edge(EdgeId(1)), ];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 1);
assert_eq!(output[0].as_edge_id(), Some(EdgeId(0)));
}
#[test]
fn filters_out_non_element_values() {
let graph = create_graph_with_properties();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = HasStep::new("name");
let input: Vec<Traverser> = vec![
Traverser::from_vertex(VertexId(0)), Traverser::new(Value::Int(42)), Traverser::new(Value::String("hello".to_string())), Traverser::new(Value::Null), ];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 1);
assert_eq!(output[0].as_vertex_id(), Some(VertexId(0)));
}
#[test]
fn filters_out_nonexistent_vertices() {
let graph = create_graph_with_properties();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = HasStep::new("name");
let input: Vec<Traverser> = vec![
Traverser::from_vertex(VertexId(0)), Traverser::from_vertex(VertexId(999)), ];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 1);
assert_eq!(output[0].as_vertex_id(), Some(VertexId(0)));
}
#[test]
fn returns_empty_for_nonexistent_property() {
let graph = create_graph_with_properties();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = HasStep::new("nonexistent_property");
let input: Vec<Traverser> = 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!(output.is_empty());
}
#[test]
fn empty_input_returns_empty_output() {
let graph = create_graph_with_properties();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = HasStep::new("name");
let input: Vec<Traverser> = vec![];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert!(output.is_empty());
}
#[test]
fn preserves_traverser_metadata() {
let graph = create_graph_with_properties();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = HasStep::new("name");
let mut traverser = Traverser::from_vertex(VertexId(0));
traverser.extend_path_labeled("start");
traverser.loops = 5;
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!(output[0].path.has_label("start"));
assert_eq!(output[0].loops, 5);
assert_eq!(output[0].bulk, 10);
}
#[test]
fn debug_format() {
let step = HasStep::new("age");
let debug_str = format!("{:?}", step);
assert!(debug_str.contains("HasStep"));
assert!(debug_str.contains("age"));
}
}
mod has_not_step_tests {
use super::*;
use crate::traversal::step::DynStep;
fn create_graph_with_properties() -> 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("person", {
let mut props = HashMap::new();
props.insert("name".to_string(), Value::String("Bob".to_string()));
props
});
let v2 = graph.add_vertex("software", {
let mut props = HashMap::new();
props.insert("name".to_string(), Value::String("Graph DB".to_string()));
props.insert("version".to_string(), Value::Float(1.0));
props
});
graph.add_vertex("company", HashMap::new());
graph
.add_edge(v0, v1, "knows", {
let mut props = HashMap::new();
props.insert("since".to_string(), Value::Int(2020));
props
})
.unwrap();
graph.add_edge(v1, v2, "uses", HashMap::new()).unwrap();
graph
}
#[test]
fn new_creates_step_with_key() {
let step = HasNotStep::new("email");
assert_eq!(step.key, "email");
}
#[test]
fn name_returns_has_not() {
let step = HasNotStep::new("email");
assert_eq!(step.name(), "hasNot");
}
#[test]
fn clone_box_works() {
let step = HasNotStep::new("email");
let cloned = DynStep::clone_box(&step);
assert_eq!(cloned.dyn_name(), "hasNot");
}
#[test]
fn filters_out_vertices_with_property() {
let graph = create_graph_with_properties();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = HasNotStep::new("age");
let input: Vec<Traverser> = vec![
Traverser::from_vertex(VertexId(0)), Traverser::from_vertex(VertexId(1)), Traverser::from_vertex(VertexId(2)), Traverser::from_vertex(VertexId(3)), ];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 3);
assert_eq!(output[0].as_vertex_id(), Some(VertexId(1)));
assert_eq!(output[1].as_vertex_id(), Some(VertexId(2)));
assert_eq!(output[2].as_vertex_id(), Some(VertexId(3)));
}
#[test]
fn keeps_vertices_without_property() {
let graph = create_graph_with_properties();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = HasNotStep::new("version");
let input: Vec<Traverser> = vec![
Traverser::from_vertex(VertexId(0)), Traverser::from_vertex(VertexId(1)), Traverser::from_vertex(VertexId(2)), Traverser::from_vertex(VertexId(3)), ];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 3);
assert_eq!(output[0].as_vertex_id(), Some(VertexId(0)));
assert_eq!(output[1].as_vertex_id(), Some(VertexId(1)));
assert_eq!(output[2].as_vertex_id(), Some(VertexId(3)));
}
#[test]
fn filters_out_edges_with_property() {
let graph = create_graph_with_properties();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = HasNotStep::new("since");
let input: Vec<Traverser> = vec![
Traverser::from_edge(EdgeId(0)), Traverser::from_edge(EdgeId(1)), ];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 1);
assert_eq!(output[0].as_edge_id(), Some(EdgeId(1)));
}
#[test]
fn passes_through_non_element_values() {
let graph = create_graph_with_properties();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = HasNotStep::new("name");
let input: Vec<Traverser> = vec![
Traverser::from_vertex(VertexId(0)), Traverser::new(Value::Int(42)), Traverser::new(Value::String("hello".to_string())), Traverser::new(Value::Bool(true)), Traverser::new(Value::Null), ];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 4);
assert_eq!(output[0].value, Value::Int(42));
assert_eq!(output[1].value, Value::String("hello".to_string()));
assert_eq!(output[2].value, Value::Bool(true));
assert_eq!(output[3].value, Value::Null);
}
#[test]
fn nonexistent_vertices_pass_through() {
let graph = create_graph_with_properties();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = HasNotStep::new("name");
let input: Vec<Traverser> = vec![
Traverser::from_vertex(VertexId(0)), Traverser::from_vertex(VertexId(999)), ];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 1);
assert_eq!(output[0].as_vertex_id(), Some(VertexId(999)));
}
#[test]
fn nonexistent_edges_pass_through() {
let graph = create_graph_with_properties();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = HasNotStep::new("since");
let input: Vec<Traverser> = vec![
Traverser::from_edge(EdgeId(0)), Traverser::from_edge(EdgeId(999)), ];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 1);
assert_eq!(output[0].as_edge_id(), Some(EdgeId(999)));
}
#[test]
fn all_pass_for_nonexistent_property() {
let graph = create_graph_with_properties();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = HasNotStep::new("nonexistent_property");
let input: Vec<Traverser> = vec![
Traverser::from_vertex(VertexId(0)),
Traverser::from_vertex(VertexId(1)),
Traverser::from_vertex(VertexId(2)),
Traverser::from_vertex(VertexId(3)),
];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 4);
}
#[test]
fn empty_input_returns_empty_output() {
let graph = create_graph_with_properties();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = HasNotStep::new("name");
let input: Vec<Traverser> = vec![];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert!(output.is_empty());
}
#[test]
fn preserves_traverser_metadata() {
let graph = create_graph_with_properties();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = HasNotStep::new("name");
let mut traverser = Traverser::from_vertex(VertexId(3));
traverser.extend_path_labeled("start");
traverser.loops = 5;
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!(output[0].path.has_label("start"));
assert_eq!(output[0].loops, 5);
assert_eq!(output[0].bulk, 10);
}
#[test]
fn debug_format() {
let step = HasNotStep::new("email");
let debug_str = format!("{:?}", step);
assert!(debug_str.contains("HasNotStep"));
assert!(debug_str.contains("email"));
}
#[test]
fn mixed_vertices_and_edges() {
let graph = create_graph_with_properties();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = HasNotStep::new("name");
let input: Vec<Traverser> = vec![
Traverser::from_vertex(VertexId(0)), Traverser::from_edge(EdgeId(0)), Traverser::from_vertex(VertexId(3)), Traverser::from_edge(EdgeId(1)), ];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 3);
assert_eq!(output[0].as_edge_id(), Some(EdgeId(0)));
assert_eq!(output[1].as_vertex_id(), Some(VertexId(3)));
assert_eq!(output[2].as_edge_id(), Some(EdgeId(1)));
}
#[test]
fn inverse_of_has_step() {
let graph = create_graph_with_properties();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let has_step = HasStep::new("age");
let has_not_step = HasNotStep::new("age");
let input: Vec<Traverser> = vec![
Traverser::from_vertex(VertexId(0)), Traverser::from_vertex(VertexId(1)), Traverser::from_vertex(VertexId(2)), Traverser::from_vertex(VertexId(3)), ];
let input_clone: Vec<Traverser> = vec![
Traverser::from_vertex(VertexId(0)),
Traverser::from_vertex(VertexId(1)),
Traverser::from_vertex(VertexId(2)),
Traverser::from_vertex(VertexId(3)),
];
let has_output: Vec<Traverser> =
has_step.apply(&ctx, Box::new(input.into_iter())).collect();
let has_not_output: Vec<Traverser> = has_not_step
.apply(&ctx, Box::new(input_clone.into_iter()))
.collect();
assert_eq!(has_output.len(), 1);
assert_eq!(has_output[0].as_vertex_id(), Some(VertexId(0)));
assert_eq!(has_not_output.len(), 3);
assert_eq!(has_not_output[0].as_vertex_id(), Some(VertexId(1)));
assert_eq!(has_not_output[1].as_vertex_id(), Some(VertexId(2)));
assert_eq!(has_not_output[2].as_vertex_id(), Some(VertexId(3)));
assert_eq!(has_output.len() + has_not_output.len(), 4);
}
}
mod has_value_step_tests {
use super::*;
use crate::traversal::step::DynStep;
fn create_graph_with_properties() -> 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("person", {
let mut props = HashMap::new();
props.insert("name".to_string(), Value::String("Bob".to_string()));
props.insert("age".to_string(), Value::Int(25));
props
});
let v2 = graph.add_vertex("person", {
let mut props = HashMap::new();
props.insert("name".to_string(), Value::String("Charlie".to_string()));
props.insert("age".to_string(), Value::Int(30));
props
});
graph.add_vertex("software", {
let mut props = HashMap::new();
props.insert("name".to_string(), Value::String("Graph DB".to_string()));
props.insert("version".to_string(), Value::Float(1.0));
props
});
graph
.add_edge(v0, v1, "knows", {
let mut props = HashMap::new();
props.insert("since".to_string(), Value::Int(2020));
props
})
.unwrap();
graph
.add_edge(v1, v2, "knows", {
let mut props = HashMap::new();
props.insert("since".to_string(), Value::Int(2019));
props
})
.unwrap();
graph
}
#[test]
fn new_creates_step_with_key_and_value() {
let step = HasValueStep::new("age", 30i64);
assert_eq!(step.key, "age");
assert_eq!(step.value, Value::Int(30));
}
#[test]
fn name_returns_has() {
let step = HasValueStep::new("age", 30i64);
assert_eq!(step.name(), "has");
}
#[test]
fn clone_box_works() {
let step = HasValueStep::new("age", 30i64);
let cloned = DynStep::clone_box(&step);
assert_eq!(cloned.dyn_name(), "has");
}
#[test]
fn filters_vertices_by_string_value() {
let graph = create_graph_with_properties();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = HasValueStep::new("name", "Alice");
let input: Vec<Traverser> = vec![
Traverser::from_vertex(VertexId(0)), Traverser::from_vertex(VertexId(1)), Traverser::from_vertex(VertexId(2)), ];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 1);
assert_eq!(output[0].as_vertex_id(), Some(VertexId(0)));
}
#[test]
fn filters_vertices_by_int_value() {
let graph = create_graph_with_properties();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = HasValueStep::new("age", 30i64);
let input: Vec<Traverser> = vec![
Traverser::from_vertex(VertexId(0)), Traverser::from_vertex(VertexId(1)), Traverser::from_vertex(VertexId(2)), ];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 2);
assert_eq!(output[0].as_vertex_id(), Some(VertexId(0)));
assert_eq!(output[1].as_vertex_id(), Some(VertexId(2)));
}
#[test]
fn filters_vertices_by_float_value() {
let graph = create_graph_with_properties();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = HasValueStep::new("version", Value::Float(1.0));
let input: Vec<Traverser> = vec![
Traverser::from_vertex(VertexId(0)), Traverser::from_vertex(VertexId(3)), ];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 1);
assert_eq!(output[0].as_vertex_id(), Some(VertexId(3)));
}
#[test]
fn filters_edges_by_value() {
let graph = create_graph_with_properties();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = HasValueStep::new("since", 2020i64);
let input: Vec<Traverser> = vec![
Traverser::from_edge(EdgeId(0)), Traverser::from_edge(EdgeId(1)), ];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 1);
assert_eq!(output[0].as_edge_id(), Some(EdgeId(0)));
}
#[test]
fn filters_out_vertices_with_different_value() {
let graph = create_graph_with_properties();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = HasValueStep::new("age", 99i64);
let input: Vec<Traverser> = 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!(output.is_empty());
}
#[test]
fn filters_out_vertices_without_property() {
let graph = create_graph_with_properties();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = HasValueStep::new("age", 30i64);
let input: Vec<Traverser> = vec![
Traverser::from_vertex(VertexId(0)), Traverser::from_vertex(VertexId(3)), ];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 1);
assert_eq!(output[0].as_vertex_id(), Some(VertexId(0)));
}
#[test]
fn filters_out_non_element_values() {
let graph = create_graph_with_properties();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = HasValueStep::new("name", "Alice");
let input: Vec<Traverser> = vec![
Traverser::from_vertex(VertexId(0)), Traverser::new(Value::Int(42)), Traverser::new(Value::String("Alice".to_string())), ];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 1);
assert_eq!(output[0].as_vertex_id(), Some(VertexId(0)));
}
#[test]
fn filters_out_nonexistent_vertices() {
let graph = create_graph_with_properties();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = HasValueStep::new("name", "Alice");
let input: Vec<Traverser> = vec![
Traverser::from_vertex(VertexId(0)), Traverser::from_vertex(VertexId(999)), ];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 1);
}
#[test]
fn empty_input_returns_empty_output() {
let graph = create_graph_with_properties();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = HasValueStep::new("name", "Alice");
let input: Vec<Traverser> = vec![];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert!(output.is_empty());
}
#[test]
fn preserves_traverser_metadata() {
let graph = create_graph_with_properties();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = HasValueStep::new("name", "Alice");
let mut traverser = Traverser::from_vertex(VertexId(0));
traverser.extend_path_labeled("start");
traverser.loops = 5;
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!(output[0].path.has_label("start"));
assert_eq!(output[0].loops, 5);
assert_eq!(output[0].bulk, 10);
}
#[test]
fn debug_format() {
let step = HasValueStep::new("age", 30i64);
let debug_str = format!("{:?}", step);
assert!(debug_str.contains("HasValueStep"));
assert!(debug_str.contains("age"));
}
}
mod filter_step_tests {
use super::*;
use crate::traversal::step::DynStep;
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.add_vertex("person", {
let mut props = HashMap::new();
props.insert("name".to_string(), Value::String("Bob".to_string()));
props.insert("age".to_string(), Value::Int(25));
props
});
graph
}
#[test]
fn new_creates_filter_step() {
let step = FilterStep::new(|_ctx, _v| true);
assert_eq!(step.name(), "filter");
}
#[test]
fn name_returns_filter() {
let step = FilterStep::new(|_ctx, _v| true);
assert_eq!(step.name(), "filter");
}
#[test]
fn clone_box_works() {
let step = FilterStep::new(|_ctx, _v| true);
let cloned = DynStep::clone_box(&step);
assert_eq!(cloned.dyn_name(), "filter");
}
#[test]
fn filters_with_always_true_predicate() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = FilterStep::new(|_ctx, _v| true);
let input: Vec<Traverser> = vec![
Traverser::new(Value::Int(1)),
Traverser::new(Value::Int(2)),
Traverser::new(Value::Int(3)),
];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 3);
}
#[test]
fn filters_with_always_false_predicate() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = FilterStep::new(|_ctx, _v| false);
let input: Vec<Traverser> = vec![
Traverser::new(Value::Int(1)),
Traverser::new(Value::Int(2)),
Traverser::new(Value::Int(3)),
];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert!(output.is_empty());
}
#[test]
fn filters_positive_integers() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = FilterStep::new(|_ctx, v| matches!(v, Value::Int(n) if *n > 0));
let input: Vec<Traverser> = vec![
Traverser::new(Value::Int(-2)),
Traverser::new(Value::Int(0)),
Traverser::new(Value::Int(1)),
Traverser::new(Value::Int(3)),
];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 2);
assert_eq!(output[0].value, Value::Int(1));
assert_eq!(output[1].value, Value::Int(3));
}
#[test]
fn filters_by_value_type() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = FilterStep::new(|_ctx, v| v.is_vertex());
let input: Vec<Traverser> = vec![
Traverser::from_vertex(VertexId(0)),
Traverser::new(Value::Int(42)),
Traverser::from_vertex(VertexId(1)),
Traverser::new(Value::String("hello".to_string())),
];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 2);
assert!(output[0].is_vertex());
assert!(output[1].is_vertex());
}
#[test]
fn can_access_execution_context() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = FilterStep::new(|ctx, v| {
if let Some(id) = v.as_vertex_id() {
ctx.storage().get_vertex(id).is_some()
} else {
false
}
});
let input: Vec<Traverser> = vec![
Traverser::from_vertex(VertexId(0)), Traverser::from_vertex(VertexId(999)), 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].as_vertex_id(), Some(VertexId(0)));
assert_eq!(output[1].as_vertex_id(), Some(VertexId(1)));
}
#[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 = FilterStep::new(|_ctx, _v| true);
let input: Vec<Traverser> = vec![];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert!(output.is_empty());
}
#[test]
fn preserves_traverser_metadata() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = FilterStep::new(|_ctx, _v| true);
let mut traverser = Traverser::new(Value::Int(42));
traverser.extend_path_labeled("start");
traverser.loops = 5;
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!(output[0].path.has_label("start"));
assert_eq!(output[0].loops, 5);
assert_eq!(output[0].bulk, 10);
}
#[test]
fn debug_format() {
let step = FilterStep::new(|_ctx, _v| true);
let debug_str = format!("{:?}", step);
assert!(debug_str.contains("FilterStep"));
assert!(debug_str.contains("<closure>"));
}
#[test]
fn filter_with_string_matching() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step =
FilterStep::new(|_ctx, v| matches!(v, Value::String(s) if s.starts_with("A")));
let input: Vec<Traverser> = vec![
Traverser::new(Value::String("Alice".to_string())),
Traverser::new(Value::String("Bob".to_string())),
Traverser::new(Value::String("Anna".to_string())),
];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 2);
}
#[test]
fn filter_step_is_cloneable() {
let step1 = FilterStep::new(|_ctx, v| matches!(v, Value::Int(n) if *n > 0));
let step2 = step1.clone();
assert_eq!(step1.name(), step2.name());
}
}
mod dedup_step_tests {
use super::*;
use crate::traversal::step::DynStep;
#[test]
fn new_creates_dedup_step() {
let step = DedupStep::new();
assert_eq!(step.name(), "dedup");
}
#[test]
fn default_creates_dedup_step() {
let step = DedupStep::new();
assert_eq!(step.name(), "dedup");
}
#[test]
fn name_returns_dedup() {
let step = DedupStep::new();
assert_eq!(step.name(), "dedup");
}
#[test]
fn clone_box_works() {
let step = DedupStep::new();
let cloned = DynStep::clone_box(&step);
assert_eq!(cloned.dyn_name(), "dedup");
}
#[test]
fn removes_duplicate_integers() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = DedupStep::new();
let input: Vec<Traverser> = vec![
Traverser::new(Value::Int(1)),
Traverser::new(Value::Int(2)),
Traverser::new(Value::Int(1)),
Traverser::new(Value::Int(3)),
Traverser::new(Value::Int(2)),
Traverser::new(Value::Int(1)),
];
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(1));
assert_eq!(output[1].value, Value::Int(2));
assert_eq!(output[2].value, Value::Int(3));
}
#[test]
fn removes_duplicate_strings() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = DedupStep::new();
let input: Vec<Traverser> = vec![
Traverser::new(Value::String("alice".to_string())),
Traverser::new(Value::String("bob".to_string())),
Traverser::new(Value::String("alice".to_string())),
Traverser::new(Value::String("charlie".to_string())),
];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 3);
assert_eq!(output[0].value, Value::String("alice".to_string()));
assert_eq!(output[1].value, Value::String("bob".to_string()));
assert_eq!(output[2].value, Value::String("charlie".to_string()));
}
#[test]
fn removes_duplicate_vertices() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = DedupStep::new();
let input: Vec<Traverser> = vec![
Traverser::from_vertex(VertexId(0)),
Traverser::from_vertex(VertexId(1)),
Traverser::from_vertex(VertexId(0)),
Traverser::from_vertex(VertexId(2)),
Traverser::from_vertex(VertexId(1)),
];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 3);
assert_eq!(output[0].as_vertex_id(), Some(VertexId(0)));
assert_eq!(output[1].as_vertex_id(), Some(VertexId(1)));
assert_eq!(output[2].as_vertex_id(), Some(VertexId(2)));
}
#[test]
fn removes_duplicate_edges() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = DedupStep::new();
let input: Vec<Traverser> = vec![
Traverser::from_edge(EdgeId(0)),
Traverser::from_edge(EdgeId(1)),
Traverser::from_edge(EdgeId(0)),
Traverser::from_edge(EdgeId(2)),
];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 3);
assert_eq!(output[0].as_edge_id(), Some(EdgeId(0)));
assert_eq!(output[1].as_edge_id(), Some(EdgeId(1)));
assert_eq!(output[2].as_edge_id(), Some(EdgeId(2)));
}
#[test]
fn preserves_first_occurrence_order() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = DedupStep::new();
let input: Vec<Traverser> = vec![
Traverser::new(Value::Int(3)),
Traverser::new(Value::Int(1)),
Traverser::new(Value::Int(2)),
Traverser::new(Value::Int(1)),
Traverser::new(Value::Int(3)),
Traverser::new(Value::Int(2)),
];
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(3));
assert_eq!(output[1].value, Value::Int(1));
assert_eq!(output[2].value, Value::Int(2));
}
#[test]
fn handles_mixed_types() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = DedupStep::new();
let input: Vec<Traverser> = vec![
Traverser::new(Value::Int(1)),
Traverser::new(Value::String("1".to_string())),
Traverser::new(Value::Bool(true)),
Traverser::new(Value::Int(1)), ];
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(1));
assert_eq!(output[1].value, Value::String("1".to_string()));
assert_eq!(output[2].value, Value::Bool(true));
}
#[test]
fn handles_floats() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = DedupStep::new();
let input: Vec<Traverser> = vec![
Traverser::new(Value::Float(1.0)),
Traverser::new(Value::Float(2.0)),
Traverser::new(Value::Float(1.0)),
Traverser::new(Value::Float(3.0)),
];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 3);
}
#[test]
fn handles_null_values() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = DedupStep::new();
let input: Vec<Traverser> = vec![
Traverser::new(Value::Null),
Traverser::new(Value::Int(1)),
Traverser::new(Value::Null),
Traverser::new(Value::Int(2)),
];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 3);
assert_eq!(output[0].value, Value::Null);
assert_eq!(output[1].value, Value::Int(1));
assert_eq!(output[2].value, Value::Int(2));
}
#[test]
fn handles_boolean_values() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = DedupStep::new();
let input: Vec<Traverser> = vec![
Traverser::new(Value::Bool(true)),
Traverser::new(Value::Bool(false)),
Traverser::new(Value::Bool(true)),
Traverser::new(Value::Bool(false)),
];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 2);
assert_eq!(output[0].value, Value::Bool(true));
assert_eq!(output[1].value, Value::Bool(false));
}
#[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 = DedupStep::new();
let input: Vec<Traverser> = vec![];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert!(output.is_empty());
}
#[test]
fn single_element_passes_through() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = DedupStep::new();
let input: Vec<Traverser> = 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::Int(42));
}
#[test]
fn all_unique_values_pass_through() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = DedupStep::new();
let input: Vec<Traverser> = vec![
Traverser::new(Value::Int(1)),
Traverser::new(Value::Int(2)),
Traverser::new(Value::Int(3)),
Traverser::new(Value::Int(4)),
];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 4);
}
#[test]
fn all_same_values_reduced_to_one() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = DedupStep::new();
let input: Vec<Traverser> = vec![
Traverser::new(Value::Int(42)),
Traverser::new(Value::Int(42)),
Traverser::new(Value::Int(42)),
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::Int(42));
}
#[test]
fn preserves_traverser_metadata() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = DedupStep::new();
let mut traverser = Traverser::new(Value::Int(42));
traverser.extend_path_labeled("start");
traverser.loops = 5;
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!(output[0].path.has_label("start"));
assert_eq!(output[0].loops, 5);
assert_eq!(output[0].bulk, 10);
}
#[test]
fn preserves_metadata_of_first_occurrence() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = DedupStep::new();
let mut t1 = Traverser::new(Value::Int(42));
t1.extend_path_labeled("first");
t1.loops = 1;
let mut t2 = Traverser::new(Value::Int(42));
t2.extend_path_labeled("second");
t2.loops = 2;
let input = vec![t1, t2];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 1);
assert!(output[0].path.has_label("first"));
assert!(!output[0].path.has_label("second"));
assert_eq!(output[0].loops, 1);
}
#[test]
fn dedup_step_is_clone() {
let step1 = DedupStep::new();
let step2 = step1.clone();
let _step3 = step1.clone();
assert_eq!(step2.name(), "dedup");
}
#[test]
fn debug_format() {
let step = DedupStep::new();
let debug_str = format!("{:?}", step);
assert!(debug_str.contains("DedupStep"));
}
#[test]
fn handles_list_values() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = DedupStep::new();
let input: Vec<Traverser> = vec![
Traverser::new(Value::List(vec![Value::Int(1), Value::Int(2)])),
Traverser::new(Value::List(vec![Value::Int(3)])),
Traverser::new(Value::List(vec![Value::Int(1), Value::Int(2)])), ];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 2);
}
#[test]
fn handles_map_values() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = DedupStep::new();
let mut map1 = std::collections::HashMap::new();
map1.insert("a".to_string(), Value::Int(1));
let mut map2 = std::collections::HashMap::new();
map2.insert("b".to_string(), Value::Int(2));
let mut map3 = std::collections::HashMap::new();
map3.insert("a".to_string(), Value::Int(1));
let input: Vec<Traverser> = vec![
Traverser::new(Value::Map(map1.into_iter().collect())),
Traverser::new(Value::Map(map2.into_iter().collect())),
Traverser::new(Value::Map(map3.into_iter().collect())),
];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 2);
}
}
mod dedup_by_key_step_tests {
use super::*;
use crate::traversal::step::DynStep;
fn create_test_graph_with_ages() -> 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.add_vertex("person", {
let mut props = HashMap::new();
props.insert("name".to_string(), Value::String("Bob".to_string()));
props.insert("age".to_string(), Value::Int(25));
props
});
graph.add_vertex("person", {
let mut props = HashMap::new();
props.insert("name".to_string(), Value::String("Charlie".to_string()));
props.insert("age".to_string(), Value::Int(30)); props
});
graph.add_vertex("person", {
let mut props = HashMap::new();
props.insert("name".to_string(), Value::String("Diana".to_string()));
props
});
graph
}
#[test]
fn new_creates_dedup_by_key_step() {
let step = DedupByKeyStep::new("age");
assert_eq!(step.key, "age");
}
#[test]
fn name_returns_dedup() {
let step = DedupByKeyStep::new("age");
assert_eq!(step.name(), "dedup");
}
#[test]
fn clone_box_works() {
let step = DedupByKeyStep::new("age");
let cloned = DynStep::clone_box(&step);
assert_eq!(cloned.dyn_name(), "dedup");
}
#[test]
fn dedup_by_property_keeps_first_occurrence() {
let graph = create_test_graph_with_ages();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = DedupByKeyStep::new("age");
let input: Vec<Traverser> = vec![
Traverser::from_vertex(VertexId(0)), Traverser::from_vertex(VertexId(1)), Traverser::from_vertex(VertexId(2)), ];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 2);
assert_eq!(output[0].as_vertex_id(), Some(VertexId(0))); assert_eq!(output[1].as_vertex_id(), Some(VertexId(1))); }
#[test]
fn missing_property_treated_as_null() {
let graph = create_test_graph_with_ages();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = DedupByKeyStep::new("age");
let input: Vec<Traverser> = vec![
Traverser::from_vertex(VertexId(3)), 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(), 3);
}
#[test]
fn multiple_elements_without_property_deduplicated() {
let graph = create_test_graph_with_ages();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = DedupByKeyStep::new("nonexistent");
let input: Vec<Traverser> = vec![
Traverser::from_vertex(VertexId(0)),
Traverser::from_vertex(VertexId(1)),
Traverser::from_vertex(VertexId(2)),
];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 1);
assert_eq!(output[0].as_vertex_id(), Some(VertexId(0)));
}
#[test]
fn works_with_edges() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = DedupByKeyStep::new("weight");
let input: Vec<Traverser> = vec![
Traverser::new(Value::Edge(EdgeId(0))),
Traverser::new(Value::Edge(EdgeId(1))),
Traverser::new(Value::Edge(EdgeId(2))),
];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 1);
}
#[test]
fn non_element_values_use_null_key() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = DedupByKeyStep::new("age");
let input: Vec<Traverser> = vec![
Traverser::new(Value::Int(1)),
Traverser::new(Value::Int(2)),
Traverser::new(Value::Int(3)),
];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 1);
assert_eq!(output[0].value, Value::Int(1));
}
#[test]
fn empty_input_returns_empty() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = DedupByKeyStep::new("age");
let input: Vec<Traverser> = vec![];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert!(output.is_empty());
}
#[test]
fn preserves_traverser_metadata() {
let graph = create_test_graph_with_ages();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = DedupByKeyStep::new("age");
let mut traverser = Traverser::from_vertex(VertexId(0));
traverser.extend_path_labeled("start");
traverser.loops = 5;
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!(output[0].path.has_label("start"));
assert_eq!(output[0].loops, 5);
assert_eq!(output[0].bulk, 10);
}
#[test]
fn debug_format() {
let step = DedupByKeyStep::new("age");
let debug_str = format!("{:?}", step);
assert!(debug_str.contains("DedupByKeyStep"));
assert!(debug_str.contains("age"));
}
}
mod dedup_by_label_step_tests {
use super::*;
use crate::traversal::step::DynStep;
#[test]
fn new_creates_dedup_by_label_step() {
let step = DedupByLabelStep::new();
assert_eq!(step.name(), "dedup");
}
#[test]
fn default_creates_step() {
let step = DedupByLabelStep::new();
assert_eq!(step.name(), "dedup");
}
#[test]
fn clone_box_works() {
let step = DedupByLabelStep::new();
let cloned = DynStep::clone_box(&step);
assert_eq!(cloned.dyn_name(), "dedup");
}
#[test]
fn dedup_by_label_keeps_first_per_label() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = DedupByLabelStep::new();
let input: Vec<Traverser> = vec![
Traverser::from_vertex(VertexId(0)), Traverser::from_vertex(VertexId(1)), Traverser::from_vertex(VertexId(2)), Traverser::from_vertex(VertexId(3)), ];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 3);
assert_eq!(output[0].as_vertex_id(), Some(VertexId(0)));
assert_eq!(output[1].as_vertex_id(), Some(VertexId(2)));
assert_eq!(output[2].as_vertex_id(), Some(VertexId(3)));
}
#[test]
fn works_with_edges() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = DedupByLabelStep::new();
let input: Vec<Traverser> = vec![
Traverser::new(Value::Edge(EdgeId(0))), Traverser::new(Value::Edge(EdgeId(1))), Traverser::new(Value::Edge(EdgeId(2))), ];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 3);
}
#[test]
fn non_element_values_use_empty_label() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = DedupByLabelStep::new();
let input: Vec<Traverser> = vec![
Traverser::new(Value::Int(1)),
Traverser::new(Value::Int(2)),
Traverser::new(Value::String("hello".to_string())),
];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 1);
assert_eq!(output[0].value, Value::Int(1));
}
#[test]
fn empty_input_returns_empty() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = DedupByLabelStep::new();
let input: Vec<Traverser> = vec![];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert!(output.is_empty());
}
#[test]
fn preserves_traverser_metadata() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = DedupByLabelStep::new();
let mut traverser = Traverser::from_vertex(VertexId(0));
traverser.extend_path_labeled("start");
traverser.loops = 5;
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!(output[0].path.has_label("start"));
assert_eq!(output[0].loops, 5);
assert_eq!(output[0].bulk, 10);
}
#[test]
fn dedup_by_label_step_is_clone() {
let step1 = DedupByLabelStep::new();
let step2 = step1.clone();
let _step3 = step1.clone();
assert_eq!(step2.name(), "dedup");
}
#[test]
fn debug_format() {
let step = DedupByLabelStep::new();
let debug_str = format!("{:?}", step);
assert!(debug_str.contains("DedupByLabelStep"));
}
}
mod dedup_by_traversal_step_tests {
use super::*;
use crate::traversal::step::DynStep;
use crate::traversal::Traversal;
#[test]
fn name_returns_dedup() {
let sub = Traversal::<Value, Value>::new();
let step = DedupByTraversalStep::new(sub);
assert_eq!(step.name(), "dedup");
}
#[test]
fn clone_box_works() {
let sub = Traversal::<Value, Value>::new();
let step = DedupByTraversalStep::new(sub);
let cloned = DynStep::clone_box(&step);
assert_eq!(cloned.dyn_name(), "dedup");
}
#[test]
fn dedup_by_traversal_uses_first_result() {
use crate::traversal::transform::ValuesStep;
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let sub = Traversal::<Value, Value>::new().add_step(ValuesStep::new("name"));
let step = DedupByTraversalStep::new(sub);
let input: Vec<Traverser> = vec![
Traverser::from_vertex(VertexId(0)), Traverser::from_vertex(VertexId(1)), Traverser::from_vertex(VertexId(2)), Traverser::from_vertex(VertexId(3)), ];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 4);
}
#[test]
fn dedup_by_traversal_with_no_results_uses_null() {
use crate::traversal::transform::ValuesStep;
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let sub = Traversal::<Value, Value>::new().add_step(ValuesStep::new("nonexistent"));
let step = DedupByTraversalStep::new(sub);
let input: Vec<Traverser> = vec![
Traverser::from_vertex(VertexId(0)),
Traverser::from_vertex(VertexId(1)),
Traverser::from_vertex(VertexId(2)),
];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 1);
assert_eq!(output[0].as_vertex_id(), Some(VertexId(0)));
}
#[test]
fn dedup_by_label_traversal() {
use crate::traversal::transform::LabelStep;
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let sub = Traversal::<Value, Value>::new().add_step(LabelStep::new());
let step = DedupByTraversalStep::new(sub);
let input: Vec<Traverser> = vec![
Traverser::from_vertex(VertexId(0)), Traverser::from_vertex(VertexId(1)), Traverser::from_vertex(VertexId(2)), Traverser::from_vertex(VertexId(3)), ];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 3);
}
#[test]
fn empty_input_returns_empty() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let sub = Traversal::<Value, Value>::new();
let step = DedupByTraversalStep::new(sub);
let input: Vec<Traverser> = vec![];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert!(output.is_empty());
}
#[test]
fn preserves_traverser_metadata() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let sub = Traversal::<Value, Value>::new();
let step = DedupByTraversalStep::new(sub);
let mut traverser = Traverser::from_vertex(VertexId(0));
traverser.extend_path_labeled("start");
traverser.loops = 5;
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!(output[0].path.has_label("start"));
assert_eq!(output[0].loops, 5);
assert_eq!(output[0].bulk, 10);
}
#[test]
fn debug_format() {
let sub = Traversal::<Value, Value>::new();
let step = DedupByTraversalStep::new(sub);
let debug_str = format!("{:?}", step);
assert!(debug_str.contains("DedupByTraversalStep"));
assert!(debug_str.contains("<traversal>"));
}
}
mod limit_step_tests {
use super::*;
use crate::traversal::step::DynStep;
#[test]
fn new_creates_limit_step() {
let step = LimitStep::new(5);
assert_eq!(step.limit, 5);
}
#[test]
fn name_returns_limit() {
let step = LimitStep::new(5);
assert_eq!(step.name(), "limit");
}
#[test]
fn clone_box_works() {
let step = LimitStep::new(5);
let cloned = DynStep::clone_box(&step);
assert_eq!(cloned.dyn_name(), "limit");
}
#[test]
fn limits_traversers() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = LimitStep::new(3);
let input: Vec<Traverser> = vec![
Traverser::new(Value::Int(1)),
Traverser::new(Value::Int(2)),
Traverser::new(Value::Int(3)),
Traverser::new(Value::Int(4)),
Traverser::new(Value::Int(5)),
];
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(1));
assert_eq!(output[1].value, Value::Int(2));
assert_eq!(output[2].value, Value::Int(3));
}
#[test]
fn limit_zero_returns_empty() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = LimitStep::new(0);
let input: Vec<Traverser> =
vec![Traverser::new(Value::Int(1)), Traverser::new(Value::Int(2))];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert!(output.is_empty());
}
#[test]
fn limit_greater_than_input_returns_all() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = LimitStep::new(100);
let input: Vec<Traverser> = vec![
Traverser::new(Value::Int(1)),
Traverser::new(Value::Int(2)),
Traverser::new(Value::Int(3)),
];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 3);
}
#[test]
fn limit_one_returns_first() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = LimitStep::new(1);
let input: Vec<Traverser> = vec![
Traverser::new(Value::Int(10)),
Traverser::new(Value::Int(20)),
Traverser::new(Value::Int(30)),
];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 1);
assert_eq!(output[0].value, Value::Int(10));
}
#[test]
fn empty_input_returns_empty() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = LimitStep::new(5);
let input: Vec<Traverser> = vec![];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert!(output.is_empty());
}
#[test]
fn preserves_traverser_metadata() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = LimitStep::new(1);
let mut traverser = Traverser::new(Value::Int(42));
traverser.extend_path_labeled("start");
traverser.loops = 5;
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!(output[0].path.has_label("start"));
assert_eq!(output[0].loops, 5);
assert_eq!(output[0].bulk, 10);
}
#[test]
fn limit_step_is_clone() {
let step1 = LimitStep::new(5);
let step2 = step1.clone();
let _step3 = step1.clone();
assert_eq!(step2.limit, 5);
}
#[test]
fn debug_format() {
let step = LimitStep::new(5);
let debug_str = format!("{:?}", step);
assert!(debug_str.contains("LimitStep"));
assert!(debug_str.contains("5"));
}
#[test]
fn works_with_vertices() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = LimitStep::new(2);
let input: Vec<Traverser> = vec![
Traverser::from_vertex(VertexId(0)),
Traverser::from_vertex(VertexId(1)),
Traverser::from_vertex(VertexId(2)),
Traverser::from_vertex(VertexId(3)),
];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 2);
assert_eq!(output[0].as_vertex_id(), Some(VertexId(0)));
assert_eq!(output[1].as_vertex_id(), Some(VertexId(1)));
}
#[test]
fn streaming_limit_matches_eager() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let g = snapshot.gremlin();
let eager: Vec<Value> = g.v().limit(2).to_list();
let streaming: Vec<Value> = g.v().limit(2).iter().collect();
assert_eq!(eager.len(), streaming.len());
assert_eq!(eager.len(), 2);
}
#[test]
fn streaming_limit_respects_count() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let g = snapshot.gremlin();
let result: Vec<Value> = g.v().limit(1).iter().collect();
assert_eq!(result.len(), 1);
let result2: Vec<Value> = g.v().limit(100).iter().collect();
assert_eq!(result2.len(), 4);
}
#[test]
fn streaming_limit_zero_returns_empty() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let g = snapshot.gremlin();
let result: Vec<Value> = g.v().limit(0).iter().collect();
assert!(result.is_empty());
}
}
mod skip_step_tests {
use super::*;
use crate::traversal::step::DynStep;
#[test]
fn new_creates_skip_step() {
let step = SkipStep::new(3);
assert_eq!(step.count, 3);
}
#[test]
fn name_returns_skip() {
let step = SkipStep::new(3);
assert_eq!(step.name(), "skip");
}
#[test]
fn clone_box_works() {
let step = SkipStep::new(3);
let cloned = DynStep::clone_box(&step);
assert_eq!(cloned.dyn_name(), "skip");
}
#[test]
fn skips_traversers() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = SkipStep::new(2);
let input: Vec<Traverser> = vec![
Traverser::new(Value::Int(1)),
Traverser::new(Value::Int(2)),
Traverser::new(Value::Int(3)),
Traverser::new(Value::Int(4)),
Traverser::new(Value::Int(5)),
];
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(3));
assert_eq!(output[1].value, Value::Int(4));
assert_eq!(output[2].value, Value::Int(5));
}
#[test]
fn skip_zero_returns_all() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = SkipStep::new(0);
let input: Vec<Traverser> = vec![
Traverser::new(Value::Int(1)),
Traverser::new(Value::Int(2)),
Traverser::new(Value::Int(3)),
];
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(1));
}
#[test]
fn skip_greater_than_input_returns_empty() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = SkipStep::new(100);
let input: Vec<Traverser> = vec![
Traverser::new(Value::Int(1)),
Traverser::new(Value::Int(2)),
Traverser::new(Value::Int(3)),
];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert!(output.is_empty());
}
#[test]
fn skip_equal_to_input_returns_empty() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = SkipStep::new(3);
let input: Vec<Traverser> = vec![
Traverser::new(Value::Int(1)),
Traverser::new(Value::Int(2)),
Traverser::new(Value::Int(3)),
];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert!(output.is_empty());
}
#[test]
fn skip_one_less_than_input_returns_last() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = SkipStep::new(4);
let input: Vec<Traverser> = vec![
Traverser::new(Value::Int(1)),
Traverser::new(Value::Int(2)),
Traverser::new(Value::Int(3)),
Traverser::new(Value::Int(4)),
Traverser::new(Value::Int(5)),
];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 1);
assert_eq!(output[0].value, Value::Int(5));
}
#[test]
fn empty_input_returns_empty() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = SkipStep::new(5);
let input: Vec<Traverser> = vec![];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert!(output.is_empty());
}
#[test]
fn preserves_traverser_metadata() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = SkipStep::new(1);
let t1 = Traverser::new(Value::Int(1));
let mut t2 = Traverser::new(Value::Int(2));
t2.extend_path_labeled("kept");
t2.loops = 5;
t2.bulk = 10;
let input = vec![t1, t2];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 1);
assert!(output[0].path.has_label("kept"));
assert_eq!(output[0].loops, 5);
assert_eq!(output[0].bulk, 10);
}
#[test]
fn skip_step_is_clone() {
let step1 = SkipStep::new(3);
let step2 = step1.clone();
let _step3 = step1.clone();
assert_eq!(step2.count, 3);
}
#[test]
fn debug_format() {
let step = SkipStep::new(3);
let debug_str = format!("{:?}", step);
assert!(debug_str.contains("SkipStep"));
assert!(debug_str.contains("3"));
}
#[test]
fn works_with_vertices() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = SkipStep::new(2);
let input: Vec<Traverser> = vec![
Traverser::from_vertex(VertexId(0)),
Traverser::from_vertex(VertexId(1)),
Traverser::from_vertex(VertexId(2)),
Traverser::from_vertex(VertexId(3)),
];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 2);
assert_eq!(output[0].as_vertex_id(), Some(VertexId(2)));
assert_eq!(output[1].as_vertex_id(), Some(VertexId(3)));
}
#[test]
fn streaming_skip_matches_eager() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let g = snapshot.gremlin();
let eager: Vec<Value> = g.v().skip(2).to_list();
let streaming: Vec<Value> = g.v().skip(2).iter().collect();
assert_eq!(eager.len(), streaming.len());
assert_eq!(eager.len(), 2); }
#[test]
fn streaming_skip_respects_count() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let g = snapshot.gremlin();
let result: Vec<Value> = g.v().skip(1).iter().collect();
assert_eq!(result.len(), 3);
let result2: Vec<Value> = g.v().skip(100).iter().collect();
assert!(result2.is_empty());
}
#[test]
fn streaming_skip_zero_returns_all() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let g = snapshot.gremlin();
let result: Vec<Value> = g.v().skip(0).iter().collect();
assert_eq!(result.len(), 4);
}
}
mod tail_step_tests {
use super::*;
use crate::traversal::step::DynStep;
#[test]
fn new_creates_tail_step() {
let step = TailStep::new(3);
assert_eq!(step.count, 3);
}
#[test]
fn last_creates_tail_step_with_count_one() {
let step = TailStep::last();
assert_eq!(step.count, 1);
}
#[test]
fn default_creates_last() {
let step = TailStep::default();
assert_eq!(step.count, 1);
}
#[test]
fn name_returns_tail() {
let step = TailStep::new(3);
assert_eq!(step.name(), "tail");
}
#[test]
fn clone_box_works() {
let step = TailStep::new(3);
let cloned = DynStep::clone_box(&step);
assert_eq!(cloned.dyn_name(), "tail");
}
#[test]
fn tail_returns_last_n_elements() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = TailStep::new(3);
let input: Vec<Traverser> = vec![
Traverser::new(Value::Int(1)),
Traverser::new(Value::Int(2)),
Traverser::new(Value::Int(3)),
Traverser::new(Value::Int(4)),
Traverser::new(Value::Int(5)),
];
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(3));
assert_eq!(output[1].value, Value::Int(4));
assert_eq!(output[2].value, Value::Int(5));
}
#[test]
fn tail_last_returns_single_element() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = TailStep::last();
let input: Vec<Traverser> = vec![
Traverser::new(Value::Int(1)),
Traverser::new(Value::Int(2)),
Traverser::new(Value::Int(3)),
Traverser::new(Value::Int(4)),
Traverser::new(Value::Int(5)),
];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 1);
assert_eq!(output[0].value, Value::Int(5));
}
#[test]
fn tail_zero_returns_empty() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = TailStep::new(0);
let input: Vec<Traverser> = vec![
Traverser::new(Value::Int(1)),
Traverser::new(Value::Int(2)),
Traverser::new(Value::Int(3)),
];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert!(output.is_empty());
}
#[test]
fn tail_greater_than_input_returns_all() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = TailStep::new(100);
let input: Vec<Traverser> = vec![
Traverser::new(Value::Int(1)),
Traverser::new(Value::Int(2)),
Traverser::new(Value::Int(3)),
];
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(1));
assert_eq!(output[1].value, Value::Int(2));
assert_eq!(output[2].value, Value::Int(3));
}
#[test]
fn tail_equal_to_input_returns_all() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = TailStep::new(3);
let input: Vec<Traverser> = vec![
Traverser::new(Value::Int(1)),
Traverser::new(Value::Int(2)),
Traverser::new(Value::Int(3)),
];
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(1));
}
#[test]
fn empty_input_returns_empty() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = TailStep::new(5);
let input: Vec<Traverser> = vec![];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert!(output.is_empty());
}
#[test]
fn preserves_traverser_metadata() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = TailStep::new(1);
let t1 = Traverser::new(Value::Int(1));
let mut t2 = Traverser::new(Value::Int(2));
t2.extend_path_labeled("kept");
t2.loops = 5;
t2.bulk = 10;
let input = vec![t1, t2];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 1);
assert!(output[0].path.has_label("kept"));
assert_eq!(output[0].loops, 5);
assert_eq!(output[0].bulk, 10);
}
#[test]
fn tail_step_is_copy() {
let step1 = TailStep::new(3);
let step2 = step1; let _step3 = step1;
assert_eq!(step2.count, 3);
}
#[test]
fn debug_format() {
let step = TailStep::new(3);
let debug_str = format!("{:?}", step);
assert!(debug_str.contains("TailStep"));
assert!(debug_str.contains("3"));
}
#[test]
fn works_with_vertices() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = TailStep::new(2);
let input: Vec<Traverser> = vec![
Traverser::from_vertex(VertexId(0)),
Traverser::from_vertex(VertexId(1)),
Traverser::from_vertex(VertexId(2)),
Traverser::from_vertex(VertexId(3)),
];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 2);
assert_eq!(output[0].as_vertex_id(), Some(VertexId(2)));
assert_eq!(output[1].as_vertex_id(), Some(VertexId(3)));
}
#[test]
fn works_with_edges() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = TailStep::new(2);
let input: Vec<Traverser> = vec![
Traverser::new(Value::Edge(EdgeId(0))),
Traverser::new(Value::Edge(EdgeId(1))),
Traverser::new(Value::Edge(EdgeId(2))),
];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 2);
assert_eq!(output[0].value, Value::Edge(EdgeId(1)));
assert_eq!(output[1].value, Value::Edge(EdgeId(2)));
}
#[test]
fn preserves_order() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = TailStep::new(4);
let input: Vec<Traverser> = vec![
Traverser::new(Value::Int(10)),
Traverser::new(Value::Int(20)),
Traverser::new(Value::Int(30)),
Traverser::new(Value::Int(40)),
Traverser::new(Value::Int(50)),
];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 4);
assert_eq!(output[0].value, Value::Int(20));
assert_eq!(output[1].value, Value::Int(30));
assert_eq!(output[2].value, Value::Int(40));
assert_eq!(output[3].value, Value::Int(50));
}
}
mod coin_step_tests {
use super::*;
use crate::traversal::step::DynStep;
#[test]
fn new_creates_coin_step() {
let step = CoinStep::new(0.5);
assert!((step.probability() - 0.5).abs() < f64::EPSILON);
}
#[test]
fn new_clamps_probability_above_one() {
let step = CoinStep::new(1.5);
assert!((step.probability() - 1.0).abs() < f64::EPSILON);
}
#[test]
fn new_clamps_probability_below_zero() {
let step = CoinStep::new(-0.5);
assert!(step.probability().abs() < f64::EPSILON);
}
#[test]
fn always_returns_probability_one() {
let step = CoinStep::always();
assert!((step.probability() - 1.0).abs() < f64::EPSILON);
}
#[test]
fn never_returns_probability_zero() {
let step = CoinStep::never();
assert!(step.probability().abs() < f64::EPSILON);
}
#[test]
fn default_is_always() {
let step = CoinStep::default();
assert!((step.probability() - 1.0).abs() < f64::EPSILON);
}
#[test]
fn name_returns_coin() {
let step = CoinStep::new(0.5);
assert_eq!(step.name(), "coin");
}
#[test]
fn clone_box_works() {
let step = CoinStep::new(0.5);
let cloned = DynStep::clone_box(&step);
assert_eq!(cloned.dyn_name(), "coin");
}
#[test]
fn coin_zero_returns_empty() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = CoinStep::new(0.0);
let input: Vec<Traverser> = vec![
Traverser::new(Value::Int(1)),
Traverser::new(Value::Int(2)),
Traverser::new(Value::Int(3)),
Traverser::new(Value::Int(4)),
Traverser::new(Value::Int(5)),
];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert!(output.is_empty());
}
#[test]
fn coin_one_returns_all() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = CoinStep::new(1.0);
let input: Vec<Traverser> = vec![
Traverser::new(Value::Int(1)),
Traverser::new(Value::Int(2)),
Traverser::new(Value::Int(3)),
Traverser::new(Value::Int(4)),
Traverser::new(Value::Int(5)),
];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 5);
}
#[test]
fn coin_never_returns_empty() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = CoinStep::never();
let input: Vec<Traverser> = vec![
Traverser::new(Value::Int(1)),
Traverser::new(Value::Int(2)),
Traverser::new(Value::Int(3)),
];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert!(output.is_empty());
}
#[test]
fn coin_always_returns_all() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = CoinStep::always();
let input: Vec<Traverser> = vec![
Traverser::new(Value::Int(1)),
Traverser::new(Value::Int(2)),
Traverser::new(Value::Int(3)),
];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 3);
}
#[test]
fn coin_half_returns_approximately_half_statistical() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = CoinStep::new(0.5);
let input: Vec<Traverser> = (0..1000).map(|i| Traverser::new(Value::Int(i))).collect();
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
let count = output.len();
assert!(
count > 400 && count < 600,
"Expected approximately 500 results, got {}",
count
);
}
#[test]
fn coin_tenth_returns_approximately_tenth_statistical() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = CoinStep::new(0.1);
let input: Vec<Traverser> = (0..1000).map(|i| Traverser::new(Value::Int(i))).collect();
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
let count = output.len();
assert!(
count > 50 && count < 150,
"Expected approximately 100 results, got {}",
count
);
}
#[test]
fn empty_input_returns_empty() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = CoinStep::new(0.5);
let input: Vec<Traverser> = vec![];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert!(output.is_empty());
}
#[test]
fn preserves_traverser_metadata() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = CoinStep::new(1.0);
let mut traverser = Traverser::new(Value::Int(42));
traverser.extend_path_labeled("start");
traverser.loops = 5;
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!(output[0].path.has_label("start"));
assert_eq!(output[0].loops, 5);
assert_eq!(output[0].bulk, 10);
}
#[test]
fn coin_step_is_copy() {
let step1 = CoinStep::new(0.5);
let step2 = step1; let _step3 = step1;
assert!((step2.probability() - 0.5).abs() < f64::EPSILON);
}
#[test]
fn debug_format() {
let step = CoinStep::new(0.5);
let debug_str = format!("{:?}", step);
assert!(debug_str.contains("CoinStep"));
assert!(debug_str.contains("0.5"));
}
#[test]
fn works_with_vertices() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = CoinStep::new(1.0);
let input: Vec<Traverser> = vec![
Traverser::from_vertex(VertexId(0)),
Traverser::from_vertex(VertexId(1)),
Traverser::from_vertex(VertexId(2)),
];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 3);
}
#[test]
fn works_with_edges() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = CoinStep::new(1.0);
let input: Vec<Traverser> = vec![
Traverser::new(Value::Edge(EdgeId(0))),
Traverser::new(Value::Edge(EdgeId(1))),
];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 2);
}
#[test]
fn nan_probability_treated_as_zero() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = CoinStep::new(f64::NAN);
let input: Vec<Traverser> = vec![
Traverser::new(Value::Int(1)),
Traverser::new(Value::Int(2)),
Traverser::new(Value::Int(3)),
];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert!(output.is_empty());
}
}
mod sample_step_tests {
use super::*;
use crate::traversal::step::DynStep;
use std::collections::HashSet;
#[test]
fn new_creates_sample_step() {
let step = SampleStep::new(5);
assert_eq!(step.count(), 5);
}
#[test]
fn default_creates_sample_one() {
let step = SampleStep::default();
assert_eq!(step.count(), 1);
}
#[test]
fn name_returns_sample() {
let step = SampleStep::new(5);
assert_eq!(step.name(), "sample");
}
#[test]
fn clone_box_works() {
let step = SampleStep::new(5);
let cloned = DynStep::clone_box(&step);
assert_eq!(cloned.dyn_name(), "sample");
}
#[test]
fn sample_zero_returns_empty() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = SampleStep::new(0);
let input: Vec<Traverser> = vec![
Traverser::new(Value::Int(1)),
Traverser::new(Value::Int(2)),
Traverser::new(Value::Int(3)),
];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert!(output.is_empty());
}
#[test]
fn sample_larger_than_input_returns_all() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = SampleStep::new(10);
let input: Vec<Traverser> = vec![
Traverser::new(Value::Int(1)),
Traverser::new(Value::Int(2)),
Traverser::new(Value::Int(3)),
];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 3);
}
#[test]
fn sample_equal_to_input_returns_all() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = SampleStep::new(5);
let input: Vec<Traverser> = vec![
Traverser::new(Value::Int(1)),
Traverser::new(Value::Int(2)),
Traverser::new(Value::Int(3)),
Traverser::new(Value::Int(4)),
Traverser::new(Value::Int(5)),
];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 5);
}
#[test]
fn sample_returns_exactly_n_elements() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = SampleStep::new(5);
let input: Vec<Traverser> = (0..100).map(|i| Traverser::new(Value::Int(i))).collect();
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 5);
}
#[test]
fn empty_input_returns_empty() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = SampleStep::new(5);
let input: Vec<Traverser> = vec![];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert!(output.is_empty());
}
#[test]
fn preserves_traverser_metadata() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = SampleStep::new(1);
let mut traverser = Traverser::new(Value::Int(42));
traverser.extend_path_labeled("start");
traverser.loops = 5;
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!(output[0].path.has_label("start"));
assert_eq!(output[0].loops, 5);
assert_eq!(output[0].bulk, 10);
}
#[test]
fn sample_step_is_copy() {
let step1 = SampleStep::new(5);
let step2 = step1; let _step3 = step1;
assert_eq!(step2.count(), 5);
}
#[test]
fn debug_format() {
let step = SampleStep::new(5);
let debug_str = format!("{:?}", step);
assert!(debug_str.contains("SampleStep"));
assert!(debug_str.contains("5"));
}
#[test]
fn works_with_vertices() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = SampleStep::new(2);
let input: Vec<Traverser> = vec![
Traverser::from_vertex(VertexId(0)),
Traverser::from_vertex(VertexId(1)),
Traverser::from_vertex(VertexId(2)),
];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 2);
}
#[test]
fn works_with_edges() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = SampleStep::new(2);
let input: Vec<Traverser> = vec![
Traverser::new(Value::Edge(EdgeId(0))),
Traverser::new(Value::Edge(EdgeId(1))),
Traverser::new(Value::Edge(EdgeId(2))),
];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 2);
}
#[test]
fn sample_elements_come_from_input() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = SampleStep::new(5);
let input_values: Vec<i64> = (0..100).collect();
let input: Vec<Traverser> = input_values
.iter()
.map(|&i| Traverser::new(Value::Int(i)))
.collect();
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
let input_set: HashSet<i64> = input_values.into_iter().collect();
for t in output {
if let Value::Int(v) = t.value {
assert!(
input_set.contains(&v),
"Output value {} not in input set",
v
);
} else {
panic!("Expected Int value");
}
}
}
#[test]
fn sample_returns_distinct_elements_from_distinct_input() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = SampleStep::new(5);
let input: Vec<Traverser> = (0..100).map(|i| Traverser::new(Value::Int(i))).collect();
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
let output_values: Vec<i64> = output
.iter()
.map(|t| match &t.value {
Value::Int(v) => *v,
_ => panic!("Expected Int"),
})
.collect();
let unique_values: HashSet<i64> = output_values.iter().copied().collect();
assert_eq!(
unique_values.len(),
output_values.len(),
"Sampled elements should be unique when input is unique"
);
}
#[test]
fn distribution_is_approximately_uniform_statistical() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let mut counts: HashMap<i64, usize> = HashMap::new();
let input_size = 100;
let sample_size = 10;
let num_trials = 1000;
for _ in 0..num_trials {
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = SampleStep::new(sample_size);
let input: Vec<Traverser> = (0..input_size as i64)
.map(|i| Traverser::new(Value::Int(i)))
.collect();
let output: Vec<Traverser> =
step.apply(&ctx, Box::new(input.into_iter())).collect();
for t in output {
if let Value::Int(v) = t.value {
*counts.entry(v).or_insert(0) += 1;
}
}
}
let expected = (sample_size as f64 / input_size as f64) * num_trials as f64;
let min_expected = (expected * 0.5) as usize;
let max_expected = (expected * 1.5) as usize;
let within_range = counts
.values()
.filter(|&&c| c >= min_expected && c <= max_expected)
.count();
assert!(
within_range >= (input_size as f64 * 0.8) as usize,
"Expected at least 80% of values to be sampled with approximately uniform frequency"
);
}
}
mod has_key_step_tests {
use super::*;
use crate::traversal::step::DynStep;
fn create_property_map(key: &str, value: Value) -> Value {
let mut map = crate::value::ValueMap::new();
map.insert("key".to_string(), Value::String(key.to_string()));
map.insert("value".to_string(), value);
Value::Map(map)
}
#[test]
fn new_creates_has_key_step() {
let step = HasKeyStep::new("name");
assert_eq!(step.keys(), &["name".to_string()]);
}
#[test]
fn any_creates_multi_key_step() {
let step = HasKeyStep::any(["name", "age", "email"]);
assert_eq!(step.keys().len(), 3);
}
#[test]
fn name_returns_has_key() {
let step = HasKeyStep::new("name");
assert_eq!(step.name(), "hasKey");
}
#[test]
fn clone_box_works() {
let step = HasKeyStep::new("name");
let cloned = DynStep::clone_box(&step);
assert_eq!(cloned.dyn_name(), "hasKey");
}
#[test]
fn filters_property_map_by_single_key() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = HasKeyStep::new("name");
let input: Vec<Traverser> = vec![
Traverser::new(create_property_map(
"name",
Value::String("Alice".to_string()),
)),
Traverser::new(create_property_map("age", Value::Int(30))),
Traverser::new(create_property_map(
"name",
Value::String("Bob".to_string()),
)),
Traverser::new(create_property_map(
"email",
Value::String("test@test.com".to_string()),
)),
];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 2);
for t in &output {
if let Value::Map(map) = &t.value {
assert_eq!(map.get("key"), Some(&Value::String("name".to_string())));
} else {
panic!("Expected Map value");
}
}
}
#[test]
fn filters_property_map_by_multiple_keys() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = HasKeyStep::any(["name", "age"]);
let input: Vec<Traverser> = vec![
Traverser::new(create_property_map(
"name",
Value::String("Alice".to_string()),
)),
Traverser::new(create_property_map("age", Value::Int(30))),
Traverser::new(create_property_map(
"email",
Value::String("test@test.com".to_string()),
)),
Traverser::new(create_property_map(
"city",
Value::String("NYC".to_string()),
)),
];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 2);
}
#[test]
fn non_map_values_filtered_out() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = HasKeyStep::new("name");
let input: Vec<Traverser> = vec![
Traverser::new(Value::Int(42)),
Traverser::new(Value::String("hello".to_string())),
Traverser::from_vertex(VertexId(0)),
];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert!(output.is_empty());
}
#[test]
fn map_without_key_field_filtered_out() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = HasKeyStep::new("name");
let mut map = crate::value::ValueMap::new();
map.insert("value".to_string(), Value::String("Alice".to_string()));
let input: Vec<Traverser> = vec![Traverser::new(Value::Map(map))];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert!(output.is_empty());
}
#[test]
fn empty_input_returns_empty() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = HasKeyStep::new("name");
let input: Vec<Traverser> = vec![];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert!(output.is_empty());
}
#[test]
fn preserves_traverser_metadata() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = HasKeyStep::new("name");
let mut traverser = Traverser::new(create_property_map(
"name",
Value::String("Alice".to_string()),
));
traverser.extend_path_labeled("start");
traverser.loops = 5;
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!(output[0].path.has_label("start"));
assert_eq!(output[0].loops, 5);
assert_eq!(output[0].bulk, 10);
}
#[test]
fn debug_format() {
let step = HasKeyStep::new("name");
let debug_str = format!("{:?}", step);
assert!(debug_str.contains("HasKeyStep"));
assert!(debug_str.contains("name"));
}
}
mod has_prop_value_step_tests {
use super::*;
use crate::traversal::step::DynStep;
fn create_property_map(key: &str, value: Value) -> Value {
let mut map = crate::value::ValueMap::new();
map.insert("key".to_string(), Value::String(key.to_string()));
map.insert("value".to_string(), value);
Value::Map(map)
}
#[test]
fn new_creates_has_prop_value_step() {
let step = HasPropValueStep::new("Alice");
assert_eq!(step.values().len(), 1);
}
#[test]
fn any_creates_multi_value_step() {
let step = HasPropValueStep::any(["Alice", "Bob", "Carol"]);
assert_eq!(step.values().len(), 3);
}
#[test]
fn name_returns_has_value() {
let step = HasPropValueStep::new("Alice");
assert_eq!(step.name(), "hasValue");
}
#[test]
fn clone_box_works() {
let step = HasPropValueStep::new("Alice");
let cloned = DynStep::clone_box(&step);
assert_eq!(cloned.dyn_name(), "hasValue");
}
#[test]
fn filters_property_map_by_single_string_value() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = HasPropValueStep::new("Alice");
let input: Vec<Traverser> = vec![
Traverser::new(create_property_map(
"name",
Value::String("Alice".to_string()),
)),
Traverser::new(create_property_map(
"name",
Value::String("Bob".to_string()),
)),
Traverser::new(create_property_map(
"name",
Value::String("Alice".to_string()),
)),
];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 2);
}
#[test]
fn filters_property_map_by_single_int_value() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = HasPropValueStep::new(30i64);
let input: Vec<Traverser> = vec![
Traverser::new(create_property_map("age", Value::Int(30))),
Traverser::new(create_property_map("age", Value::Int(25))),
Traverser::new(create_property_map("age", Value::Int(30))),
];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 2);
}
#[test]
fn filters_property_map_by_multiple_values() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = HasPropValueStep::any(["Alice", "Carol"]);
let input: Vec<Traverser> = vec![
Traverser::new(create_property_map(
"name",
Value::String("Alice".to_string()),
)),
Traverser::new(create_property_map(
"name",
Value::String("Bob".to_string()),
)),
Traverser::new(create_property_map(
"name",
Value::String("Carol".to_string()),
)),
Traverser::new(create_property_map(
"name",
Value::String("Dave".to_string()),
)),
];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 2);
}
#[test]
fn non_map_values_filtered_out() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = HasPropValueStep::new("Alice");
let input: Vec<Traverser> = vec![
Traverser::new(Value::Int(42)),
Traverser::new(Value::String("Alice".to_string())),
Traverser::from_vertex(VertexId(0)),
];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert!(output.is_empty());
}
#[test]
fn map_without_value_field_filtered_out() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = HasPropValueStep::new("Alice");
let mut map = crate::value::ValueMap::new();
map.insert("key".to_string(), Value::String("name".to_string()));
let input: Vec<Traverser> = vec![Traverser::new(Value::Map(map))];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert!(output.is_empty());
}
#[test]
fn empty_input_returns_empty() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = HasPropValueStep::new("Alice");
let input: Vec<Traverser> = vec![];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert!(output.is_empty());
}
#[test]
fn preserves_traverser_metadata() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = HasPropValueStep::new("Alice");
let mut traverser = Traverser::new(create_property_map(
"name",
Value::String("Alice".to_string()),
));
traverser.extend_path_labeled("start");
traverser.loops = 5;
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!(output[0].path.has_label("start"));
assert_eq!(output[0].loops, 5);
assert_eq!(output[0].bulk, 10);
}
#[test]
fn debug_format() {
let step = HasPropValueStep::new("Alice");
let debug_str = format!("{:?}", step);
assert!(debug_str.contains("HasPropValueStep"));
}
#[test]
fn works_with_mixed_value_types() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = HasPropValueStep::new(30i64);
let input: Vec<Traverser> = vec![
Traverser::new(create_property_map("age", Value::Int(30))),
Traverser::new(create_property_map("id", Value::String("30".to_string()))),
Traverser::new(create_property_map("count", Value::Int(30))),
];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 2);
}
}
mod range_step_tests {
use super::*;
use crate::traversal::step::DynStep;
#[test]
fn new_creates_range_step() {
let step = RangeStep::new(2, 5);
assert_eq!(step.start, 2);
assert_eq!(step.end, 5);
}
#[test]
fn name_returns_range() {
let step = RangeStep::new(2, 5);
assert_eq!(step.name(), "range");
}
#[test]
fn clone_box_works() {
let step = RangeStep::new(2, 5);
let cloned = DynStep::clone_box(&step);
assert_eq!(cloned.dyn_name(), "range");
}
#[test]
fn range_selects_middle_elements() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = RangeStep::new(2, 5);
let input: Vec<Traverser> = vec![
Traverser::new(Value::Int(0)),
Traverser::new(Value::Int(1)),
Traverser::new(Value::Int(2)),
Traverser::new(Value::Int(3)),
Traverser::new(Value::Int(4)),
Traverser::new(Value::Int(5)),
Traverser::new(Value::Int(6)),
];
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(2));
assert_eq!(output[1].value, Value::Int(3));
assert_eq!(output[2].value, Value::Int(4));
}
#[test]
fn range_from_start() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = RangeStep::new(0, 3);
let input: Vec<Traverser> = vec![
Traverser::new(Value::Int(1)),
Traverser::new(Value::Int(2)),
Traverser::new(Value::Int(3)),
Traverser::new(Value::Int(4)),
Traverser::new(Value::Int(5)),
];
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(1));
assert_eq!(output[1].value, Value::Int(2));
assert_eq!(output[2].value, Value::Int(3));
}
#[test]
fn range_to_end() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = RangeStep::new(3, 100);
let input: Vec<Traverser> = vec![
Traverser::new(Value::Int(1)),
Traverser::new(Value::Int(2)),
Traverser::new(Value::Int(3)),
Traverser::new(Value::Int(4)),
Traverser::new(Value::Int(5)),
];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 2);
assert_eq!(output[0].value, Value::Int(4));
assert_eq!(output[1].value, Value::Int(5));
}
#[test]
fn range_equal_start_end_returns_empty() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = RangeStep::new(3, 3);
let input: Vec<Traverser> = vec![
Traverser::new(Value::Int(1)),
Traverser::new(Value::Int(2)),
Traverser::new(Value::Int(3)),
Traverser::new(Value::Int(4)),
Traverser::new(Value::Int(5)),
];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert!(output.is_empty());
}
#[test]
fn range_end_less_than_start_returns_empty() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = RangeStep::new(5, 2);
let input: Vec<Traverser> = vec![
Traverser::new(Value::Int(1)),
Traverser::new(Value::Int(2)),
Traverser::new(Value::Int(3)),
Traverser::new(Value::Int(4)),
Traverser::new(Value::Int(5)),
];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert!(output.is_empty());
}
#[test]
fn range_start_beyond_input_returns_empty() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = RangeStep::new(10, 20);
let input: Vec<Traverser> = vec![
Traverser::new(Value::Int(1)),
Traverser::new(Value::Int(2)),
Traverser::new(Value::Int(3)),
];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert!(output.is_empty());
}
#[test]
fn range_single_element() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = RangeStep::new(2, 3);
let input: Vec<Traverser> = vec![
Traverser::new(Value::Int(1)),
Traverser::new(Value::Int(2)),
Traverser::new(Value::Int(3)),
Traverser::new(Value::Int(4)),
Traverser::new(Value::Int(5)),
];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 1);
assert_eq!(output[0].value, Value::Int(3));
}
#[test]
fn empty_input_returns_empty() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = RangeStep::new(0, 5);
let input: Vec<Traverser> = vec![];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert!(output.is_empty());
}
#[test]
fn preserves_traverser_metadata() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = RangeStep::new(1, 2);
let t1 = Traverser::new(Value::Int(1));
let mut t2 = Traverser::new(Value::Int(2));
t2.extend_path_labeled("kept");
t2.loops = 5;
t2.bulk = 10;
let t3 = Traverser::new(Value::Int(3));
let input = vec![t1, t2, t3];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 1);
assert!(output[0].path.has_label("kept"));
assert_eq!(output[0].loops, 5);
assert_eq!(output[0].bulk, 10);
}
#[test]
fn range_step_is_clone() {
let step1 = RangeStep::new(2, 5);
let step2 = step1.clone();
let _step3 = step1.clone();
assert_eq!(step2.start, 2);
assert_eq!(step2.end, 5);
}
#[test]
fn debug_format() {
let step = RangeStep::new(2, 5);
let debug_str = format!("{:?}", step);
assert!(debug_str.contains("RangeStep"));
assert!(debug_str.contains("2"));
assert!(debug_str.contains("5"));
}
#[test]
fn works_with_vertices() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = RangeStep::new(1, 3);
let input: Vec<Traverser> = vec![
Traverser::from_vertex(VertexId(0)),
Traverser::from_vertex(VertexId(1)),
Traverser::from_vertex(VertexId(2)),
Traverser::from_vertex(VertexId(3)),
];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 2);
assert_eq!(output[0].as_vertex_id(), Some(VertexId(1)));
assert_eq!(output[1].as_vertex_id(), Some(VertexId(2)));
}
#[test]
fn range_equivalent_to_skip_then_limit() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let range_step = RangeStep::new(2, 5);
let input: Vec<Traverser> = vec![
Traverser::new(Value::Int(0)),
Traverser::new(Value::Int(1)),
Traverser::new(Value::Int(2)),
Traverser::new(Value::Int(3)),
Traverser::new(Value::Int(4)),
Traverser::new(Value::Int(5)),
Traverser::new(Value::Int(6)),
];
let range_output: Vec<Value> = range_step
.apply(&ctx, Box::new(input.clone().into_iter()))
.map(|t| t.value)
.collect();
let skip_limit_output: Vec<Value> =
input.into_iter().skip(2).take(3).map(|t| t.value).collect();
assert_eq!(range_output, skip_limit_output);
}
#[test]
fn streaming_range_matches_eager() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let g = snapshot.gremlin();
let eager: Vec<Value> = g.v().range(1, 3).to_list();
let streaming: Vec<Value> = g.v().range(1, 3).iter().collect();
assert_eq!(eager.len(), streaming.len());
assert_eq!(eager.len(), 2); }
#[test]
fn streaming_range_respects_bounds() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let g = snapshot.gremlin();
let result: Vec<Value> = g.v().range(0, 2).iter().collect();
assert_eq!(result.len(), 2);
let result2: Vec<Value> = g.v().range(2, 4).iter().collect();
assert_eq!(result2.len(), 2);
let result3: Vec<Value> = g.v().range(10, 20).iter().collect();
assert!(result3.is_empty());
}
#[test]
fn streaming_range_empty_when_start_equals_end() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let g = snapshot.gremlin();
let result: Vec<Value> = g.v().range(2, 2).iter().collect();
assert!(result.is_empty());
}
}
mod has_id_step_tests {
use super::*;
use crate::traversal::step::DynStep;
#[test]
fn vertex_creates_single_vertex_id_step() {
let step = HasIdStep::vertex(VertexId(42));
assert_eq!(step.ids.len(), 1);
assert_eq!(step.ids[0], Value::Vertex(VertexId(42)));
}
#[test]
fn vertices_creates_multi_vertex_id_step() {
let step = HasIdStep::vertices(vec![VertexId(1), VertexId(2), VertexId(3)]);
assert_eq!(step.ids.len(), 3);
assert_eq!(step.ids[0], Value::Vertex(VertexId(1)));
assert_eq!(step.ids[1], Value::Vertex(VertexId(2)));
assert_eq!(step.ids[2], Value::Vertex(VertexId(3)));
}
#[test]
fn edge_creates_single_edge_id_step() {
let step = HasIdStep::edge(EdgeId(99));
assert_eq!(step.ids.len(), 1);
assert_eq!(step.ids[0], Value::Edge(EdgeId(99)));
}
#[test]
fn edges_creates_multi_edge_id_step() {
let step = HasIdStep::edges(vec![EdgeId(10), EdgeId(20)]);
assert_eq!(step.ids.len(), 2);
assert_eq!(step.ids[0], Value::Edge(EdgeId(10)));
assert_eq!(step.ids[1], Value::Edge(EdgeId(20)));
}
#[test]
fn from_value_creates_single_id_step() {
let step = HasIdStep::from_value(Value::Vertex(VertexId(5)));
assert_eq!(step.ids.len(), 1);
assert_eq!(step.ids[0], Value::Vertex(VertexId(5)));
}
#[test]
fn from_values_creates_multi_id_step() {
let step = HasIdStep::from_values(vec![
Value::Vertex(VertexId(1)),
Value::Vertex(VertexId(2)),
]);
assert_eq!(step.ids.len(), 2);
}
#[test]
fn name_returns_has_id() {
let step = HasIdStep::vertex(VertexId(1));
assert_eq!(step.name(), "hasId");
}
#[test]
fn clone_box_works() {
let step = HasIdStep::vertex(VertexId(1));
let cloned = DynStep::clone_box(&step);
assert_eq!(cloned.dyn_name(), "hasId");
}
#[test]
fn filters_vertices_by_single_id() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = HasIdStep::vertex(VertexId(1));
let input: Vec<Traverser> = vec![
Traverser::from_vertex(VertexId(0)),
Traverser::from_vertex(VertexId(1)), Traverser::from_vertex(VertexId(2)),
Traverser::from_vertex(VertexId(3)),
];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 1);
assert_eq!(output[0].as_vertex_id(), Some(VertexId(1)));
}
#[test]
fn filters_vertices_by_multiple_ids() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = HasIdStep::vertices(vec![VertexId(0), VertexId(2)]);
let input: Vec<Traverser> = vec![
Traverser::from_vertex(VertexId(0)), Traverser::from_vertex(VertexId(1)),
Traverser::from_vertex(VertexId(2)), Traverser::from_vertex(VertexId(3)),
];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 2);
assert_eq!(output[0].as_vertex_id(), Some(VertexId(0)));
assert_eq!(output[1].as_vertex_id(), Some(VertexId(2)));
}
#[test]
fn filters_edges_by_single_id() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = HasIdStep::edge(EdgeId(1));
let input: Vec<Traverser> = vec![
Traverser::from_edge(EdgeId(0)),
Traverser::from_edge(EdgeId(1)), Traverser::from_edge(EdgeId(2)),
];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 1);
assert_eq!(output[0].as_edge_id(), Some(EdgeId(1)));
}
#[test]
fn filters_edges_by_multiple_ids() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = HasIdStep::edges(vec![EdgeId(0), EdgeId(2)]);
let input: Vec<Traverser> = vec![
Traverser::from_edge(EdgeId(0)), Traverser::from_edge(EdgeId(1)),
Traverser::from_edge(EdgeId(2)), ];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 2);
assert_eq!(output[0].as_edge_id(), Some(EdgeId(0)));
assert_eq!(output[1].as_edge_id(), Some(EdgeId(2)));
}
#[test]
fn filters_out_non_element_values() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = HasIdStep::vertex(VertexId(0));
let input: Vec<Traverser> = vec![
Traverser::from_vertex(VertexId(0)), Traverser::new(Value::Int(0)), Traverser::new(Value::String("0".to_string())), Traverser::new(Value::Bool(false)), Traverser::new(Value::Null), ];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 1);
assert_eq!(output[0].as_vertex_id(), Some(VertexId(0)));
}
#[test]
fn vertex_id_step_does_not_match_edges() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = HasIdStep::vertex(VertexId(0));
let input: Vec<Traverser> = vec![
Traverser::from_edge(EdgeId(0)), Traverser::from_edge(EdgeId(1)),
];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert!(output.is_empty());
}
#[test]
fn edge_id_step_does_not_match_vertices() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = HasIdStep::edge(EdgeId(0));
let input: Vec<Traverser> = 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!(output.is_empty());
}
#[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 = HasIdStep::vertex(VertexId(1));
let input: Vec<Traverser> = vec![];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert!(output.is_empty());
}
#[test]
fn returns_empty_for_nonexistent_id() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = HasIdStep::vertex(VertexId(999));
let input: Vec<Traverser> = vec![
Traverser::from_vertex(VertexId(0)),
Traverser::from_vertex(VertexId(1)),
Traverser::from_vertex(VertexId(2)),
];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert!(output.is_empty());
}
#[test]
fn preserves_traverser_metadata() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = HasIdStep::vertex(VertexId(1));
let mut traverser = Traverser::from_vertex(VertexId(1));
traverser.extend_path_labeled("start");
traverser.loops = 5;
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!(output[0].path.has_label("start"));
assert_eq!(output[0].loops, 5);
assert_eq!(output[0].bulk, 10);
}
#[test]
fn mixed_vertex_and_edge_input() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = HasIdStep::vertex(VertexId(1));
let input: Vec<Traverser> = vec![
Traverser::from_vertex(VertexId(0)),
Traverser::from_edge(EdgeId(1)), Traverser::from_vertex(VertexId(1)), Traverser::from_edge(EdgeId(2)),
];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 1);
assert_eq!(output[0].as_vertex_id(), Some(VertexId(1)));
}
#[test]
fn from_values_with_mixed_types() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step =
HasIdStep::from_values(vec![Value::Vertex(VertexId(0)), Value::Edge(EdgeId(1))]);
let input: Vec<Traverser> = vec![
Traverser::from_vertex(VertexId(0)), Traverser::from_vertex(VertexId(1)),
Traverser::from_edge(EdgeId(0)),
Traverser::from_edge(EdgeId(1)), ];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 2);
assert_eq!(output[0].as_vertex_id(), Some(VertexId(0)));
assert_eq!(output[1].as_edge_id(), Some(EdgeId(1)));
}
#[test]
fn debug_format() {
let step = HasIdStep::vertices(vec![VertexId(1), VertexId(2)]);
let debug_str = format!("{:?}", step);
assert!(debug_str.contains("HasIdStep"));
assert!(debug_str.contains("ids"));
}
#[test]
fn clone_works() {
let step = HasIdStep::vertex(VertexId(42));
let cloned = step.clone();
assert_eq!(cloned.ids.len(), 1);
assert_eq!(cloned.ids[0], Value::Vertex(VertexId(42)));
}
}
mod has_where_step_tests {
use super::*;
use crate::traversal::predicate::p;
use crate::traversal::step::DynStep;
fn create_graph_with_ages() -> 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("person", {
let mut props = HashMap::new();
props.insert("name".to_string(), Value::String("Bob".to_string()));
props.insert("age".to_string(), Value::Int(25));
props
});
let v2 = graph.add_vertex("person", {
let mut props = HashMap::new();
props.insert("name".to_string(), Value::String("Charlie".to_string()));
props.insert("age".to_string(), Value::Int(35));
props
});
graph.add_vertex("person", {
let mut props = HashMap::new();
props.insert("name".to_string(), Value::String("Dave".to_string()));
props
});
graph.add_vertex("software", {
let mut props = HashMap::new();
props.insert("name".to_string(), Value::String("Graph DB".to_string()));
props.insert("version".to_string(), Value::Float(1.5));
props
});
graph
.add_edge(v0, v1, "knows", {
let mut props = HashMap::new();
props.insert("weight".to_string(), Value::Float(0.8));
props
})
.unwrap();
graph
.add_edge(v1, v2, "knows", {
let mut props = HashMap::new();
props.insert("weight".to_string(), Value::Float(0.3));
props
})
.unwrap();
graph
}
#[test]
fn new_creates_has_where_step() {
let step = HasWhereStep::new("age", p::gte(18));
assert_eq!(step.key, "age");
}
#[test]
fn name_returns_has() {
let step = HasWhereStep::new("age", p::gte(18));
assert_eq!(step.name(), "has");
}
#[test]
fn clone_box_works() {
let step = HasWhereStep::new("age", p::gte(18));
let cloned = DynStep::clone_box(&step);
assert_eq!(cloned.dyn_name(), "has");
}
#[test]
fn filters_vertices_with_gte_predicate() {
let graph = create_graph_with_ages();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = HasWhereStep::new("age", p::gte(30i64));
let input: Vec<Traverser> = vec![
Traverser::from_vertex(VertexId(0)), Traverser::from_vertex(VertexId(1)), Traverser::from_vertex(VertexId(2)), Traverser::from_vertex(VertexId(3)), ];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 2);
assert_eq!(output[0].as_vertex_id(), Some(VertexId(0))); assert_eq!(output[1].as_vertex_id(), Some(VertexId(2))); }
#[test]
fn filters_vertices_with_lt_predicate() {
let graph = create_graph_with_ages();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = HasWhereStep::new("age", p::lt(30i64));
let input: Vec<Traverser> = vec![
Traverser::from_vertex(VertexId(0)), Traverser::from_vertex(VertexId(1)), Traverser::from_vertex(VertexId(2)), ];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 1);
assert_eq!(output[0].as_vertex_id(), Some(VertexId(1))); }
#[test]
fn filters_vertices_with_between_predicate() {
let graph = create_graph_with_ages();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = HasWhereStep::new("age", p::between(26i64, 34i64));
let input: Vec<Traverser> = vec![
Traverser::from_vertex(VertexId(0)), Traverser::from_vertex(VertexId(1)), Traverser::from_vertex(VertexId(2)), ];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 1);
assert_eq!(output[0].as_vertex_id(), Some(VertexId(0))); }
#[test]
fn filters_vertices_with_eq_predicate() {
let graph = create_graph_with_ages();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = HasWhereStep::new("age", p::eq(30i64));
let input: Vec<Traverser> = vec![
Traverser::from_vertex(VertexId(0)), Traverser::from_vertex(VertexId(1)), Traverser::from_vertex(VertexId(2)), ];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 1);
assert_eq!(output[0].as_vertex_id(), Some(VertexId(0))); }
#[test]
fn filters_vertices_with_neq_predicate() {
let graph = create_graph_with_ages();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = HasWhereStep::new("age", p::neq(30i64));
let input: Vec<Traverser> = vec![
Traverser::from_vertex(VertexId(0)), Traverser::from_vertex(VertexId(1)), Traverser::from_vertex(VertexId(2)), ];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 2);
assert_eq!(output[0].as_vertex_id(), Some(VertexId(1))); assert_eq!(output[1].as_vertex_id(), Some(VertexId(2))); }
#[test]
fn filters_vertices_with_within_predicate() {
let graph = create_graph_with_ages();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = HasWhereStep::new("age", p::within([25i64, 35i64]));
let input: Vec<Traverser> = vec![
Traverser::from_vertex(VertexId(0)), Traverser::from_vertex(VertexId(1)), Traverser::from_vertex(VertexId(2)), ];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 2);
assert_eq!(output[0].as_vertex_id(), Some(VertexId(1))); assert_eq!(output[1].as_vertex_id(), Some(VertexId(2))); }
#[test]
fn filters_vertices_with_without_predicate() {
let graph = create_graph_with_ages();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = HasWhereStep::new("age", p::without([25i64, 35i64]));
let input: Vec<Traverser> = vec![
Traverser::from_vertex(VertexId(0)), Traverser::from_vertex(VertexId(1)), Traverser::from_vertex(VertexId(2)), ];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 1);
assert_eq!(output[0].as_vertex_id(), Some(VertexId(0))); }
#[test]
fn filters_vertices_with_string_predicate() {
let graph = create_graph_with_ages();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = HasWhereStep::new("name", p::starting_with("A"));
let input: Vec<Traverser> = vec![
Traverser::from_vertex(VertexId(0)), Traverser::from_vertex(VertexId(1)), Traverser::from_vertex(VertexId(2)), ];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 1);
assert_eq!(output[0].as_vertex_id(), Some(VertexId(0))); }
#[test]
fn filters_vertices_with_containing_predicate() {
let graph = create_graph_with_ages();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = HasWhereStep::new("name", p::containing("li"));
let input: Vec<Traverser> = vec![
Traverser::from_vertex(VertexId(0)), Traverser::from_vertex(VertexId(1)), Traverser::from_vertex(VertexId(2)), ];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 2);
assert_eq!(output[0].as_vertex_id(), Some(VertexId(0))); assert_eq!(output[1].as_vertex_id(), Some(VertexId(2))); }
#[test]
fn filters_vertices_with_and_predicate() {
let graph = create_graph_with_ages();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = HasWhereStep::new("age", p::and(p::gte(25i64), p::lt(35i64)));
let input: Vec<Traverser> = vec![
Traverser::from_vertex(VertexId(0)), Traverser::from_vertex(VertexId(1)), Traverser::from_vertex(VertexId(2)), ];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 2);
assert_eq!(output[0].as_vertex_id(), Some(VertexId(0))); assert_eq!(output[1].as_vertex_id(), Some(VertexId(1))); }
#[test]
fn filters_vertices_with_or_predicate() {
let graph = create_graph_with_ages();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = HasWhereStep::new("age", p::or(p::eq(25i64), p::eq(35i64)));
let input: Vec<Traverser> = vec![
Traverser::from_vertex(VertexId(0)), Traverser::from_vertex(VertexId(1)), Traverser::from_vertex(VertexId(2)), ];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 2);
assert_eq!(output[0].as_vertex_id(), Some(VertexId(1))); assert_eq!(output[1].as_vertex_id(), Some(VertexId(2))); }
#[test]
fn filters_vertices_with_not_predicate() {
let graph = create_graph_with_ages();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = HasWhereStep::new("age", p::not(p::eq(30i64)));
let input: Vec<Traverser> = vec![
Traverser::from_vertex(VertexId(0)), Traverser::from_vertex(VertexId(1)), Traverser::from_vertex(VertexId(2)), ];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 2);
assert_eq!(output[0].as_vertex_id(), Some(VertexId(1))); assert_eq!(output[1].as_vertex_id(), Some(VertexId(2))); }
#[test]
fn filters_edges_with_predicate() {
let graph = create_graph_with_ages();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = HasWhereStep::new("weight", p::gt(0.5));
let input: Vec<Traverser> = vec![
Traverser::from_edge(EdgeId(0)), Traverser::from_edge(EdgeId(1)), ];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 1);
assert_eq!(output[0].as_edge_id(), Some(EdgeId(0)));
}
#[test]
fn filters_out_vertices_without_property() {
let graph = create_graph_with_ages();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = HasWhereStep::new("age", p::gte(0i64));
let input: Vec<Traverser> = vec![
Traverser::from_vertex(VertexId(0)), Traverser::from_vertex(VertexId(3)), ];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 1);
assert_eq!(output[0].as_vertex_id(), Some(VertexId(0)));
}
#[test]
fn filters_out_non_element_values() {
let graph = create_graph_with_ages();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = HasWhereStep::new("age", p::gte(0i64));
let input: Vec<Traverser> = vec![
Traverser::from_vertex(VertexId(0)), Traverser::new(Value::Int(30)), Traverser::new(Value::String("30".into())), Traverser::new(Value::Bool(true)), Traverser::new(Value::Null), ];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 1);
assert_eq!(output[0].as_vertex_id(), Some(VertexId(0)));
}
#[test]
fn filters_out_nonexistent_vertices() {
let graph = create_graph_with_ages();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = HasWhereStep::new("age", p::gte(0i64));
let input: Vec<Traverser> = vec![
Traverser::from_vertex(VertexId(0)), Traverser::from_vertex(VertexId(999)), ];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 1);
assert_eq!(output[0].as_vertex_id(), Some(VertexId(0)));
}
#[test]
fn empty_input_returns_empty_output() {
let graph = create_graph_with_ages();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = HasWhereStep::new("age", p::gte(18i64));
let input: Vec<Traverser> = vec![];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert!(output.is_empty());
}
#[test]
fn preserves_traverser_metadata() {
let graph = create_graph_with_ages();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = HasWhereStep::new("age", p::gte(18i64));
let mut traverser = Traverser::from_vertex(VertexId(0));
traverser.extend_path_labeled("start");
traverser.loops = 5;
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!(output[0].path.has_label("start"));
assert_eq!(output[0].loops, 5);
assert_eq!(output[0].bulk, 10);
}
#[test]
fn debug_format() {
let step = HasWhereStep::new("age", p::gte(18i64));
let debug_str = format!("{:?}", step);
assert!(debug_str.contains("HasWhereStep"));
assert!(debug_str.contains("age"));
assert!(debug_str.contains("<predicate>"));
}
#[test]
fn clone_works() {
let step = HasWhereStep::new("age", p::gte(18i64));
let cloned = step.clone();
assert_eq!(cloned.key, "age");
}
#[test]
fn filters_float_property_with_gte() {
let graph = create_graph_with_ages();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = HasWhereStep::new("version", p::gte(Value::Float(1.0)));
let input: Vec<Traverser> = vec![
Traverser::from_vertex(VertexId(4)), Traverser::from_vertex(VertexId(0)), ];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 1);
assert_eq!(output[0].as_vertex_id(), Some(VertexId(4)));
}
}
mod is_step_tests {
use super::*;
use crate::traversal::predicate::p;
use crate::traversal::step::Step;
fn create_value_traverser(value: Value) -> Traverser {
Traverser::new(value)
}
fn create_empty_graph() -> Graph {
Graph::in_memory()
}
#[test]
fn new_creates_is_step() {
let step = IsStep::new(p::gt(25i64));
assert_eq!(step.name(), "is");
}
#[test]
fn eq_creates_equality_step() {
let step = IsStep::eq(29i64);
assert_eq!(step.name(), "is");
}
#[test]
fn filters_integer_values_with_eq() {
let graph = create_empty_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = IsStep::eq(29i64);
let input: Vec<Traverser> = vec![
create_value_traverser(Value::Int(29)),
create_value_traverser(Value::Int(30)),
create_value_traverser(Value::Int(25)),
create_value_traverser(Value::Int(29)),
];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 2);
assert_eq!(output[0].value, Value::Int(29));
assert_eq!(output[1].value, Value::Int(29));
}
#[test]
fn filters_integer_values_with_gt() {
let graph = create_empty_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = IsStep::new(p::gt(25i64));
let input: Vec<Traverser> = vec![
create_value_traverser(Value::Int(20)),
create_value_traverser(Value::Int(25)),
create_value_traverser(Value::Int(26)),
create_value_traverser(Value::Int(30)),
];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 2);
assert_eq!(output[0].value, Value::Int(26));
assert_eq!(output[1].value, Value::Int(30));
}
#[test]
fn filters_integer_values_with_gte() {
let graph = create_empty_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = IsStep::new(p::gte(25i64));
let input: Vec<Traverser> = vec![
create_value_traverser(Value::Int(20)),
create_value_traverser(Value::Int(24)),
create_value_traverser(Value::Int(25)),
create_value_traverser(Value::Int(30)),
];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 2);
assert_eq!(output[0].value, Value::Int(25));
assert_eq!(output[1].value, Value::Int(30));
}
#[test]
fn filters_integer_values_with_lt() {
let graph = create_empty_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = IsStep::new(p::lt(25i64));
let input: Vec<Traverser> = vec![
create_value_traverser(Value::Int(20)),
create_value_traverser(Value::Int(25)),
create_value_traverser(Value::Int(30)),
];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 1);
assert_eq!(output[0].value, Value::Int(20));
}
#[test]
fn filters_integer_values_with_lte() {
let graph = create_empty_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = IsStep::new(p::lte(25i64));
let input: Vec<Traverser> = vec![
create_value_traverser(Value::Int(20)),
create_value_traverser(Value::Int(25)),
create_value_traverser(Value::Int(30)),
];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 2);
assert_eq!(output[0].value, Value::Int(20));
assert_eq!(output[1].value, Value::Int(25));
}
#[test]
fn filters_integer_values_with_neq() {
let graph = create_empty_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = IsStep::new(p::neq(25i64));
let input: Vec<Traverser> = vec![
create_value_traverser(Value::Int(20)),
create_value_traverser(Value::Int(25)),
create_value_traverser(Value::Int(30)),
];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 2);
assert_eq!(output[0].value, Value::Int(20));
assert_eq!(output[1].value, Value::Int(30));
}
#[test]
fn filters_integer_values_with_between() {
let graph = create_empty_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = IsStep::new(p::between(20i64, 40i64));
let input: Vec<Traverser> = vec![
create_value_traverser(Value::Int(10)),
create_value_traverser(Value::Int(20)), create_value_traverser(Value::Int(30)),
create_value_traverser(Value::Int(40)), create_value_traverser(Value::Int(50)),
];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 2);
assert_eq!(output[0].value, Value::Int(20));
assert_eq!(output[1].value, Value::Int(30));
}
#[test]
fn filters_integer_values_with_inside() {
let graph = create_empty_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = IsStep::new(p::inside(20i64, 40i64));
let input: Vec<Traverser> = vec![
create_value_traverser(Value::Int(10)),
create_value_traverser(Value::Int(20)), create_value_traverser(Value::Int(30)),
create_value_traverser(Value::Int(40)), create_value_traverser(Value::Int(50)),
];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 1);
assert_eq!(output[0].value, Value::Int(30));
}
#[test]
fn filters_integer_values_with_outside() {
let graph = create_empty_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = IsStep::new(p::outside(20i64, 40i64));
let input: Vec<Traverser> = vec![
create_value_traverser(Value::Int(10)),
create_value_traverser(Value::Int(20)),
create_value_traverser(Value::Int(30)),
create_value_traverser(Value::Int(40)),
create_value_traverser(Value::Int(50)),
];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 2);
assert_eq!(output[0].value, Value::Int(10));
assert_eq!(output[1].value, Value::Int(50));
}
#[test]
fn filters_integer_values_with_within() {
let graph = create_empty_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = IsStep::new(p::within(vec![
Value::Int(20),
Value::Int(30),
Value::Int(40),
]));
let input: Vec<Traverser> = vec![
create_value_traverser(Value::Int(10)),
create_value_traverser(Value::Int(20)),
create_value_traverser(Value::Int(25)),
create_value_traverser(Value::Int(30)),
create_value_traverser(Value::Int(50)),
];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 2);
assert_eq!(output[0].value, Value::Int(20));
assert_eq!(output[1].value, Value::Int(30));
}
#[test]
fn filters_integer_values_with_without() {
let graph = create_empty_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = IsStep::new(p::without(vec![Value::Int(20), Value::Int(30)]));
let input: Vec<Traverser> = vec![
create_value_traverser(Value::Int(10)),
create_value_traverser(Value::Int(20)),
create_value_traverser(Value::Int(25)),
create_value_traverser(Value::Int(30)),
create_value_traverser(Value::Int(50)),
];
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(10));
assert_eq!(output[1].value, Value::Int(25));
assert_eq!(output[2].value, Value::Int(50));
}
#[test]
fn filters_float_values_with_gt() {
let graph = create_empty_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = IsStep::new(p::gt(Value::Float(2.5)));
let input: Vec<Traverser> = vec![
create_value_traverser(Value::Float(1.0)),
create_value_traverser(Value::Float(2.5)),
create_value_traverser(Value::Float(3.0)),
create_value_traverser(Value::Float(4.5)),
];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 2);
assert_eq!(output[0].value, Value::Float(3.0));
assert_eq!(output[1].value, Value::Float(4.5));
}
#[test]
fn filters_string_values_with_eq() {
let graph = create_empty_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = IsStep::eq("alice");
let input: Vec<Traverser> = vec![
create_value_traverser(Value::String("alice".to_string())),
create_value_traverser(Value::String("bob".to_string())),
create_value_traverser(Value::String("alice".to_string())),
];
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("alice".to_string()));
assert_eq!(output[1].value, Value::String("alice".to_string()));
}
#[test]
fn filters_string_values_with_containing() {
let graph = create_empty_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = IsStep::new(p::containing("lic"));
let input: Vec<Traverser> = vec![
create_value_traverser(Value::String("alice".to_string())),
create_value_traverser(Value::String("bob".to_string())),
create_value_traverser(Value::String("malice".to_string())),
];
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("alice".to_string()));
assert_eq!(output[1].value, Value::String("malice".to_string()));
}
#[test]
fn filters_string_values_with_starting_with() {
let graph = create_empty_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = IsStep::new(p::starting_with("al"));
let input: Vec<Traverser> = vec![
create_value_traverser(Value::String("alice".to_string())),
create_value_traverser(Value::String("bob".to_string())),
create_value_traverser(Value::String("alex".to_string())),
];
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("alice".to_string()));
assert_eq!(output[1].value, Value::String("alex".to_string()));
}
#[test]
fn filters_string_values_with_ending_with() {
let graph = create_empty_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = IsStep::new(p::ending_with("ce"));
let input: Vec<Traverser> = vec![
create_value_traverser(Value::String("alice".to_string())),
create_value_traverser(Value::String("bob".to_string())),
create_value_traverser(Value::String("grace".to_string())),
];
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("alice".to_string()));
assert_eq!(output[1].value, Value::String("grace".to_string()));
}
#[test]
fn filters_boolean_values_with_eq() {
let graph = create_empty_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = IsStep::eq(true);
let input: Vec<Traverser> = vec![
create_value_traverser(Value::Bool(true)),
create_value_traverser(Value::Bool(false)),
create_value_traverser(Value::Bool(true)),
];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 2);
assert_eq!(output[0].value, Value::Bool(true));
assert_eq!(output[1].value, Value::Bool(true));
}
#[test]
fn filters_with_and_predicate() {
let graph = create_empty_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = IsStep::new(p::and(p::gte(20i64), p::lt(30i64)));
let input: Vec<Traverser> = vec![
create_value_traverser(Value::Int(10)),
create_value_traverser(Value::Int(20)),
create_value_traverser(Value::Int(25)),
create_value_traverser(Value::Int(30)),
create_value_traverser(Value::Int(40)),
];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 2);
assert_eq!(output[0].value, Value::Int(20));
assert_eq!(output[1].value, Value::Int(25));
}
#[test]
fn filters_with_or_predicate() {
let graph = create_empty_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = IsStep::new(p::or(p::lt(15i64), p::gt(35i64)));
let input: Vec<Traverser> = vec![
create_value_traverser(Value::Int(10)),
create_value_traverser(Value::Int(20)),
create_value_traverser(Value::Int(30)),
create_value_traverser(Value::Int(40)),
];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 2);
assert_eq!(output[0].value, Value::Int(10));
assert_eq!(output[1].value, Value::Int(40));
}
#[test]
fn filters_with_not_predicate() {
let graph = create_empty_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = IsStep::new(p::not(p::eq(25i64)));
let input: Vec<Traverser> = vec![
create_value_traverser(Value::Int(20)),
create_value_traverser(Value::Int(25)),
create_value_traverser(Value::Int(30)),
];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 2);
assert_eq!(output[0].value, Value::Int(20));
assert_eq!(output[1].value, Value::Int(30));
}
#[test]
fn empty_input_returns_empty_output() {
let graph = create_empty_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = IsStep::eq(29i64);
let input: Vec<Traverser> = vec![];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert!(output.is_empty());
}
#[test]
fn no_matches_returns_empty_output() {
let graph = create_empty_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = IsStep::eq(100i64);
let input: Vec<Traverser> = vec![
create_value_traverser(Value::Int(20)),
create_value_traverser(Value::Int(30)),
create_value_traverser(Value::Int(40)),
];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert!(output.is_empty());
}
#[test]
fn preserves_traverser_metadata() {
let graph = create_empty_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = IsStep::eq(29i64);
let mut traverser = Traverser::new(Value::Int(29));
traverser.extend_path_labeled("start");
traverser.loops = 5;
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!(output[0].path.has_label("start"));
assert_eq!(output[0].loops, 5);
assert_eq!(output[0].bulk, 10);
}
#[test]
fn debug_format() {
let step = IsStep::eq(29i64);
let debug_str = format!("{:?}", step);
assert!(debug_str.contains("IsStep"));
assert!(debug_str.contains("<predicate>"));
}
#[test]
fn clone_works() {
let graph = create_empty_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = IsStep::eq(29i64);
let cloned = step.clone();
let input: Vec<Traverser> = vec![
create_value_traverser(Value::Int(29)),
create_value_traverser(Value::Int(30)),
];
let output1: Vec<Traverser> = step
.apply(&ctx, Box::new(input.clone().into_iter()))
.collect();
let output2: Vec<Traverser> = cloned.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output1.len(), 1);
assert_eq!(output2.len(), 1);
assert_eq!(output1[0].value, output2[0].value);
}
#[test]
fn type_mismatch_does_not_match() {
let graph = create_empty_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = IsStep::eq(29i64);
let input: Vec<Traverser> = vec![
create_value_traverser(Value::String("29".to_string())),
create_value_traverser(Value::Float(29.0)),
];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert!(output.is_empty());
}
}
mod simple_path_step_tests {
use super::*;
use crate::traversal::step::DynStep;
fn create_empty_graph() -> Graph {
Graph::in_memory()
}
fn create_traverser_with_path(values: Vec<Value>) -> Traverser {
let final_value = values.last().cloned().unwrap_or(Value::Null);
let mut traverser = Traverser::new(final_value);
for value in values {
traverser.value = value;
traverser.extend_path_unlabeled();
}
traverser
}
#[test]
fn new_creates_step() {
let step = SimplePathStep::new();
assert_eq!(step.name(), "simplePath");
}
#[test]
fn default_creates_step() {
let step = SimplePathStep;
assert_eq!(step.name(), "simplePath");
}
#[test]
fn clone_works() {
let step = SimplePathStep::new();
let cloned = step;
assert_eq!(step.name(), cloned.name());
}
#[test]
fn clone_box_works() {
let step = SimplePathStep::new();
let cloned = DynStep::clone_box(&step);
assert_eq!(cloned.dyn_name(), "simplePath");
}
#[test]
fn simple_linear_path_passes() {
let graph = create_empty_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = SimplePathStep::new();
let traverser = create_traverser_with_path(vec![
Value::Vertex(VertexId(0)),
Value::Vertex(VertexId(1)),
Value::Vertex(VertexId(2)),
Value::Vertex(VertexId(3)),
]);
let input = vec![traverser];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 1);
}
#[test]
fn cyclic_path_filtered_out() {
let graph = create_empty_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = SimplePathStep::new();
let traverser = create_traverser_with_path(vec![
Value::Vertex(VertexId(0)),
Value::Vertex(VertexId(1)),
Value::Vertex(VertexId(2)),
Value::Vertex(VertexId(0)), ]);
let input = vec![traverser];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert!(output.is_empty());
}
#[test]
fn single_element_path_is_simple() {
let graph = create_empty_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = SimplePathStep::new();
let traverser = create_traverser_with_path(vec![Value::Vertex(VertexId(0))]);
let input = vec![traverser];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 1);
}
#[test]
fn empty_path_is_simple() {
let graph = create_empty_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = SimplePathStep::new();
let traverser = Traverser::new(Value::Vertex(VertexId(0)));
let input = vec![traverser];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 1);
}
#[test]
fn consecutive_duplicates_filtered_out() {
let graph = create_empty_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = SimplePathStep::new();
let traverser = create_traverser_with_path(vec![
Value::Vertex(VertexId(0)),
Value::Vertex(VertexId(0)), ]);
let input = vec![traverser];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert!(output.is_empty());
}
#[test]
fn mixed_simple_and_cyclic_paths() {
let graph = create_empty_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = SimplePathStep::new();
let simple_traverser = create_traverser_with_path(vec![
Value::Vertex(VertexId(0)),
Value::Vertex(VertexId(1)),
Value::Vertex(VertexId(2)),
]);
let cyclic_traverser = create_traverser_with_path(vec![
Value::Vertex(VertexId(3)),
Value::Vertex(VertexId(4)),
Value::Vertex(VertexId(3)),
]);
let input = vec![simple_traverser, cyclic_traverser];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 1);
assert_eq!(output[0].value, Value::Vertex(VertexId(2)));
}
#[test]
fn edges_in_path_checked_for_duplicates() {
let graph = create_empty_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = SimplePathStep::new();
let traverser = create_traverser_with_path(vec![
Value::Vertex(VertexId(0)),
Value::Edge(EdgeId(0)),
Value::Vertex(VertexId(1)),
Value::Edge(EdgeId(0)), Value::Vertex(VertexId(2)),
]);
let input = vec![traverser];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert!(output.is_empty());
}
#[test]
fn properties_in_path_checked_for_duplicates() {
let graph = create_empty_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = SimplePathStep::new();
let traverser = create_traverser_with_path(vec![
Value::String("Alice".to_string()),
Value::String("Bob".to_string()),
Value::String("Alice".to_string()), ]);
let input = vec![traverser];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert!(output.is_empty());
}
#[test]
fn preserves_traverser_metadata() {
let graph = create_empty_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = SimplePathStep::new();
let mut traverser = Traverser::new(Value::Vertex(VertexId(0)));
traverser.extend_path_unlabeled();
traverser.value = Value::Vertex(VertexId(1));
traverser.extend_path_labeled("end");
traverser.loops = 5;
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!(output[0].path.has_label("end"));
assert_eq!(output[0].loops, 5);
assert_eq!(output[0].bulk, 10);
}
}
mod cyclic_path_step_tests {
use super::*;
use crate::traversal::step::DynStep;
fn create_empty_graph() -> Graph {
Graph::in_memory()
}
fn create_traverser_with_path(values: Vec<Value>) -> Traverser {
let final_value = values.last().cloned().unwrap_or(Value::Null);
let mut traverser = Traverser::new(final_value);
for value in values {
traverser.value = value;
traverser.extend_path_unlabeled();
}
traverser
}
#[test]
fn new_creates_step() {
let step = CyclicPathStep::new();
assert_eq!(step.name(), "cyclicPath");
}
#[test]
fn default_creates_step() {
let step = CyclicPathStep;
assert_eq!(step.name(), "cyclicPath");
}
#[test]
fn clone_works() {
let step = CyclicPathStep::new();
let cloned = step;
assert_eq!(step.name(), cloned.name());
}
#[test]
fn clone_box_works() {
let step = CyclicPathStep::new();
let cloned = DynStep::clone_box(&step);
assert_eq!(cloned.dyn_name(), "cyclicPath");
}
#[test]
fn cyclic_path_passes() {
let graph = create_empty_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = CyclicPathStep::new();
let traverser = create_traverser_with_path(vec![
Value::Vertex(VertexId(0)),
Value::Vertex(VertexId(1)),
Value::Vertex(VertexId(2)),
Value::Vertex(VertexId(0)), ]);
let input = vec![traverser];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 1);
}
#[test]
fn simple_path_filtered_out() {
let graph = create_empty_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = CyclicPathStep::new();
let traverser = create_traverser_with_path(vec![
Value::Vertex(VertexId(0)),
Value::Vertex(VertexId(1)),
Value::Vertex(VertexId(2)),
Value::Vertex(VertexId(3)),
]);
let input = vec![traverser];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert!(output.is_empty());
}
#[test]
fn single_element_path_is_not_cyclic() {
let graph = create_empty_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = CyclicPathStep::new();
let traverser = create_traverser_with_path(vec![Value::Vertex(VertexId(0))]);
let input = vec![traverser];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert!(output.is_empty());
}
#[test]
fn empty_path_is_not_cyclic() {
let graph = create_empty_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = CyclicPathStep::new();
let traverser = Traverser::new(Value::Vertex(VertexId(0)));
let input = vec![traverser];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert!(output.is_empty());
}
#[test]
fn consecutive_duplicates_pass() {
let graph = create_empty_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = CyclicPathStep::new();
let traverser = create_traverser_with_path(vec![
Value::Vertex(VertexId(0)),
Value::Vertex(VertexId(0)), ]);
let input = vec![traverser];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 1);
}
#[test]
fn mixed_simple_and_cyclic_paths() {
let graph = create_empty_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = CyclicPathStep::new();
let simple_traverser = create_traverser_with_path(vec![
Value::Vertex(VertexId(0)),
Value::Vertex(VertexId(1)),
Value::Vertex(VertexId(2)),
]);
let cyclic_traverser = create_traverser_with_path(vec![
Value::Vertex(VertexId(3)),
Value::Vertex(VertexId(4)),
Value::Vertex(VertexId(3)),
]);
let input = vec![simple_traverser, cyclic_traverser];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 1);
assert_eq!(output[0].value, Value::Vertex(VertexId(3)));
}
#[test]
fn is_inverse_of_simple_path() {
let graph = create_empty_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let simple_step = SimplePathStep::new();
let cyclic_step = CyclicPathStep::new();
let paths = vec![
create_traverser_with_path(vec![
Value::Vertex(VertexId(0)),
Value::Vertex(VertexId(1)),
]),
create_traverser_with_path(vec![
Value::Vertex(VertexId(2)),
Value::Vertex(VertexId(2)),
]),
];
let simple_output: Vec<Traverser> = simple_step
.apply(&ctx, Box::new(paths.clone().into_iter()))
.collect();
let cyclic_output: Vec<Traverser> = cyclic_step
.apply(&ctx, Box::new(paths.into_iter()))
.collect();
assert_eq!(simple_output.len() + cyclic_output.len(), 2);
assert_eq!(simple_output.len(), 1);
assert_eq!(simple_output[0].value, Value::Vertex(VertexId(1)));
assert_eq!(cyclic_output.len(), 1);
assert_eq!(cyclic_output[0].value, Value::Vertex(VertexId(2)));
}
#[test]
fn preserves_traverser_metadata() {
let graph = create_empty_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = CyclicPathStep::new();
let mut traverser = Traverser::new(Value::Vertex(VertexId(0)));
traverser.extend_path_unlabeled();
traverser.extend_path_labeled("end");
traverser.loops = 5;
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!(output[0].path.has_label("end"));
assert_eq!(output[0].loops, 5);
assert_eq!(output[0].bulk, 10);
}
}
mod where_p_step_tests {
use super::*;
use crate::traversal::predicate::p;
use crate::traversal::step::DynStep;
#[test]
fn new_creates_where_p_step() {
let step = WherePStep::new(p::gt(25));
assert_eq!(step.name(), "where");
}
#[test]
fn clone_box_works() {
let step = WherePStep::new(p::gt(25));
let cloned = DynStep::clone_box(&step);
assert_eq!(cloned.dyn_name(), "where");
}
#[test]
fn filters_with_gt_predicate() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = WherePStep::new(p::gt(25));
let input: Vec<Traverser> = vec![
Traverser::new(Value::Int(30)),
Traverser::new(Value::Int(20)),
Traverser::new(Value::Int(25)),
Traverser::new(Value::Int(40)),
];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 2);
assert_eq!(output[0].value, Value::Int(30));
assert_eq!(output[1].value, Value::Int(40));
}
#[test]
fn filters_with_lt_predicate() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = WherePStep::new(p::lt(25));
let input: Vec<Traverser> = vec![
Traverser::new(Value::Int(30)),
Traverser::new(Value::Int(20)),
Traverser::new(Value::Int(15)),
Traverser::new(Value::Int(25)),
];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 2);
assert_eq!(output[0].value, Value::Int(20));
assert_eq!(output[1].value, Value::Int(15));
}
#[test]
fn filters_with_eq_predicate() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = WherePStep::new(p::eq(42));
let input: Vec<Traverser> = vec![
Traverser::new(Value::Int(42)),
Traverser::new(Value::Int(41)),
Traverser::new(Value::Int(43)),
Traverser::new(Value::Int(42)),
];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 2);
assert_eq!(output[0].value, Value::Int(42));
assert_eq!(output[1].value, Value::Int(42));
}
#[test]
fn filters_with_neq_predicate() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = WherePStep::new(p::neq(42));
let input: Vec<Traverser> = vec![
Traverser::new(Value::Int(42)),
Traverser::new(Value::Int(41)),
Traverser::new(Value::Int(43)),
];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 2);
assert_eq!(output[0].value, Value::Int(41));
assert_eq!(output[1].value, Value::Int(43));
}
#[test]
fn filters_with_gte_predicate() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = WherePStep::new(p::gte(25));
let input: Vec<Traverser> = vec![
Traverser::new(Value::Int(30)),
Traverser::new(Value::Int(20)),
Traverser::new(Value::Int(25)),
];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 2);
assert_eq!(output[0].value, Value::Int(30));
assert_eq!(output[1].value, Value::Int(25));
}
#[test]
fn filters_with_lte_predicate() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = WherePStep::new(p::lte(25));
let input: Vec<Traverser> = vec![
Traverser::new(Value::Int(30)),
Traverser::new(Value::Int(20)),
Traverser::new(Value::Int(25)),
];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 2);
assert_eq!(output[0].value, Value::Int(20));
assert_eq!(output[1].value, Value::Int(25));
}
#[test]
fn filters_with_within_predicate() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = WherePStep::new(p::within([1, 2, 3]));
let input: Vec<Traverser> = vec![
Traverser::new(Value::Int(1)),
Traverser::new(Value::Int(4)),
Traverser::new(Value::Int(2)),
Traverser::new(Value::Int(5)),
Traverser::new(Value::Int(3)),
];
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(1));
assert_eq!(output[1].value, Value::Int(2));
assert_eq!(output[2].value, Value::Int(3));
}
#[test]
fn filters_with_without_predicate() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = WherePStep::new(p::without([1, 2, 3]));
let input: Vec<Traverser> = vec![
Traverser::new(Value::Int(1)),
Traverser::new(Value::Int(4)),
Traverser::new(Value::Int(2)),
Traverser::new(Value::Int(5)),
Traverser::new(Value::Int(3)),
];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 2);
assert_eq!(output[0].value, Value::Int(4));
assert_eq!(output[1].value, Value::Int(5));
}
#[test]
fn filters_with_between_predicate() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = WherePStep::new(p::between(10, 20));
let input: Vec<Traverser> = vec![
Traverser::new(Value::Int(5)),
Traverser::new(Value::Int(10)),
Traverser::new(Value::Int(15)),
Traverser::new(Value::Int(20)),
Traverser::new(Value::Int(25)),
];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 2);
assert_eq!(output[0].value, Value::Int(10));
assert_eq!(output[1].value, Value::Int(15));
}
#[test]
fn filters_with_and_predicate() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = WherePStep::new(p::and(p::gt(10), p::lt(30)));
let input: Vec<Traverser> = vec![
Traverser::new(Value::Int(5)),
Traverser::new(Value::Int(15)),
Traverser::new(Value::Int(25)),
Traverser::new(Value::Int(35)),
];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 2);
assert_eq!(output[0].value, Value::Int(15));
assert_eq!(output[1].value, Value::Int(25));
}
#[test]
fn filters_with_or_predicate() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = WherePStep::new(p::or(p::lt(10), p::gt(30)));
let input: Vec<Traverser> = vec![
Traverser::new(Value::Int(5)),
Traverser::new(Value::Int(15)),
Traverser::new(Value::Int(25)),
Traverser::new(Value::Int(35)),
];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 2);
assert_eq!(output[0].value, Value::Int(5));
assert_eq!(output[1].value, Value::Int(35));
}
#[test]
fn empty_input_returns_empty() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = WherePStep::new(p::gt(25));
let input: Vec<Traverser> = vec![];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert!(output.is_empty());
}
#[test]
fn preserves_traverser_metadata() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = WherePStep::new(p::gt(25));
let mut traverser = Traverser::new(Value::Int(30));
traverser.extend_path_labeled("start");
traverser.loops = 5;
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!(output[0].path.has_label("start"));
assert_eq!(output[0].loops, 5);
assert_eq!(output[0].bulk, 10);
}
#[test]
fn works_with_string_values() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = WherePStep::new(p::within(["Alice", "Bob"]));
let input: Vec<Traverser> = vec![
Traverser::new(Value::String("Alice".to_string())),
Traverser::new(Value::String("Carol".to_string())),
Traverser::new(Value::String("Bob".to_string())),
Traverser::new(Value::String("Dave".to_string())),
];
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("Alice".to_string()));
assert_eq!(output[1].value, Value::String("Bob".to_string()));
}
#[test]
fn works_with_float_values() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = WherePStep::new(p::gt(2.5));
let input: Vec<Traverser> = vec![
Traverser::new(Value::Float(1.5)),
Traverser::new(Value::Float(2.5)),
Traverser::new(Value::Float(3.5)),
Traverser::new(Value::Float(4.5)),
];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 2);
assert_eq!(output[0].value, Value::Float(3.5));
assert_eq!(output[1].value, Value::Float(4.5));
}
#[test]
fn debug_format() {
let step = WherePStep::new(p::gt(25));
let debug_str = format!("{:?}", step);
assert!(debug_str.contains("WherePStep"));
assert!(debug_str.contains("predicate"));
}
#[test]
fn filters_all_when_none_match() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = WherePStep::new(p::gt(100));
let input: Vec<Traverser> = vec![
Traverser::new(Value::Int(10)),
Traverser::new(Value::Int(20)),
Traverser::new(Value::Int(30)),
];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert!(output.is_empty());
}
#[test]
fn passes_all_when_all_match() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = WherePStep::new(p::gt(0));
let input: Vec<Traverser> = vec![
Traverser::new(Value::Int(10)),
Traverser::new(Value::Int(20)),
Traverser::new(Value::Int(30)),
];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 3);
}
}
mod streaming_tests {
use super::*;
use crate::storage::GraphStorage;
use crate::traversal::context::StreamingContext;
use crate::traversal::step::Step;
use crate::traversal::SnapshotLike;
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.add_vertex("person", {
let mut props = HashMap::new();
props.insert("name".to_string(), Value::String("Bob".to_string()));
props.insert("age".to_string(), Value::Int(25));
props
});
graph.add_vertex("software", {
let mut props = HashMap::new();
props.insert("name".to_string(), Value::String("Graph DB".to_string()));
props
});
graph
}
#[test]
fn limit_step_streaming_limits_traversers() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = StreamingContext::new(snapshot.arc_streamable(), snapshot.arc_interner());
let step = LimitStep::new(2);
let mut outputs = vec![];
for i in 0..5 {
let result: Vec<_> =
Step::apply_streaming(&step, ctx.clone(), Traverser::new(Value::Int(i)))
.collect();
outputs.extend(result);
}
assert_eq!(outputs.len(), 2);
assert_eq!(outputs[0].value, Value::Int(0));
assert_eq!(outputs[1].value, Value::Int(1));
}
#[test]
fn limit_step_streaming_shares_counter_across_clones() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = StreamingContext::new(snapshot.arc_streamable(), snapshot.arc_interner());
let step = LimitStep::new(3);
let step_clone = step.clone();
let _: Vec<_> =
Step::apply_streaming(&step, ctx.clone(), Traverser::new(Value::Int(1))).collect();
let _: Vec<_> =
Step::apply_streaming(&step, ctx.clone(), Traverser::new(Value::Int(2))).collect();
let result1: Vec<_> =
Step::apply_streaming(&step_clone, ctx.clone(), Traverser::new(Value::Int(3)))
.collect();
let result2: Vec<_> =
Step::apply_streaming(&step_clone, ctx.clone(), Traverser::new(Value::Int(4)))
.collect();
assert_eq!(result1.len(), 1); assert_eq!(result2.len(), 0); }
#[test]
fn limit_step_streaming_matches_eager() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let eager_ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = LimitStep::new(2);
let input = vec![
Traverser::new(Value::Int(1)),
Traverser::new(Value::Int(2)),
Traverser::new(Value::Int(3)),
];
let eager_output: Vec<_> =
Step::apply(&step, &eager_ctx, Box::new(input.into_iter())).collect();
let streaming_ctx =
StreamingContext::new(snapshot.arc_streamable(), snapshot.arc_interner());
let step = LimitStep::new(2);
let mut streaming_output = vec![];
for i in 1..=3 {
let result: Vec<_> = Step::apply_streaming(
&step,
streaming_ctx.clone(),
Traverser::new(Value::Int(i)),
)
.collect();
streaming_output.extend(result);
}
assert_eq!(eager_output.len(), streaming_output.len());
for (e, s) in eager_output.iter().zip(streaming_output.iter()) {
assert_eq!(e.value, s.value);
}
}
#[test]
fn skip_step_streaming_skips_traversers() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = StreamingContext::new(snapshot.arc_streamable(), snapshot.arc_interner());
let step = SkipStep::new(2);
let mut outputs = vec![];
for i in 0..5 {
let result: Vec<_> =
Step::apply_streaming(&step, ctx.clone(), Traverser::new(Value::Int(i)))
.collect();
outputs.extend(result);
}
assert_eq!(outputs.len(), 3);
assert_eq!(outputs[0].value, Value::Int(2));
assert_eq!(outputs[1].value, Value::Int(3));
assert_eq!(outputs[2].value, Value::Int(4));
}
#[test]
fn skip_step_streaming_shares_counter_across_clones() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = StreamingContext::new(snapshot.arc_streamable(), snapshot.arc_interner());
let step = SkipStep::new(2);
let step_clone = step.clone();
let result1: Vec<_> =
Step::apply_streaming(&step, ctx.clone(), Traverser::new(Value::Int(1))).collect();
assert_eq!(result1.len(), 0);
let result2: Vec<_> =
Step::apply_streaming(&step_clone, ctx.clone(), Traverser::new(Value::Int(2)))
.collect();
assert_eq!(result2.len(), 0);
let result3: Vec<_> =
Step::apply_streaming(&step, ctx.clone(), Traverser::new(Value::Int(3))).collect();
assert_eq!(result3.len(), 1);
assert_eq!(result3[0].value, Value::Int(3));
}
#[test]
fn range_step_streaming_applies_range() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = StreamingContext::new(snapshot.arc_streamable(), snapshot.arc_interner());
let step = RangeStep::new(1, 4);
let mut outputs = vec![];
for i in 0..6 {
let result: Vec<_> =
Step::apply_streaming(&step, ctx.clone(), Traverser::new(Value::Int(i)))
.collect();
outputs.extend(result);
}
assert_eq!(outputs.len(), 3);
assert_eq!(outputs[0].value, Value::Int(1));
assert_eq!(outputs[1].value, Value::Int(2));
assert_eq!(outputs[2].value, Value::Int(3));
}
#[test]
fn range_step_streaming_shares_counter_across_clones() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = StreamingContext::new(snapshot.arc_streamable(), snapshot.arc_interner());
let step = RangeStep::new(1, 3); let step_clone = step.clone();
let r1: Vec<_> =
Step::apply_streaming(&step, ctx.clone(), Traverser::new(Value::Int(0))).collect();
assert_eq!(r1.len(), 0);
let r2: Vec<_> =
Step::apply_streaming(&step_clone, ctx.clone(), Traverser::new(Value::Int(1)))
.collect();
assert_eq!(r2.len(), 1);
let r3: Vec<_> =
Step::apply_streaming(&step, ctx.clone(), Traverser::new(Value::Int(2))).collect();
assert_eq!(r3.len(), 1);
let r4: Vec<_> =
Step::apply_streaming(&step_clone, ctx.clone(), Traverser::new(Value::Int(3)))
.collect();
assert_eq!(r4.len(), 0);
}
#[test]
fn dedup_step_streaming_removes_duplicates() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = StreamingContext::new(snapshot.arc_streamable(), snapshot.arc_interner());
let step = DedupStep::new();
let inputs = vec![
Value::Int(1),
Value::Int(2),
Value::Int(1), Value::Int(3),
Value::Int(2), ];
let mut outputs = vec![];
for v in inputs {
let result: Vec<_> =
Step::apply_streaming(&step, ctx.clone(), Traverser::new(v)).collect();
outputs.extend(result);
}
assert_eq!(outputs.len(), 3);
assert_eq!(outputs[0].value, Value::Int(1));
assert_eq!(outputs[1].value, Value::Int(2));
assert_eq!(outputs[2].value, Value::Int(3));
}
#[test]
fn dedup_step_streaming_shares_hashset_across_clones() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = StreamingContext::new(snapshot.arc_streamable(), snapshot.arc_interner());
let step = DedupStep::new();
let step_clone = step.clone();
let r1: Vec<_> =
Step::apply_streaming(&step, ctx.clone(), Traverser::new(Value::Int(1))).collect();
assert_eq!(r1.len(), 1);
let r2: Vec<_> =
Step::apply_streaming(&step_clone, ctx.clone(), Traverser::new(Value::Int(1)))
.collect();
assert_eq!(r2.len(), 0);
let r3: Vec<_> =
Step::apply_streaming(&step_clone, ctx.clone(), Traverser::new(Value::Int(2)))
.collect();
assert_eq!(r3.len(), 1);
}
#[test]
fn dedup_step_streaming_matches_eager() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let inputs = vec![Value::Int(1), Value::Int(2), Value::Int(1), Value::Int(3)];
let eager_ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = DedupStep::new();
let input: Vec<_> = inputs.iter().map(|v| Traverser::new(v.clone())).collect();
let eager_output: Vec<_> =
Step::apply(&step, &eager_ctx, Box::new(input.into_iter())).collect();
let streaming_ctx =
StreamingContext::new(snapshot.arc_streamable(), snapshot.arc_interner());
let step = DedupStep::new();
let mut streaming_output = vec![];
for v in inputs {
let result: Vec<_> =
Step::apply_streaming(&step, streaming_ctx.clone(), Traverser::new(v))
.collect();
streaming_output.extend(result);
}
assert_eq!(eager_output.len(), streaming_output.len());
for (e, s) in eager_output.iter().zip(streaming_output.iter()) {
assert_eq!(e.value, s.value);
}
}
#[test]
fn dedup_by_key_step_streaming_removes_duplicates() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = StreamingContext::new(snapshot.arc_streamable(), snapshot.arc_interner());
let step = DedupByKeyStep::new("name");
let vertices: Vec<_> = snapshot.all_vertices().collect();
let mut outputs = vec![];
for v in &vertices {
let result: Vec<_> =
Step::apply_streaming(&step, ctx.clone(), Traverser::from_vertex(v.id))
.collect();
outputs.extend(result);
}
assert_eq!(outputs.len(), vertices.len());
}
#[test]
fn dedup_by_key_step_streaming_shares_hashset() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = StreamingContext::new(snapshot.arc_streamable(), snapshot.arc_interner());
let step = DedupByKeyStep::new("type");
let map1 = {
let mut m = crate::value::ValueMap::new();
m.insert("type".to_string(), Value::String("A".to_string()));
m.insert("id".to_string(), Value::Int(1));
Value::Map(m)
};
let map2 = {
let mut m = crate::value::ValueMap::new();
m.insert("type".to_string(), Value::String("A".to_string())); m.insert("id".to_string(), Value::Int(2));
Value::Map(m)
};
let step_clone = step.clone();
let r1: Vec<_> =
Step::apply_streaming(&step, ctx.clone(), Traverser::new(map1)).collect();
assert_eq!(r1.len(), 1);
let r2: Vec<_> =
Step::apply_streaming(&step_clone, ctx.clone(), Traverser::new(map2)).collect();
assert_eq!(r2.len(), 0);
}
#[test]
fn dedup_by_label_step_streaming_removes_duplicates() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = StreamingContext::new(snapshot.arc_streamable(), snapshot.arc_interner());
let step = DedupByLabelStep::new();
let vertices: Vec<_> = snapshot.all_vertices().collect();
let mut outputs = vec![];
for v in &vertices {
let result: Vec<_> =
Step::apply_streaming(&step, ctx.clone(), Traverser::from_vertex(v.id))
.collect();
outputs.extend(result);
}
assert_eq!(outputs.len(), 2);
}
#[test]
fn dedup_by_label_step_streaming_shares_hashset() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = StreamingContext::new(snapshot.arc_streamable(), snapshot.arc_interner());
let step = DedupByLabelStep::new();
let step_clone = step.clone();
let person_vertices: Vec<_> = snapshot
.all_vertices()
.filter(|v| v.label == "person")
.collect();
assert!(
person_vertices.len() >= 2,
"Need at least 2 person vertices"
);
let r1: Vec<_> = Step::apply_streaming(
&step,
ctx.clone(),
Traverser::from_vertex(person_vertices[0].id),
)
.collect();
assert_eq!(r1.len(), 1);
let r2: Vec<_> = Step::apply_streaming(
&step_clone,
ctx.clone(),
Traverser::from_vertex(person_vertices[1].id),
)
.collect();
assert_eq!(r2.len(), 0);
}
#[test]
fn dedup_by_traversal_step_streaming_removes_duplicates() {
use crate::traversal::IdentityStep;
use crate::traversal::Traversal;
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = StreamingContext::new(snapshot.arc_streamable(), snapshot.arc_interner());
let sub = Traversal::<Value, Value>::new().add_step(IdentityStep);
let step = DedupByTraversalStep::new(sub);
let inputs = vec![
Value::Int(1),
Value::Int(2),
Value::Int(1), Value::Int(3),
];
let mut outputs = vec![];
for v in inputs {
let result: Vec<_> =
Step::apply_streaming(&step, ctx.clone(), Traverser::new(v)).collect();
outputs.extend(result);
}
assert_eq!(outputs.len(), 3);
}
#[test]
fn dedup_by_traversal_step_streaming_shares_hashset() {
use crate::traversal::IdentityStep;
use crate::traversal::Traversal;
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = StreamingContext::new(snapshot.arc_streamable(), snapshot.arc_interner());
let sub = Traversal::<Value, Value>::new().add_step(IdentityStep);
let step = DedupByTraversalStep::new(sub);
let step_clone = step.clone();
let r1: Vec<_> =
Step::apply_streaming(&step, ctx.clone(), Traverser::new(Value::Int(1))).collect();
assert_eq!(r1.len(), 1);
let r2: Vec<_> =
Step::apply_streaming(&step_clone, ctx.clone(), Traverser::new(Value::Int(1)))
.collect();
assert_eq!(r2.len(), 0);
let r3: Vec<_> =
Step::apply_streaming(&step_clone, ctx.clone(), Traverser::new(Value::Int(2)))
.collect();
assert_eq!(r3.len(), 1);
}
#[test]
fn dedup_by_traversal_step_streaming_matches_eager() {
use crate::traversal::IdentityStep;
use crate::traversal::Traversal;
let graph = create_test_graph();
let snapshot = graph.snapshot();
let inputs = vec![Value::Int(1), Value::Int(2), Value::Int(1), Value::Int(3)];
let eager_ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let sub = Traversal::<Value, Value>::new().add_step(IdentityStep);
let step = DedupByTraversalStep::new(sub);
let input: Vec<_> = inputs.iter().map(|v| Traverser::new(v.clone())).collect();
let eager_output: Vec<_> =
Step::apply(&step, &eager_ctx, Box::new(input.into_iter())).collect();
let streaming_ctx =
StreamingContext::new(snapshot.arc_streamable(), snapshot.arc_interner());
let sub = Traversal::<Value, Value>::new().add_step(IdentityStep);
let step = DedupByTraversalStep::new(sub);
let mut streaming_output = vec![];
for v in inputs {
let result: Vec<_> =
Step::apply_streaming(&step, streaming_ctx.clone(), Traverser::new(v))
.collect();
streaming_output.extend(result);
}
assert_eq!(eager_output.len(), streaming_output.len());
for (e, s) in eager_output.iter().zip(streaming_output.iter()) {
assert_eq!(e.value, s.value);
}
}
}
}