This is an automated email from the ASF dual-hosted git repository.
spmallette pushed a commit to branch TINKERPOP-3178
in repository https://gitbox.apache.org/repos/asf/tinkerpop.git
The following commit(s) were added to refs/heads/TINKERPOP-3178 by this push:
new 89ec9152fd wip
89ec9152fd is described below
commit 89ec9152fd37675554bd58703baef6e71522b485
Author: Stephen Mallette <[email protected]>
AuthorDate: Tue Jul 29 11:48:44 2025 -0400
wip
---
docs/src/dev/provider/gremlin-semantics.asciidoc | 11 +++---
.../process/traversal/step/branch/ChooseStep.java | 4 ++-
.../src/Gremlin.Net/Process/Traversal/Pick.cs | 3 ++
.../Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs | 2 ++
gremlin-go/driver/cucumber/gremlin.go | 2 ++
gremlin-go/driver/traversal.go | 10 +++---
.../gremlin-javascript/lib/process/traversal.js | 2 +-
.../gremlin-javascript/test/cucumber/gremlin.js | 2 ++
gremlin-language/src/main/antlr4/Gremlin.g4 | 3 ++
.../python/gremlin_python/process/traversal.py | 3 +-
gremlin-python/src/main/python/radish/gremlin.py | 2 ++
.../gremlin/test/features/branch/Choose.feature | 40 ++++++++++++++++++++++
12 files changed, 73 insertions(+), 11 deletions(-)
diff --git a/docs/src/dev/provider/gremlin-semantics.asciidoc
b/docs/src/dev/provider/gremlin-semantics.asciidoc
index d02663bf10..db16f1bf6c 100644
--- a/docs/src/dev/provider/gremlin-semantics.asciidoc
+++ b/docs/src/dev/provider/gremlin-semantics.asciidoc
@@ -1155,17 +1155,20 @@ two main forms of the `choose()` step:
(has a next element), the traverser is routed to the trueChoice traversal.
Otherwise, it is routed to the falseChoice
traversal. If the predicate is unproductive or if the falseChoice is not
specified, then the traverser passes through.
-2. *Choice value form*: `choose(choice).option(pickValue, resultTraversal)` -
The choice which may be a `Traversal` or
+2. *switch form*: `choose(choice).option(pickValue, resultTraversal)` - The
choice which may be a `Traversal` or
`T` produces a value that is matched against the pickValue of each option. If
a match is found, the traverser is routed
-to the corresponding resultTraversal. If no match is found then the traverser
is filtered, but, for these cases, the
-`Pick.none` option can be provided to match those cases. If the
choiceTraversal is unproductive, then the traverser
-passes through.
+to the corresponding resultTraversal and no further matches are attempted. If
no match is found then the traverser is
+filtered, but, for these cases, the `Pick.none` option can be provided to
match those cases. If the choiceTraversal is
+unproductive, then the traverser passes through by default or can be matched
on `Pick.unproductive`.
The `choose()` step can be used with the following special `Pick` tokens:
* `Pick.none` - Used to specify a default option when no other options match.
* `Pick.unproductive` - Used when the choice traversal produces no value.
+`choose` does not allow more than one traversal to be assigned to a single
`Pick`. The last `Pick` assigned via `option`
+is the one that will be used.
+
The `choose()` step ensures that only one option is selected for each
traverser, unlike other branch steps like
`union()` that can route a traverser to multiple paths. As it is like
`union()`, note that each `option` stream will
behave like one:
diff --git
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/branch/ChooseStep.java
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/branch/ChooseStep.java
index ea69c77705..9a8d59dd5a 100644
---
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/branch/ChooseStep.java
+++
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/branch/ChooseStep.java
@@ -53,8 +53,10 @@ public final class ChooseStep<S, E, M> extends BranchStep<S,
E, M> {
if (pickToken instanceof Pick) {
if (Pick.any.equals(pickToken))
throw new IllegalArgumentException("Choose step can not have
an any-option as only one option per traverser is allowed");
+
+ // replace the previous Pick with the new one - can't assign more
than one traversal to a single Pick
if (this.traversalPickOptions.containsKey(pickToken))
- throw new IllegalArgumentException("Choose step can only have
one traversal per pick token: " + pickToken);
+ this.traversalPickOptions.remove(pickToken);
}
super.addChildOption(pickToken, traversalOption);
}
diff --git a/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/Pick.cs
b/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/Pick.cs
index f0312a3a2f..44973a5971 100644
--- a/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/Pick.cs
+++ b/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/Pick.cs
@@ -39,10 +39,13 @@ namespace Gremlin.Net.Process.Traversal
public static Pick None => new Pick("none");
+ public static Pick Unproductive => new Pick("unproductive");
+
private static readonly IDictionary<string, Pick> Properties = new
Dictionary<string, Pick>
{
{ "any", Any },
{ "none", None },
+ { "nunproductiveone", Unproductive },
};
/// <summary>
diff --git a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs
b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs
index 6fbe3af9fa..2068875982 100644
--- a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs
+++ b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs
@@ -86,6 +86,8 @@ namespace Gremlin.Net.IntegrationTest.Gherkin
{"g_unionXV_VXhasLabelXpersonX_barrier_localXchooseXageX_optionXbetweenX26_30X_name_foldX_optionXnone_name_foldXX",
new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>>
{(g,p) =>g.Union<object>(__.V(),
__.V()).HasLabel("person").Barrier().Local<object>(__.Choose<object>(__.Values<object>("age")).Option(P.Between(26,
30), __.Values<object>("name").Fold()).Option(Pick.None,
__.Values<object>("name").Fold()))}},
{"g_unionXV_VXhasLabelXpersonX_barrier_mapXchooseXageX_optionXbetweenX26_30X_name_foldX_optionXnone_name_foldXX",
new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>>
{(g,p) =>g.Union<object>(__.V(),
__.V()).HasLabel("person").Barrier().Map<object>(__.Choose<object>(__.Values<object>("age")).Option(P.Between(26,
30), __.Values<object>("name").Fold()).Option(Pick.None,
__.Values<object>("name").Fold()))}},
{"g_V_chooseXageX_optionXbetweenX26_30X_nameX_optionXnone_nameX", new
List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>>
{(g,p) =>g.V().Choose<object>(__.Values<object>("age")).Option(P.Between(26,
30), __.Values<object>("name")).Option(Pick.None, __.Values<object>("name"))}},
+
{"g_V_chooseXageX_optionXbetweenX26_30X_nameX_optionXnone_nameX_optionXunproductive_labelX",
new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>>
{(g,p) =>g.V().Choose<object>(__.Values<object>("age")).Option(P.Between(26,
30), __.Values<object>("name")).Option(Pick.None,
__.Values<object>("name")).Option(Pick.Unproductive, __.Label())}},
+
{"g_V_chooseXageX_optionXbetweenX26_30X_nameX_optionXnone_nameX_optionXunproductive_identityX_optionXunproductive_labelX",
new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>>
{(g,p) =>g.V().Choose<object>(__.Values<object>("age")).Option(P.Between(26,
30), __.Values<object>("name")).Option(Pick.None,
__.Identity()).Option(Pick.None,
__.Values<object>("name")).Option(Pick.Unproductive,
__.Identity()).Option(Pick.Unproductive, __.Label())}},
{"g_V_chooseXage_nameX", new List<Func<GraphTraversalSource,
IDictionary<string, object>, ITraversal>> {(g,p)
=>g.V().Choose<object>(__.Values<object>("age"), __.Values<object>("name"))}},
{"g_V_chooseXageX_optionXbetweenX26_30X_nameX", new
List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>>
{(g,p) =>g.V().Choose<object>(__.Values<object>("age")).Option(P.Between(26,
30), __.Values<object>("name"))}},
{"g_V_hasLabelXpersonX_chooseXoutXcreatedX_count_isXeqX0XX__constantXdidnt_createX__constantXcreatedXX",
new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>>
{(g,p)
=>g.V().HasLabel("person").Choose<object>(__.Out("created").Count().Is(P.Eq(0)),
__.Constant<object>("didnt_create"), __.Constant<object>("created"))}},
diff --git a/gremlin-go/driver/cucumber/gremlin.go
b/gremlin-go/driver/cucumber/gremlin.go
index 724d31d48e..ca1b060ea8 100644
--- a/gremlin-go/driver/cucumber/gremlin.go
+++ b/gremlin-go/driver/cucumber/gremlin.go
@@ -56,6 +56,8 @@ var translationMap = map[string][]func(g
*gremlingo.GraphTraversalSource, p map[
"g_unionXV_VXhasLabelXpersonX_barrier_localXchooseXageX_optionXbetweenX26_30X_name_foldX_optionXnone_name_foldXX":
{func(g *gremlingo.GraphTraversalSource, p map[string]interface{})
*gremlingo.GraphTraversal {return g.Union(gremlingo.T__.V(),
gremlingo.T__.V()).HasLabel("person").Barrier().Local(gremlingo.T__.Choose(gremlingo.T__.Values("age")).Option(gremlingo.P.Between(26,
30), gremlingo.T__.Values("name").Fold()).Option(gremlingo.Pick.None,
gremlingo.T__.Values("name").Fold()))}},
"g_unionXV_VXhasLabelXpersonX_barrier_mapXchooseXageX_optionXbetweenX26_30X_name_foldX_optionXnone_name_foldXX":
{func(g *gremlingo.GraphTraversalSource, p map[string]interface{})
*gremlingo.GraphTraversal {return g.Union(gremlingo.T__.V(),
gremlingo.T__.V()).HasLabel("person").Barrier().Map(gremlingo.T__.Choose(gremlingo.T__.Values("age")).Option(gremlingo.P.Between(26,
30), gremlingo.T__.Values("name").Fold()).Option(gremlingo.Pick.None,
gremlingo.T__.Values("name").Fold()))}},
"g_V_chooseXageX_optionXbetweenX26_30X_nameX_optionXnone_nameX": {func(g
*gremlingo.GraphTraversalSource, p map[string]interface{})
*gremlingo.GraphTraversal {return
g.V().Choose(gremlingo.T__.Values("age")).Option(gremlingo.P.Between(26, 30),
gremlingo.T__.Values("name")).Option(gremlingo.Pick.None,
gremlingo.T__.Values("name"))}},
+
"g_V_chooseXageX_optionXbetweenX26_30X_nameX_optionXnone_nameX_optionXunproductive_labelX":
{func(g *gremlingo.GraphTraversalSource, p map[string]interface{})
*gremlingo.GraphTraversal {return
g.V().Choose(gremlingo.T__.Values("age")).Option(gremlingo.P.Between(26, 30),
gremlingo.T__.Values("name")).Option(gremlingo.Pick.None,
gremlingo.T__.Values("name")).Option(gremlingo.Pick.Unproductive,
gremlingo.T__.Label())}},
+
"g_V_chooseXageX_optionXbetweenX26_30X_nameX_optionXnone_nameX_optionXunproductive_identityX_optionXunproductive_labelX":
{func(g *gremlingo.GraphTraversalSource, p map[string]interface{})
*gremlingo.GraphTraversal {return
g.V().Choose(gremlingo.T__.Values("age")).Option(gremlingo.P.Between(26, 30),
gremlingo.T__.Values("name")).Option(gremlingo.Pick.None,
gremlingo.T__.Identity()).Option(gremlingo.Pick.None,
gremlingo.T__.Values("name")).Option(gremlingo.Pick.Unproductive, gremlingo
[...]
"g_V_chooseXage_nameX": {func(g *gremlingo.GraphTraversalSource, p
map[string]interface{}) *gremlingo.GraphTraversal {return
g.V().Choose(gremlingo.T__.Values("age"), gremlingo.T__.Values("name"))}},
"g_V_chooseXageX_optionXbetweenX26_30X_nameX": {func(g
*gremlingo.GraphTraversalSource, p map[string]interface{})
*gremlingo.GraphTraversal {return
g.V().Choose(gremlingo.T__.Values("age")).Option(gremlingo.P.Between(26, 30),
gremlingo.T__.Values("name"))}},
"g_V_hasLabelXpersonX_chooseXoutXcreatedX_count_isXeqX0XX__constantXdidnt_createX__constantXcreatedXX":
{func(g *gremlingo.GraphTraversalSource, p map[string]interface{})
*gremlingo.GraphTraversal {return
g.V().HasLabel("person").Choose(gremlingo.T__.Out("created").Count().Is(gremlingo.P.Eq(0)),
gremlingo.T__.Constant("didnt_create"), gremlingo.T__.Constant("created"))}},
diff --git a/gremlin-go/driver/traversal.go b/gremlin-go/driver/traversal.go
index 6d82f6d0af..b891abb8ee 100644
--- a/gremlin-go/driver/traversal.go
+++ b/gremlin-go/driver/traversal.go
@@ -243,13 +243,15 @@ var Order = orders{
type pick string
type picks struct {
- Any pick
- None pick
+ Any pick
+ None pick
+ Unproductive pick
}
var Pick = picks{
- Any: "any",
- None: "none",
+ Any: "any",
+ None: "none",
+ Unproductive: "unproductive",
}
type pop string
diff --git
a/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/process/traversal.js
b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/process/traversal.js
index a1ff9316dd..2c9ac1ded6 100644
---
a/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/process/traversal.js
+++
b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/process/traversal.js
@@ -517,7 +517,7 @@ module.exports = {
merge: toEnum('Merge', 'onCreate onMatch outV inV'),
operator: toEnum('Operator', 'addAll and assign div max min minus mult or
sum sumLong'),
order: toEnum('Order', 'asc desc shuffle'),
- pick: toEnum('Pick', 'any none'),
+ pick: toEnum('Pick', 'any none unproductive'),
pop: toEnum('Pop', 'all first last mixed'),
scope: toEnum('Scope', 'global local'),
t: toEnum('T', 'id key label value'),
diff --git
a/gremlin-javascript/src/main/javascript/gremlin-javascript/test/cucumber/gremlin.js
b/gremlin-javascript/src/main/javascript/gremlin-javascript/test/cucumber/gremlin.js
index 7dcbc3d394..0bac0c3af4 100644
---
a/gremlin-javascript/src/main/javascript/gremlin-javascript/test/cucumber/gremlin.js
+++
b/gremlin-javascript/src/main/javascript/gremlin-javascript/test/cucumber/gremlin.js
@@ -86,6 +86,8 @@ const gremlins = {
g_unionXV_VXhasLabelXpersonX_barrier_localXchooseXageX_optionXbetweenX26_30X_name_foldX_optionXnone_name_foldXX:
[function({g}) { return g.union(__.V(),
__.V()).hasLabel("person").barrier().local(__.choose(__.values("age")).option(P.between(26,
30), __.values("name").fold()).option(Pick.none, __.values("name").fold()))
}],
g_unionXV_VXhasLabelXpersonX_barrier_mapXchooseXageX_optionXbetweenX26_30X_name_foldX_optionXnone_name_foldXX:
[function({g}) { return g.union(__.V(),
__.V()).hasLabel("person").barrier().map(__.choose(__.values("age")).option(P.between(26,
30), __.values("name").fold()).option(Pick.none, __.values("name").fold()))
}],
g_V_chooseXageX_optionXbetweenX26_30X_nameX_optionXnone_nameX:
[function({g}) { return g.V().choose(__.values("age")).option(P.between(26,
30), __.values("name")).option(Pick.none, __.values("name")) }],
+
g_V_chooseXageX_optionXbetweenX26_30X_nameX_optionXnone_nameX_optionXunproductive_labelX:
[function({g}) { return g.V().choose(__.values("age")).option(P.between(26,
30), __.values("name")).option(Pick.none,
__.values("name")).option(Pick.unproductive, __.label()) }],
+
g_V_chooseXageX_optionXbetweenX26_30X_nameX_optionXnone_nameX_optionXunproductive_identityX_optionXunproductive_labelX:
[function({g}) { return g.V().choose(__.values("age")).option(P.between(26,
30), __.values("name")).option(Pick.none, __.identity()).option(Pick.none,
__.values("name")).option(Pick.unproductive,
__.identity()).option(Pick.unproductive, __.label()) }],
g_V_chooseXage_nameX: [function({g}) { return
g.V().choose(__.values("age"), __.values("name")) }],
g_V_chooseXageX_optionXbetweenX26_30X_nameX: [function({g}) { return
g.V().choose(__.values("age")).option(P.between(26, 30), __.values("name")) }],
g_V_hasLabelXpersonX_chooseXoutXcreatedX_count_isXeqX0XX__constantXdidnt_createX__constantXcreatedXX:
[function({g}) { return
g.V().hasLabel("person").choose(__.out("created").count().is(P.eq(0)),
__.constant("didnt_create"), __.constant("created")) }],
diff --git a/gremlin-language/src/main/antlr4/Gremlin.g4
b/gremlin-language/src/main/antlr4/Gremlin.g4
index a2ae6fea14..d29f7dd86f 100644
--- a/gremlin-language/src/main/antlr4/Gremlin.g4
+++ b/gremlin-language/src/main/antlr4/Gremlin.g4
@@ -1054,6 +1054,7 @@ traversalOperator
traversalPick
: K_ANY | K_PICK DOT K_ANY
| K_NONE | K_PICK DOT K_NONE
+ | K_UNPRODUCTIVE | K_PICK DOT K_UNPRODUCTIVE
;
traversalDT
@@ -1887,6 +1888,7 @@ keyword
| K_TX
| K_UNFOLD
| K_UNION
+ | K_UNPRODUCTIVE
| K_UNTIL
| K_UUID
| K_V
@@ -2146,6 +2148,7 @@ K_TRYNEXT: 'tryNext';
K_TX: 'tx';
K_UNFOLD: 'unfold';
K_UNION: 'union';
+K_UNPRODUCTIVE: 'unproductive';
K_UNTIL: 'until';
K_UUID: 'UUID';
K_V: 'V';
diff --git a/gremlin-python/src/main/python/gremlin_python/process/traversal.py
b/gremlin-python/src/main/python/gremlin_python/process/traversal.py
index fb74433348..f687e14bac 100644
--- a/gremlin-python/src/main/python/gremlin_python/process/traversal.py
+++ b/gremlin-python/src/main/python/gremlin_python/process/traversal.py
@@ -214,10 +214,11 @@ statics.add_static('shuffle', Order.shuffle)
statics.add_static('asc', Order.asc)
statics.add_static('desc', Order.desc)
-Pick = Enum('Pick', ' any_ none')
+Pick = Enum('Pick', ' any_ none unproductive')
statics.add_static('any_', Pick.any_)
statics.add_static('none', Pick.none)
+statics.add_static('unproductive', Pick.unproductive)
Pop = Enum('Pop', ' all_ first last mixed')
diff --git a/gremlin-python/src/main/python/radish/gremlin.py
b/gremlin-python/src/main/python/radish/gremlin.py
index 8f75a7a844..2085fe3db8 100644
--- a/gremlin-python/src/main/python/radish/gremlin.py
+++ b/gremlin-python/src/main/python/radish/gremlin.py
@@ -59,6 +59,8 @@ world.gremlins = {
'g_unionXV_VXhasLabelXpersonX_barrier_localXchooseXageX_optionXbetweenX26_30X_name_foldX_optionXnone_name_foldXX':
[(lambda g:g.union(__.V(),
__.V()).has_label('person').barrier().local(__.choose(__.values('age')).option(P.between(26,
30), __.values('name').fold()).option(Pick.none, __.values('name').fold())))],
'g_unionXV_VXhasLabelXpersonX_barrier_mapXchooseXageX_optionXbetweenX26_30X_name_foldX_optionXnone_name_foldXX':
[(lambda g:g.union(__.V(),
__.V()).has_label('person').barrier().map(__.choose(__.values('age')).option(P.between(26,
30), __.values('name').fold()).option(Pick.none, __.values('name').fold())))],
'g_V_chooseXageX_optionXbetweenX26_30X_nameX_optionXnone_nameX': [(lambda
g:g.V().choose(__.values('age')).option(P.between(26, 30),
__.values('name')).option(Pick.none, __.values('name')))],
+
'g_V_chooseXageX_optionXbetweenX26_30X_nameX_optionXnone_nameX_optionXunproductive_labelX':
[(lambda g:g.V().choose(__.values('age')).option(P.between(26, 30),
__.values('name')).option(Pick.none,
__.values('name')).option(Pick.unproductive, __.label()))],
+
'g_V_chooseXageX_optionXbetweenX26_30X_nameX_optionXnone_nameX_optionXunproductive_identityX_optionXunproductive_labelX':
[(lambda g:g.V().choose(__.values('age')).option(P.between(26, 30),
__.values('name')).option(Pick.none, __.identity()).option(Pick.none,
__.values('name')).option(Pick.unproductive,
__.identity()).option(Pick.unproductive, __.label()))],
'g_V_chooseXage_nameX': [(lambda g:g.V().choose(__.values('age'),
__.values('name')))],
'g_V_chooseXageX_optionXbetweenX26_30X_nameX': [(lambda
g:g.V().choose(__.values('age')).option(P.between(26, 30),
__.values('name')))],
'g_V_hasLabelXpersonX_chooseXoutXcreatedX_count_isXeqX0XX__constantXdidnt_createX__constantXcreatedXX':
[(lambda
g:g.V().has_label('person').choose(__.out('created').count().is_(P.eq(0)),
__.constant('didnt_create'), __.constant('created')))],
diff --git
a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/branch/Choose.feature
b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/branch/Choose.feature
index 152c53afda..5738de2f69 100644
---
a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/branch/Choose.feature
+++
b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/branch/Choose.feature
@@ -347,6 +347,46 @@ Feature: Step - choose()
| v[ripple] |
| peter |
+ Scenario:
g_V_chooseXageX_optionXbetweenX26_30X_nameX_optionXnone_nameX_optionXunproductive_labelX
+ Given the modern graph
+ And the traversal of
+ """
+ g.V().choose(__.values("age")).
+ option(P.between(26, 30), __.values("name")).
+ option(Pick.none, __.values("name")).
+ option(Pick.unproductive, __.label())
+ """
+ When iterated to list
+ Then the result should be unordered
+ | result |
+ | marko |
+ | vadas |
+ | software |
+ | josh |
+ | software |
+ | peter |
+
+ Scenario:
g_V_chooseXageX_optionXbetweenX26_30X_nameX_optionXnone_nameX_optionXunproductive_identityX_optionXunproductive_labelX
+ Given the modern graph
+ And the traversal of
+ """
+ g.V().choose(__.values("age")).
+ option(P.between(26, 30), __.values("name")).
+ option(Pick.none, __.identity()).
+ option(Pick.none, __.values("name")).
+ option(Pick.unproductive, __.identity()).
+ option(Pick.unproductive, __.label())
+ """
+ When iterated to list
+ Then the result should be unordered
+ | result |
+ | marko |
+ | vadas |
+ | software |
+ | josh |
+ | software |
+ | peter |
+
Scenario: g_V_chooseXage_nameX
Given the modern graph
And the traversal of