use crate::traversal::{ExecutionContext, Traverser};
use crate::value::{IntoValueMap, Value};
#[derive(Clone, Debug)]
pub struct PropertiesStep {
keys: Option<Vec<String>>,
}
impl Default for PropertiesStep {
fn default() -> Self {
Self::new()
}
}
impl PropertiesStep {
pub fn new() -> Self {
Self { keys: None }
}
pub fn with_keys(keys: Vec<String>) -> Self {
Self { keys: Some(keys) }
}
pub fn from_keys<I, S>(keys: I) -> Self
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
Self {
keys: Some(keys.into_iter().map(Into::into).collect()),
}
}
#[inline]
pub(crate) fn make_property_map(key: String, value: Value) -> Value {
let mut map = std::collections::HashMap::new();
map.insert("key".to_string(), Value::String(key));
map.insert("value".to_string(), value);
Value::Map(map.into_value_map())
}
fn expand<'a>(
&self,
ctx: &'a ExecutionContext<'a>,
traverser: Traverser,
) -> impl Iterator<Item = Traverser> + 'a {
let keys = self.keys.clone();
match &traverser.value {
Value::Vertex(id) => {
let props: Vec<Value> = ctx
.storage()
.get_vertex(*id)
.map(|vertex| {
match &keys {
None => {
vertex
.properties
.iter()
.map(|(k, v)| Self::make_property_map(k.clone(), v.clone()))
.collect()
}
Some(key_list) => {
key_list
.iter()
.filter_map(|key| {
vertex.properties.get(key).map(|v| {
Self::make_property_map(key.clone(), v.clone())
})
})
.collect()
}
}
})
.unwrap_or_default();
props
.into_iter()
.map(move |val| traverser.split(val))
.collect::<Vec<_>>()
.into_iter()
}
Value::Edge(id) => {
let props: Vec<Value> = ctx
.storage()
.get_edge(*id)
.map(|edge| {
match &keys {
None => {
edge.properties
.iter()
.map(|(k, v)| Self::make_property_map(k.clone(), v.clone()))
.collect()
}
Some(key_list) => {
key_list
.iter()
.filter_map(|key| {
edge.properties.get(key).map(|v| {
Self::make_property_map(key.clone(), v.clone())
})
})
.collect()
}
}
})
.unwrap_or_default();
props
.into_iter()
.map(move |val| traverser.split(val))
.collect::<Vec<_>>()
.into_iter()
}
_ => {
Vec::new().into_iter()
}
}
}
}
impl crate::traversal::step::Step for PropertiesStep {
type Iter<'a>
= impl Iterator<Item = crate::traversal::Traverser> + 'a
where
Self: 'a;
fn apply<'a>(
&'a self,
ctx: &'a ExecutionContext<'a>,
input: Box<dyn Iterator<Item = Traverser> + 'a>,
) -> Self::Iter<'a> {
input.flat_map(move |traverser| self.expand(ctx, traverser))
}
fn name(&self) -> &'static str {
"properties"
}
fn category(&self) -> crate::traversal::explain::StepCategory {
crate::traversal::explain::StepCategory::Transform
}
fn apply_streaming(
&self,
ctx: crate::traversal::context::StreamingContext,
input: Traverser,
) -> Box<dyn Iterator<Item = Traverser> + Send + 'static> {
let keys = self.keys.clone();
let props: Vec<Value> = match &input.value {
Value::Vertex(id) => ctx
.storage()
.get_vertex(*id)
.map(|vertex| match &keys {
None => vertex
.properties
.iter()
.map(|(k, v)| Self::make_property_map(k.clone(), v.clone()))
.collect(),
Some(key_list) => key_list
.iter()
.filter_map(|key| {
vertex
.properties
.get(key)
.map(|v| Self::make_property_map(key.clone(), v.clone()))
})
.collect(),
})
.unwrap_or_default(),
Value::Edge(id) => ctx
.storage()
.get_edge(*id)
.map(|edge| match &keys {
None => edge
.properties
.iter()
.map(|(k, v)| Self::make_property_map(k.clone(), v.clone()))
.collect(),
Some(key_list) => key_list
.iter()
.filter_map(|key| {
edge.properties
.get(key)
.map(|v| Self::make_property_map(key.clone(), v.clone()))
})
.collect(),
})
.unwrap_or_default(),
_ => Vec::new(),
};
Box::new(props.into_iter().map(move |val| input.split(val)))
}
}
#[derive(Clone, Debug)]
pub struct ValueMapStep {
keys: Option<Vec<String>>,
include_tokens: bool,
}
impl Default for ValueMapStep {
fn default() -> Self {
Self::new()
}
}
impl ValueMapStep {
pub fn new() -> Self {
Self {
keys: None,
include_tokens: false,
}
}
pub fn with_keys(keys: Vec<String>) -> Self {
Self {
keys: Some(keys),
include_tokens: false,
}
}
pub fn from_keys<I, S>(keys: I) -> Self
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
Self {
keys: Some(keys.into_iter().map(Into::into).collect()),
include_tokens: false,
}
}
pub fn with_tokens(mut self) -> Self {
self.include_tokens = true;
self
}
fn transform(&self, ctx: &ExecutionContext, traverser: &Traverser) -> Value {
let mut map = std::collections::HashMap::new();
match &traverser.value {
Value::Vertex(id) => {
if let Some(vertex) = ctx.storage().get_vertex(*id) {
if self.include_tokens {
map.insert("id".to_string(), Value::Int(id.0 as i64));
map.insert("label".to_string(), Value::String(vertex.label.clone()));
}
match &self.keys {
None => {
for (key, value) in &vertex.properties {
map.insert(key.clone(), Value::List(vec![value.clone()]));
}
}
Some(key_list) => {
for key in key_list {
if let Some(value) = vertex.properties.get(key) {
map.insert(key.clone(), Value::List(vec![value.clone()]));
}
}
}
}
}
}
Value::Edge(id) => {
if let Some(edge) = ctx.storage().get_edge(*id) {
if self.include_tokens {
map.insert("id".to_string(), Value::Int(id.0 as i64));
map.insert("label".to_string(), Value::String(edge.label.clone()));
}
match &self.keys {
None => {
for (key, value) in &edge.properties {
map.insert(key.clone(), Value::List(vec![value.clone()]));
}
}
Some(key_list) => {
for key in key_list {
if let Some(value) = edge.properties.get(key) {
map.insert(key.clone(), Value::List(vec![value.clone()]));
}
}
}
}
}
}
_ => {
}
}
Value::Map(map.into_value_map())
}
}
impl crate::traversal::step::Step for ValueMapStep {
type Iter<'a>
= impl Iterator<Item = crate::traversal::Traverser> + 'a
where
Self: 'a;
fn apply<'a>(
&'a self,
ctx: &'a ExecutionContext<'a>,
input: Box<dyn Iterator<Item = Traverser> + 'a>,
) -> Self::Iter<'a> {
input.map(move |t| {
let result = self.transform(ctx, &t);
t.with_value(result)
})
}
fn name(&self) -> &'static str {
"valueMap"
}
fn category(&self) -> crate::traversal::explain::StepCategory {
crate::traversal::explain::StepCategory::Transform
}
fn apply_streaming(
&self,
ctx: crate::traversal::context::StreamingContext,
input: Traverser,
) -> Box<dyn Iterator<Item = Traverser> + Send + 'static> {
let result = self.transform_streaming(&ctx, &input);
Box::new(std::iter::once(input.with_value(result)))
}
}
impl ValueMapStep {
fn transform_streaming(
&self,
ctx: &crate::traversal::context::StreamingContext,
traverser: &Traverser,
) -> Value {
let mut map = std::collections::HashMap::new();
match &traverser.value {
Value::Vertex(id) => {
if let Some(vertex) = ctx.storage().get_vertex(*id) {
if self.include_tokens {
map.insert("id".to_string(), Value::Int(id.0 as i64));
map.insert("label".to_string(), Value::String(vertex.label.clone()));
}
match &self.keys {
None => {
for (key, value) in &vertex.properties {
map.insert(key.clone(), Value::List(vec![value.clone()]));
}
}
Some(key_list) => {
for key in key_list {
if let Some(value) = vertex.properties.get(key) {
map.insert(key.clone(), Value::List(vec![value.clone()]));
}
}
}
}
}
}
Value::Edge(id) => {
if let Some(edge) = ctx.storage().get_edge(*id) {
if self.include_tokens {
map.insert("id".to_string(), Value::Int(id.0 as i64));
map.insert("label".to_string(), Value::String(edge.label.clone()));
}
match &self.keys {
None => {
for (key, value) in &edge.properties {
map.insert(key.clone(), Value::List(vec![value.clone()]));
}
}
Some(key_list) => {
for key in key_list {
if let Some(value) = edge.properties.get(key) {
map.insert(key.clone(), Value::List(vec![value.clone()]));
}
}
}
}
}
}
_ => {}
}
Value::Map(map.into_value_map())
}
}
#[derive(Clone, Debug)]
pub struct ElementMapStep {
keys: Option<Vec<String>>,
}
impl Default for ElementMapStep {
fn default() -> Self {
Self::new()
}
}
impl ElementMapStep {
pub fn new() -> Self {
Self { keys: None }
}
pub fn with_keys(keys: Vec<String>) -> Self {
Self { keys: Some(keys) }
}
pub fn from_keys<I, S>(keys: I) -> Self
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
Self {
keys: Some(keys.into_iter().map(Into::into).collect()),
}
}
fn transform(&self, ctx: &ExecutionContext, traverser: &Traverser) -> Value {
let mut map = std::collections::HashMap::new();
match &traverser.value {
Value::Vertex(id) => {
if let Some(vertex) = ctx.storage().get_vertex(*id) {
map.insert("id".to_string(), Value::Int(id.0 as i64));
map.insert("label".to_string(), Value::String(vertex.label.clone()));
match &self.keys {
None => {
for (key, value) in &vertex.properties {
map.insert(key.clone(), value.clone());
}
}
Some(key_list) => {
for key in key_list {
if let Some(value) = vertex.properties.get(key) {
map.insert(key.clone(), value.clone());
}
}
}
}
}
}
Value::Edge(id) => {
if let Some(edge) = ctx.storage().get_edge(*id) {
map.insert("id".to_string(), Value::Int(id.0 as i64));
map.insert("label".to_string(), Value::String(edge.label.clone()));
let in_ref = self.make_vertex_reference(ctx, edge.dst);
map.insert("IN".to_string(), in_ref);
let out_ref = self.make_vertex_reference(ctx, edge.src);
map.insert("OUT".to_string(), out_ref);
match &self.keys {
None => {
for (key, value) in &edge.properties {
map.insert(key.clone(), value.clone());
}
}
Some(key_list) => {
for key in key_list {
if let Some(value) = edge.properties.get(key) {
map.insert(key.clone(), value.clone());
}
}
}
}
}
}
_ => {
}
}
Value::Map(map.into_value_map())
}
fn make_vertex_reference(&self, ctx: &ExecutionContext, id: crate::value::VertexId) -> Value {
let mut map = std::collections::HashMap::new();
map.insert("id".to_string(), Value::Int(id.0 as i64));
if let Some(vertex) = ctx.storage().get_vertex(id) {
map.insert("label".to_string(), Value::String(vertex.label.clone()));
}
Value::Map(map.into_value_map())
}
}
impl crate::traversal::step::Step for ElementMapStep {
type Iter<'a>
= impl Iterator<Item = crate::traversal::Traverser> + 'a
where
Self: 'a;
fn apply<'a>(
&'a self,
ctx: &'a ExecutionContext<'a>,
input: Box<dyn Iterator<Item = Traverser> + 'a>,
) -> Self::Iter<'a> {
input.map(move |t| {
let result = self.transform(ctx, &t);
t.with_value(result)
})
}
fn name(&self) -> &'static str {
"elementMap"
}
fn category(&self) -> crate::traversal::explain::StepCategory {
crate::traversal::explain::StepCategory::Transform
}
fn apply_streaming(
&self,
ctx: crate::traversal::context::StreamingContext,
input: Traverser,
) -> Box<dyn Iterator<Item = Traverser> + Send + 'static> {
let result = self.transform_streaming(&ctx, &input);
Box::new(std::iter::once(input.with_value(result)))
}
}
impl ElementMapStep {
fn transform_streaming(
&self,
ctx: &crate::traversal::context::StreamingContext,
traverser: &Traverser,
) -> Value {
let mut map = std::collections::HashMap::new();
match &traverser.value {
Value::Vertex(id) => {
if let Some(vertex) = ctx.storage().get_vertex(*id) {
map.insert("id".to_string(), Value::Int(id.0 as i64));
map.insert("label".to_string(), Value::String(vertex.label.clone()));
match &self.keys {
None => {
for (key, value) in &vertex.properties {
map.insert(key.clone(), value.clone());
}
}
Some(key_list) => {
for key in key_list {
if let Some(value) = vertex.properties.get(key) {
map.insert(key.clone(), value.clone());
}
}
}
}
}
}
Value::Edge(id) => {
if let Some(edge) = ctx.storage().get_edge(*id) {
map.insert("id".to_string(), Value::Int(id.0 as i64));
map.insert("label".to_string(), Value::String(edge.label.clone()));
let in_ref = self.make_vertex_reference_streaming(ctx, edge.dst);
map.insert("IN".to_string(), in_ref);
let out_ref = self.make_vertex_reference_streaming(ctx, edge.src);
map.insert("OUT".to_string(), out_ref);
match &self.keys {
None => {
for (key, value) in &edge.properties {
map.insert(key.clone(), value.clone());
}
}
Some(key_list) => {
for key in key_list {
if let Some(value) = edge.properties.get(key) {
map.insert(key.clone(), value.clone());
}
}
}
}
}
}
_ => {}
}
Value::Map(map.into_value_map())
}
fn make_vertex_reference_streaming(
&self,
ctx: &crate::traversal::context::StreamingContext,
id: crate::value::VertexId,
) -> Value {
let mut map = std::collections::HashMap::new();
map.insert("id".to_string(), Value::Int(id.0 as i64));
if let Some(vertex) = ctx.storage().get_vertex(id) {
map.insert("label".to_string(), Value::String(vertex.label.clone()));
}
Value::Map(map.into_value_map())
}
}
#[derive(Clone, Debug)]
pub struct PropertyMapStep {
keys: Option<Vec<String>>,
}
impl Default for PropertyMapStep {
fn default() -> Self {
Self::new()
}
}
impl PropertyMapStep {
pub fn new() -> Self {
Self { keys: None }
}
pub fn with_keys(keys: Vec<String>) -> Self {
Self { keys: Some(keys) }
}
pub fn from_keys<I, S>(keys: I) -> Self
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
Self {
keys: Some(keys.into_iter().map(Into::into).collect()),
}
}
fn transform(&self, ctx: &ExecutionContext, traverser: &Traverser) -> Value {
let mut map = std::collections::HashMap::new();
match &traverser.value {
Value::Vertex(id) => {
if let Some(vertex) = ctx.storage().get_vertex(*id) {
match &self.keys {
None => {
for (key, value) in &vertex.properties {
let prop_obj =
PropertiesStep::make_property_map(key.clone(), value.clone());
map.insert(key.clone(), Value::List(vec![prop_obj]));
}
}
Some(key_list) => {
for key in key_list {
if let Some(value) = vertex.properties.get(key) {
let prop_obj = PropertiesStep::make_property_map(
key.clone(),
value.clone(),
);
map.insert(key.clone(), Value::List(vec![prop_obj]));
}
}
}
}
}
}
Value::Edge(id) => {
if let Some(edge) = ctx.storage().get_edge(*id) {
match &self.keys {
None => {
for (key, value) in &edge.properties {
let prop_obj =
PropertiesStep::make_property_map(key.clone(), value.clone());
map.insert(key.clone(), Value::List(vec![prop_obj]));
}
}
Some(key_list) => {
for key in key_list {
if let Some(value) = edge.properties.get(key) {
let prop_obj = PropertiesStep::make_property_map(
key.clone(),
value.clone(),
);
map.insert(key.clone(), Value::List(vec![prop_obj]));
}
}
}
}
}
}
_ => {
}
}
Value::Map(map.into_value_map())
}
}
impl crate::traversal::step::Step for PropertyMapStep {
type Iter<'a>
= impl Iterator<Item = crate::traversal::Traverser> + 'a
where
Self: 'a;
fn apply<'a>(
&'a self,
ctx: &'a ExecutionContext<'a>,
input: Box<dyn Iterator<Item = Traverser> + 'a>,
) -> Self::Iter<'a> {
input.map(move |t| {
let result = self.transform(ctx, &t);
t.with_value(result)
})
}
fn name(&self) -> &'static str {
"propertyMap"
}
fn category(&self) -> crate::traversal::explain::StepCategory {
crate::traversal::explain::StepCategory::Transform
}
fn apply_streaming(
&self,
ctx: crate::traversal::context::StreamingContext,
input: Traverser,
) -> Box<dyn Iterator<Item = Traverser> + Send + 'static> {
let result = self.transform_streaming(&ctx, &input);
Box::new(std::iter::once(input.with_value(result)))
}
}
impl PropertyMapStep {
fn transform_streaming(
&self,
ctx: &crate::traversal::context::StreamingContext,
traverser: &Traverser,
) -> Value {
let mut map = std::collections::HashMap::new();
match &traverser.value {
Value::Vertex(id) => {
if let Some(vertex) = ctx.storage().get_vertex(*id) {
match &self.keys {
None => {
for (key, value) in &vertex.properties {
let prop_obj =
PropertiesStep::make_property_map(key.clone(), value.clone());
map.insert(key.clone(), Value::List(vec![prop_obj]));
}
}
Some(key_list) => {
for key in key_list {
if let Some(value) = vertex.properties.get(key) {
let prop_obj = PropertiesStep::make_property_map(
key.clone(),
value.clone(),
);
map.insert(key.clone(), Value::List(vec![prop_obj]));
}
}
}
}
}
}
Value::Edge(id) => {
if let Some(edge) = ctx.storage().get_edge(*id) {
match &self.keys {
None => {
for (key, value) in &edge.properties {
let prop_obj =
PropertiesStep::make_property_map(key.clone(), value.clone());
map.insert(key.clone(), Value::List(vec![prop_obj]));
}
}
Some(key_list) => {
for key in key_list {
if let Some(value) = edge.properties.get(key) {
let prop_obj = PropertiesStep::make_property_map(
key.clone(),
value.clone(),
);
map.insert(key.clone(), Value::List(vec![prop_obj]));
}
}
}
}
}
}
_ => {}
}
Value::Map(map.into_value_map())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::storage::Graph;
use crate::traversal::step::{DynStep, Step};
use crate::traversal::SnapshotLike;
use crate::value::{EdgeId, VertexId};
use std::collections::HashMap;
fn create_test_graph() -> Graph {
let graph = Graph::new();
let v0 = graph.add_vertex("person", {
let mut props = HashMap::new();
props.insert("name".to_string(), Value::String("Alice".to_string()));
props.insert("age".to_string(), Value::Int(30));
props
});
let v1 = graph.add_vertex("software", {
let mut props = HashMap::new();
props.insert("name".to_string(), Value::String("Ripple".to_string()));
props.insert("lang".to_string(), Value::String("Java".to_string()));
props
});
graph
.add_edge(v0, v1, "created", {
let mut props = HashMap::new();
props.insert("weight".to_string(), Value::Float(1.0));
props
})
.unwrap();
graph
}
mod properties_step_vertex_tests {
use super::*;
#[test]
fn extracts_all_properties() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = PropertiesStep::new();
let input = vec![Traverser::from_vertex(VertexId(0))];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 2);
let mut keys = Vec::new();
for t in output {
if let Value::Map(map) = t.value {
if let Some(Value::String(k)) = map.get("key") {
keys.push(k.clone());
}
}
}
keys.sort();
assert_eq!(keys, vec!["age", "name"]);
}
#[test]
fn extracts_specific_properties() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = PropertiesStep::with_keys(vec!["name".to_string()]);
let input = vec![Traverser::from_vertex(VertexId(0))];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 1);
if let Value::Map(map) = &output[0].value {
assert_eq!(map.get("key"), Some(&Value::String("name".to_string())));
assert_eq!(map.get("value"), Some(&Value::String("Alice".to_string())));
} else {
panic!("Expected Value::Map");
}
}
}
mod properties_step_edge_tests {
use super::*;
#[test]
fn extracts_edge_properties() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = PropertiesStep::new();
let input = vec![Traverser::from_edge(EdgeId(0))];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 1);
if let Value::Map(map) = &output[0].value {
assert_eq!(map.get("key"), Some(&Value::String("weight".to_string())));
assert_eq!(map.get("value"), Some(&Value::Float(1.0)));
} else {
panic!("Expected Value::Map");
}
}
}
mod properties_step_non_element_tests {
use super::*;
#[test]
fn ignores_non_elements() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = PropertiesStep::new();
let input = vec![Traverser::new(Value::Int(42))];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert!(output.is_empty());
}
}
mod properties_step_metadata_tests {
use super::*;
#[test]
fn preserves_path_for_each_property() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = PropertiesStep::new();
let mut traverser = Traverser::from_vertex(VertexId(0));
traverser.extend_path_labeled("start");
let input = vec![traverser];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 2);
assert!(output[0].path.has_label("start"));
assert!(output[1].path.has_label("start"));
}
}
mod properties_step_empty_tests {
use super::*;
#[test]
fn empty_input_returns_empty_output() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = PropertiesStep::new();
let input: Vec<Traverser> = vec![];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert!(output.is_empty());
}
}
mod value_map_step_construction {
use super::*;
#[test]
fn test_new() {
let step = ValueMapStep::new();
assert_eq!(step.name(), "valueMap");
}
}
mod value_map_step_vertex_transform {
use super::*;
#[test]
fn extracts_all_properties_as_map() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = ValueMapStep::new();
let input = vec![Traverser::from_vertex(VertexId(0))];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 1);
if let Value::Map(map) = &output[0].value {
assert_eq!(map.len(), 2);
assert_eq!(
map.get("name"),
Some(&Value::List(vec![Value::String("Alice".to_string())]))
);
assert_eq!(map.get("age"), Some(&Value::List(vec![Value::Int(30)])));
} else {
panic!("Expected Value::Map");
}
}
#[test]
fn includes_tokens_when_requested() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = ValueMapStep::new().with_tokens();
let input = vec![Traverser::from_vertex(VertexId(0))];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 1);
if let Value::Map(map) = &output[0].value {
assert_eq!(map.get("id"), Some(&Value::Int(0)));
assert_eq!(map.get("label"), Some(&Value::String("person".to_string())));
assert!(map.contains_key("name"));
assert!(map.contains_key("age"));
} else {
panic!("Expected Value::Map");
}
}
}
mod value_map_step_edge_transform {
use super::*;
#[test]
fn extracts_edge_properties_as_map() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = ValueMapStep::new();
let input = vec![Traverser::from_edge(EdgeId(0))];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 1);
if let Value::Map(map) = &output[0].value {
assert_eq!(
map.get("weight"),
Some(&Value::List(vec![Value::Float(1.0)]))
);
} else {
panic!("Expected Value::Map");
}
}
}
mod value_map_step_non_element {
use super::*;
#[test]
fn returns_empty_map_for_non_element() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = ValueMapStep::new();
let input = vec![Traverser::new(Value::Int(42))];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 1);
if let Value::Map(map) = &output[0].value {
assert!(map.is_empty());
} else {
panic!("Expected Value::Map");
}
}
}
mod element_map_step_construction {
use super::*;
#[test]
fn test_new() {
let step = ElementMapStep::new();
assert_eq!(step.name(), "elementMap");
}
}
mod element_map_step_vertex_transform {
use super::*;
#[test]
fn extracts_vertex_element_map() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = ElementMapStep::new();
let input = vec![Traverser::from_vertex(VertexId(0))];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 1);
if let Value::Map(map) = &output[0].value {
assert_eq!(map.get("id"), Some(&Value::Int(0)));
assert_eq!(map.get("label"), Some(&Value::String("person".to_string())));
assert_eq!(map.get("name"), Some(&Value::String("Alice".to_string()))); assert_eq!(map.get("age"), Some(&Value::Int(30)));
} else {
panic!("Expected Value::Map");
}
}
}
mod element_map_step_edge_transform {
use super::*;
#[test]
fn extracts_edge_element_map_with_refs() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = ElementMapStep::new();
let input = vec![Traverser::from_edge(EdgeId(0))];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 1);
if let Value::Map(map) = &output[0].value {
assert_eq!(map.get("id"), Some(&Value::Int(0)));
assert_eq!(
map.get("label"),
Some(&Value::String("created".to_string()))
);
assert_eq!(map.get("weight"), Some(&Value::Float(1.0)));
if let Some(Value::Map(in_ref)) = map.get("IN") {
assert_eq!(in_ref.get("id"), Some(&Value::Int(1)));
assert_eq!(
in_ref.get("label"),
Some(&Value::String("software".to_string()))
);
} else {
panic!("Expected IN ref map");
}
if let Some(Value::Map(out_ref)) = map.get("OUT") {
assert_eq!(out_ref.get("id"), Some(&Value::Int(0)));
assert_eq!(
out_ref.get("label"),
Some(&Value::String("person".to_string()))
);
} else {
panic!("Expected OUT ref map");
}
} else {
panic!("Expected Value::Map");
}
}
}
mod element_map_step_non_element {
use super::*;
#[test]
fn returns_empty_map_for_non_element() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = ElementMapStep::new();
let input = vec![Traverser::new(Value::Int(42))];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 1);
if let Value::Map(map) = &output[0].value {
assert!(map.is_empty());
} else {
panic!("Expected Value::Map");
}
}
}
mod property_map_step_construction {
use super::*;
#[test]
fn new_creates_step_with_no_keys() {
let step = PropertyMapStep::new();
assert_eq!(step.name(), "propertyMap");
}
#[test]
fn default_creates_step_with_no_keys() {
let step = PropertyMapStep::default();
assert_eq!(step.name(), "propertyMap");
}
#[test]
fn with_keys_creates_step_with_specified_keys() {
let step = PropertyMapStep::with_keys(vec!["name".to_string(), "age".to_string()]);
assert_eq!(step.name(), "propertyMap");
}
#[test]
fn from_keys_creates_step_from_iterator() {
let step = PropertyMapStep::from_keys(["name", "age"]);
assert_eq!(step.name(), "propertyMap");
}
#[test]
fn clone_works() {
let step = PropertyMapStep::with_keys(vec!["name".to_string()]);
let cloned = step.clone();
assert_eq!(cloned.name(), "propertyMap");
}
#[test]
fn clone_box_works() {
let step = PropertyMapStep::new();
let boxed = DynStep::clone_box(&step);
assert_eq!(boxed.dyn_name(), "propertyMap");
}
}
mod property_map_step_vertex_transform {
use super::*;
#[test]
fn extracts_all_vertex_properties_as_property_objects() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = PropertyMapStep::new();
let input = vec![Traverser::from_vertex(VertexId(0))];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 1);
if let Value::Map(map) = &output[0].value {
let name_list = map.get("name").expect("name key should exist");
if let Value::List(list) = name_list {
assert_eq!(list.len(), 1);
if let Value::Map(prop_obj) = &list[0] {
assert_eq!(
prop_obj.get("key"),
Some(&Value::String("name".to_string()))
);
assert_eq!(
prop_obj.get("value"),
Some(&Value::String("Alice".to_string()))
);
} else {
panic!("Expected property object to be a Map");
}
} else {
panic!("Expected name value to be a List");
}
let age_list = map.get("age").expect("age key should exist");
if let Value::List(list) = age_list {
assert_eq!(list.len(), 1);
if let Value::Map(prop_obj) = &list[0] {
assert_eq!(prop_obj.get("key"), Some(&Value::String("age".to_string())));
assert_eq!(prop_obj.get("value"), Some(&Value::Int(30)));
} else {
panic!("Expected property object to be a Map");
}
} else {
panic!("Expected age value to be a List");
}
} else {
panic!("Expected Value::Map");
}
}
#[test]
fn extracts_specific_vertex_properties() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = PropertyMapStep::with_keys(vec!["name".to_string()]);
let input = vec![Traverser::from_vertex(VertexId(0))];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 1);
if let Value::Map(map) = &output[0].value {
assert!(map.contains_key("name"));
assert!(!map.contains_key("age"));
assert_eq!(map.len(), 1);
} else {
panic!("Expected Value::Map");
}
}
#[test]
fn missing_keys_are_omitted() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = PropertyMapStep::with_keys(vec!["name".to_string(), "missing".to_string()]);
let input = vec![Traverser::from_vertex(VertexId(0))];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 1);
if let Value::Map(map) = &output[0].value {
assert!(map.contains_key("name"));
assert!(!map.contains_key("missing"));
} else {
panic!("Expected Value::Map");
}
}
}
mod property_map_step_edge_transform {
use super::*;
#[test]
fn extracts_edge_properties_as_property_objects() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = PropertyMapStep::new();
let input = vec![Traverser::from_edge(EdgeId(0))];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 1);
if let Value::Map(map) = &output[0].value {
let weight_list = map.get("weight").expect("weight key should exist");
if let Value::List(list) = weight_list {
assert_eq!(list.len(), 1);
if let Value::Map(prop_obj) = &list[0] {
assert_eq!(
prop_obj.get("key"),
Some(&Value::String("weight".to_string()))
);
assert_eq!(prop_obj.get("value"), Some(&Value::Float(1.0)));
} else {
panic!("Expected property object to be a Map");
}
} else {
panic!("Expected weight value to be a List");
}
} else {
panic!("Expected Value::Map");
}
}
#[test]
fn extracts_specific_edge_properties() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = PropertyMapStep::with_keys(vec!["weight".to_string()]);
let input = vec![Traverser::from_edge(EdgeId(0))];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 1);
if let Value::Map(map) = &output[0].value {
assert!(map.contains_key("weight"));
assert_eq!(map.len(), 1);
} else {
panic!("Expected Value::Map");
}
}
}
mod property_map_step_non_element {
use super::*;
#[test]
fn returns_empty_map_for_non_element() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = PropertyMapStep::new();
let input = vec![Traverser::new(Value::Int(42))];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 1);
if let Value::Map(map) = &output[0].value {
assert!(map.is_empty());
} else {
panic!("Expected Value::Map");
}
}
#[test]
fn returns_empty_map_for_string() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = PropertyMapStep::new();
let input = vec![Traverser::new(Value::String("test".to_string()))];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 1);
if let Value::Map(map) = &output[0].value {
assert!(map.is_empty());
} else {
panic!("Expected Value::Map");
}
}
}
mod property_map_step_metadata {
use super::*;
#[test]
fn preserves_path() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = PropertyMapStep::new();
let mut t = Traverser::from_vertex(VertexId(0));
t.path
.push_labeled(crate::traversal::PathValue::Vertex(VertexId(99)), "start");
let output: Vec<Traverser> = step.apply(&ctx, Box::new(std::iter::once(t))).collect();
assert_eq!(output.len(), 1);
assert!(output[0].path.has_label("start"));
}
#[test]
fn preserves_loops() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = PropertyMapStep::new();
let mut t = Traverser::from_vertex(VertexId(0));
t.loops = 5;
let output: Vec<Traverser> = step.apply(&ctx, Box::new(std::iter::once(t))).collect();
assert_eq!(output.len(), 1);
assert_eq!(output[0].loops, 5);
}
#[test]
fn preserves_bulk() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = PropertyMapStep::new();
let mut t = Traverser::from_vertex(VertexId(0));
t.bulk = 10;
let output: Vec<Traverser> = step.apply(&ctx, Box::new(std::iter::once(t))).collect();
assert_eq!(output.len(), 1);
assert_eq!(output[0].bulk, 10);
}
}
mod property_map_step_missing_element {
use super::*;
#[test]
fn returns_empty_map_for_missing_vertex() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = PropertyMapStep::new();
let input = vec![Traverser::from_vertex(VertexId(999))];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 1);
if let Value::Map(map) = &output[0].value {
assert!(map.is_empty());
} else {
panic!("Expected Value::Map");
}
}
#[test]
fn returns_empty_map_for_missing_edge() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let ctx = ExecutionContext::new(snapshot.storage(), snapshot.interner());
let step = PropertyMapStep::new();
let input = vec![Traverser::from_edge(EdgeId(999))];
let output: Vec<Traverser> = step.apply(&ctx, Box::new(input.into_iter())).collect();
assert_eq!(output.len(), 1);
if let Value::Map(map) = &output[0].value {
assert!(map.is_empty());
} else {
panic!("Expected Value::Map");
}
}
}
}