1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
use crate::graph::GraphStorage;
use crate::*;
/// `value()`: resolves a `VertexProperty`/`EdgeProperty` traverser (produced by
/// [`properties()`](GraphTraversalSource::properties) or a raw
/// [`values()`](GraphTraversalSource::values) reference) to its stored scalar value — the read
/// half of the pair with [`key()`](GraphTraversalSource::key).
///
/// Reuses [`TraverserValue::into_node_property`], the same storage lookup a terminal step
/// (`to_list()`/`next()`) already applies to a lazy property reference — `value()` just performs
/// it explicitly, mid-chain, instead of waiting for the traversal's end.
#[derive(Default, Clone)]
struct StepValue {}
impl fmt::Display for StepValue {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "value()")
}
}
impl<'g, S: GraphStorage + 'g> GraphTraversalSource<'g, S> {
/// The stored value of a property-object traverser. Errors (not panics, rule 1) when the
/// traverser isn't a property object at all — e.g. called directly on a bare vertex without
/// going through `properties()`/`values()` first, matching vanilla Gremlin's own behaviour
/// (`Property`/`VertexProperty` only).
///
/// ```rust
/// use graphersal::prelude::*;
///
/// let graph = GraphSource::tinkerpop_modern();
/// let lock = graph.read();
/// let ages = lock.traversal().v("1").properties("age").value().to_list().unwrap();
/// assert_eq!(ages[0].as_i64(), Some(29));
/// ```
pub fn value(self) -> Self {
self.push_step(GraphStep::Map("value", Box::new(StepValue::default())))
}
}
impl __ {
/// Anonymous `value()`; see [`GraphTraversalSource::value`].
pub fn value<'g, S: GraphStorage + 'g>() -> AnonymousTraversal<'g, S> {
AnonymousTraversal::new().value()
}
}
impl<'g, S: GraphStorage + 'g> AnonymousTraversal<'g, S> {
/// Anonymous `value()`; see [`GraphTraversalSource::value`].
pub fn value(self) -> Self {
let step = GraphStep::Map("value", Box::new(StepValue::default()));
self.push_step(step)
}
}
impl<'g, S: GraphStorage + 'g> GraphStepMap<'g, S> for StepValue {
#[inline]
fn process(
&self,
context: &mut TraverserContext<'_, S>,
source: Traverser<S>,
) -> TraverserResult<Traverser<S>> {
match &source.value {
TraverserValue::PathProperty(prop) => Ok(Traverser::with_parts(
TraverserValue::Computed(prop.1.clone(), None),
source.path,
source.bulk,
source.sack,
)),
TraverserValue::VertexProperty(_, _, _) | TraverserValue::EdgeProperty(_, _, _) => {
let resolved = source.value.into_node_property(context.storage)?;
Ok(Traverser::with_parts(
TraverserValue::Computed(resolved, None),
source.path,
source.bulk,
source.sack,
))
}
v => Err(Box::new(TraverserError::cast_exception(
&["vertexProperty", "edgeProperty", "pathProperty"],
v,
))),
}
}
fn clone_box(&self) -> Box<dyn GraphStepMap<'g, S> + 'g> {
Box::new(self.clone())
}
}
#[cfg(test)]
mod tests {
use super::*;
/// `g.V().hasLabel("person").properties("name", "age").value()` (roadmap Overview): the
/// correct multiset of values across all four `person` vertices.
#[test]
fn person_values() {
test_dual!(
graph: GraphSource::tinkerpop_modern(),
rust: |g| g.v(None).has_label("person").properties(vec!["name", "age"]).value().to_list(),
rhai: [
r#"g.V().hasLabel("person").properties(["name", "age"]).value()"#,
r#"g.v().has_label("person").properties(["name", "age"]).value()"#,
],
assert: |res| {
let mut names: Vec<&str> = res.iter().filter_map(|v| v.as_string()).collect();
names.sort_unstable();
assert_eq!(names, vec!["josh", "marko", "peter", "vadas"]);
let mut ages: Vec<i64> = res.iter().filter_map(|v| v.as_i64()).collect();
ages.sort_unstable();
assert_eq!(ages, vec![27, 29, 32, 35]);
},
);
}
#[test]
fn edge_value() {
test_dual!(
graph: GraphSource::tinkerpop_modern(),
rust: |g| g.e("1").properties("weight").value().to_list(),
rhai: [
r#"g.E("1").properties("weight").value()"#,
r#"g.e("1").properties("weight").value()"#,
],
assert: |res| assert_eq!(res[0].as_f64(), Some(0.4)),
);
}
/// The full roadmap Overview chain: `properties()` -> `dedup()` -> `hasKey()` -> `value()`,
/// filtered by weight after resolution. `hasValue(P.lt(...))` was a pre-existing gap when
/// this test was first written — `has_value()` only accepted a plain `ElementProperty`, not a
/// `P` predicate — so this test used `.value().is(P.lt(0.3))` as a workaround, moving the
/// predicate check after the resolution `hasValue(P...)` couldn't yet perform. That gap is
/// now fixed (see `crates/graphersal/src/traversal/step/filter_steps/step_has_value.rs`'s
/// `StepHasValueP`); this test is kept as-is to keep covering the `.value().is(P...)` path in
/// its own right, and the no-workaround form now has its own test,
/// `step_has_value::tests::roadmap_bothe_properties_dedup_haskey_hasvalue_predicate`.
#[test]
fn full_chain_bothe_properties_dedup_haskey_value() {
test_dual!(
graph: GraphSource::tinkerpop_modern(),
rust: |g| g
.v(None)
.both_e(None)
.properties(None)
.dedup()
.has_key("weight")
.value()
.is(P::Lt(0.3))
.to_list(),
rhai: [
r#"g.V().bothE().properties().dedup().hasKey("weight").value().is(P.lt(0.3))"#,
r#"g.v().both_e().properties().dedup().has_key("weight").value().is(P.lt(0.3))"#,
],
assert: |res| {
let mut weights: Vec<f64> = res.iter().filter_map(|v| v.as_f64()).collect();
weights.sort_by(|a, b| a.partial_cmp(b).unwrap());
assert_eq!(weights, vec![0.2]);
},
);
}
#[test]
fn on_bare_vertex_is_cast_exception_not_panic() {
let graph = GraphSource::tinkerpop_modern();
let g_lock = graph.read();
let g = g_lock.traversal();
let err = g
.v("1")
.value()
.next()
.expect_err("a bare vertex is not a property object");
assert!(matches!(
err.root_cause(),
TraverserError::Value(ValueError::CastException { .. })
));
}
#[test]
fn profile() {
let graph = GraphSource::tinkerpop_modern();
let g_lock = graph.read();
let g = g_lock.traversal();
let result = g.v("1").properties("name").value().profile().unwrap();
assert!(result.to_string().contains("value("));
}
}