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 39f444a12f wip
39f444a12f is described below
commit 39f444a12fafa3c16ad8bc1bb56e8978be600534
Author: Stephen Mallette <[email protected]>
AuthorDate: Mon Jul 28 11:22:24 2025 -0400
wip
---
docs/src/dev/provider/gremlin-semantics.asciidoc | 131 ++++++++++++----
.../tinkerpop/gremlin/process/traversal/Pick.java | 15 +-
.../gremlin/test/features/branch/Choose.feature | 165 ++++++++++++++++++++-
3 files changed, 279 insertions(+), 32 deletions(-)
diff --git a/docs/src/dev/provider/gremlin-semantics.asciidoc
b/docs/src/dev/provider/gremlin-semantics.asciidoc
index a499a015fe..e469566fbc 100644
--- a/docs/src/dev/provider/gremlin-semantics.asciidoc
+++ b/docs/src/dev/provider/gremlin-semantics.asciidoc
@@ -801,6 +801,37 @@ Incoming date remains unchanged.
See:
link:https://github.com/apache/tinkerpop/tree/x.y.z/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/AsDateStep.java[source],
link:https://tinkerpop.apache.org/docs/x.y.z/reference/#asDate-step[reference]
+[[asString-step]]
+=== asString()
+
+*Description:* Returns the value of incoming traverser as strings, or if
`Scope.local` is specified, returns each element inside
+incoming list traverser as string.
+
+*Syntax:* `asString()` | `asString(Scope scope)`
+
+[width="100%",options="header"]
+|=========================================================
+|Start Step |Mid Step |Modulated |Domain |Range
+|N |Y |N |`any` |`String`/`List`
+|=========================================================
+
+*Arguments:*
+
+* `scope` - Determines the type of traverser it operates on. Both scopes will
operate on the level of individual traversers.
+The `global` scope will operate on individual traverser, casting all (except
`null`) to string. The `local` scope will behave like
+`global` for everything except lists, where it will cast individual non-`null`
elements inside the list into string and return a
+list of string instead.
+
+Null values from the incoming traverser are not processed and remain as null
when returned.
+
+*Exceptions*
+None
+
+See:
link:https://github.com/apache/tinkerpop/tree/x.y.z/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/AsStringGlobalStep.java[source],
+link:https://github.com/apache/tinkerpop/tree/x.y.z/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/AsStringLocalStep.java[source
(local)],
+link:https://tinkerpop.apache.org/docs/x.y.z/reference/#asString-step[reference]
+
+
[[barrier-step]]
=== barrier()
@@ -1004,36 +1035,6 @@ None
See:
link:https://github.com/apache/tinkerpop/tree/x.y.z/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/sideEffect/SideEffectCapStep.java[source],
link:https://tinkerpop.apache.org/docs/x.y.z/reference/#cap-step[reference]
-[[asString-step]]
-=== asString()
-
-*Description:* Returns the value of incoming traverser as strings, or if
`Scope.local` is specified, returns each element inside
-incoming list traverser as string.
-
-*Syntax:* `asString()` | `asString(Scope scope)`
-
-[width="100%",options="header"]
-|=========================================================
-|Start Step |Mid Step |Modulated |Domain |Range
-|N |Y |N |`any` |`String`/`List`
-|=========================================================
-
-*Arguments:*
-
-* `scope` - Determines the type of traverser it operates on. Both scopes will
operate on the level of individual traversers.
-The `global` scope will operate on individual traverser, casting all (except
`null`) to string. The `local` scope will behave like
-`global` for everything except lists, where it will cast individual non-`null`
elements inside the list into string and return a
-list of string instead.
-
-Null values from the incoming traverser are not processed and remain as null
when returned.
-
-*Exceptions*
-None
-
-See:
link:https://github.com/apache/tinkerpop/tree/x.y.z/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/AsStringGlobalStep.java[source],
-link:https://github.com/apache/tinkerpop/tree/x.y.z/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/AsStringLocalStep.java[source
(local)],
-link:https://tinkerpop.apache.org/docs/x.y.z/reference/#asString-step[reference]
-
[[call-step]]
=== call()
@@ -1119,6 +1120,76 @@
link:https://github.com/apache/tinkerpop/tree/x.y.z/gremlin-core/src/main/java/o
link:https://github.com/apache/tinkerpop/tree/x.y.z/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/service/ServiceRegistry.java[ServiceRegistry],
link:https://tinkerpop.apache.org/docs/x.y.z/reference/#call-step[reference]
+[[choose-step]]
+=== choose()
+
+*Description:* A branch step that routes the traverser to different paths
based on a choice criterion.
+
+*Syntax:* `choose(Traversal choiceTraversal)` | `choose(Traversal
choiceTraversal, Traversal trueChoice, Traversal falseChoice)` |
`choose(Function<Traverser, Object> choiceFunction)`
+
+[width="100%",options="header"]
+|=========================================================
+|Start Step |Mid Step |Modulated |Domain |Range
+|N |Y |Y |`any` |`any`
+|=========================================================
+
+*Arguments:*
+
+* `choiceTraversal` - A traversal that produces a value used to determine
which option to take.
+* `trueChoice` - The traversal to take if the predicate traversal returns a
value (has a next element).
+* `falseChoice` - The traversal to take if the predicate traversal returns no
value (has no next element).
+* `choiceFunction` - A function that takes a traverser and returns a value
used to determine which option to take.
+
+*Modulation:*
+
+* `option(pickToken, traversalOption)` - Adds a traversal option to the
`choose` step. The `pickToken` is matched
+against the result of the choice traversal. The `pickToken` may be a literal
value, a predicate `P`, a `Traversal`
+(whose first returned value is used for matching) or a `Pick` enum value. If a
match is found, the traverser is routed
+to the corresponding `traversalOption`.
+
+*Considerations:*
+
+The `choose()` step is a branch step that routes the traverser to different
paths based on a choice criterion. There are
+two main forms of the `choose()` step:
+
+1. *Predicate form*: `choose(predicate, trueChoice, falseChoice)` - This is
the `if-then-else` form. If the predicate
+traversal returns a value (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(choiceTraversal).option(pickValue,
resultTraversal)` - The choiceTraversal 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.
+
+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.
+
+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:
+
+[source,text]
+----
+gremlin> g.V().union(__.has("name", "vadas").values('age').fold(),
__.has('name',neq('vadas')).values('name').fold())
+==>[27]
+==>[marko,lop,josh,ripple,peter]
+gremlin> g.V().choose(__.has("name", "vadas"), __.values('age').fold(),
__.values('name').fold())
+==>[27]
+==>[marko,lop,josh,ripple,peter]
+----
+
+*Exceptions*
+
+* `IllegalArgumentException` - If `Pick.any` is used as an option token, as
only one option per traverser is allowed.
+* `IllegalArgumentException` - If the same pick token is used more than once.
+
+See:
link:https://github.com/apache/tinkerpop/tree/x.y.z/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/branch/ChooseStep.java[source],
+link:https://tinkerpop.apache.org/docs/x.y.z/reference/#choose-step[reference]
+
[[combine-step]]
=== combine()
diff --git
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Pick.java
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Pick.java
index 0569f8d9d9..b6c56e91d9 100644
---
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Pick.java
+++
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Pick.java
@@ -22,5 +22,18 @@ package org.apache.tinkerpop.gremlin.process.traversal;
* A token used with {@code option()}.
*/
public enum Pick {
- any, none, unproductive
+ /**
+ * Acceptable option for any match.
+ */
+ any,
+
+ /**
+ * The option to take when no match is found.
+ */
+ none,
+
+ /**
+ * If the incoming traverser is unproductive then match on this option.
+ */
+ unproductive
}
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 1cf374ad26..75692edeb2 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
@@ -358,4 +358,167 @@ Feature: Step - choose()
| marko |
| vadas |
| v[lop] |
- | v[ripple] |
\ No newline at end of file
+ | v[ripple] |
+
+ Scenario:
g_V_hasLabelXpersonX_chooseXoutXcreatedX_count_isXeqX0XX__constantXdidnt_createX__constantXcreatedXX
+ Given the modern graph
+ And the traversal of
+ """
+ g.V().hasLabel("person").
+ choose(__.out("created").count().is(P.eq(0)),
+ __.constant("didnt_create"),
+ __.constant("created"))
+ """
+ When iterated to list
+ Then the result should be unordered
+ | result |
+ | didnt_create |
+ | created |
+ | created |
+ | created |
+
+ Scenario:
g_V_hasLabelXpersonX_chooseXvaluesXageX_isXgtX30XX__valuesXageX__constantX30XX
+ Given the modern graph
+ And the traversal of
+ """
+ g.V().hasLabel("person").
+ choose(__.values("age").is(P.gt(30)),
+ __.values("age"),
+ __.constant(30))
+ """
+ When iterated to list
+ Then the result should be unordered
+ | result |
+ | d[30].i |
+ | d[30].i |
+ | d[32].i |
+ | d[35].i |
+
+ Scenario:
g_V_hasLabelXpersonX_chooseXvaluesXageX_isXgtX29XX_and_valuesXageX_isXltX35XX__valuesXnameX__constantXotherXX
+ Given the modern graph
+ And the traversal of
+ """
+ g.V().hasLabel("person").
+ choose(__.values("age").is(P.gt(29)).and().values("age").is(P.lt(35)),
+ __.values("name"),
+ __.constant("other"))
+ """
+ When iterated to list
+ Then the result should be unordered
+ | result |
+ | other |
+ | other |
+ | josh |
+ | other |
+
+ Scenario: g_V_chooseXhasXname_vadasX__valuesXnameX__valuesXageXX
+ Given the modern graph
+ And the traversal of
+ """
+ g.V().choose(__.has("name", "vadas"),
+ __.values("name"),
+ __.values("age"))
+ """
+ When iterated to list
+ Then the result should be unordered
+ | result |
+ | d[29].i |
+ | vadas |
+ | d[32].i |
+ | d[35].i |
+
+ Scenario:
g_V_hasLabelXpersonX_chooseXoutXcreatedX_countX_optionX0__constantXnoneXX_optionX1__constantXoneXX_optionX2__constantXmanyXX
+ Given the modern graph
+ And using the parameter xx0 defined as "d[0].l"
+ And using the parameter xx1 defined as "d[1].l"
+ And using the parameter xx2 defined as "d[2].l"
+ And the traversal of
+ """
+ g.V().hasLabel("person").
+ choose(__.out("created").count()).
+ option(xx0, __.constant("none")).
+ option(xx1, __.constant("one")).
+ option(xx2, __.constant("many"))
+ """
+ When iterated to list
+ Then the result should be unordered
+ | result |
+ | none |
+ | one |
+ | many |
+ | one |
+
+ Scenario:
g_V_hasLabelXpersonX_chooseXlocalXoutXknowsX_countX__optionX0__constantXnoFriendsXX__optionXnone__constantXhasFriendsXXX
+ Given the modern graph
+ And using the parameter xx0 defined as "d[0].l"
+ And the traversal of
+ """
+ g.V().hasLabel("person").
+ choose(__.local(__.out("knows").count())).
+ option(xx0, __.constant("noFriends")).
+ option(Pick.none, __.constant("hasFriends"))
+ """
+ When iterated to list
+ Then the result should be unordered
+ | result |
+ | hasFriends |
+ | noFriends |
+ | noFriends |
+ | noFriends |
+
+ Scenario:
g_V_chooseXoutE_countX_optionX0__constantXnoneXX_optionXnone__constantXsomeXX
+ Given the modern graph
+ And using the parameter xx0 defined as "d[0].l"
+ And the traversal of
+ """
+ g.V().choose(__.outE().count()).
+ option(xx0, __.constant("none")).
+ option(Pick.none, __.constant("some"))
+ """
+ When iterated to list
+ Then the result should be unordered
+ | result |
+ | some |
+ | none |
+ | some |
+ | some |
+ | none |
+ | none |
+
+ Scenario:
g_V_chooseXlabelX_optionXperson__chooseXageX_optionXP_lt_30__constantXyoungXX_optionXP_gte_30__constantXoldXXX_optionXsoftware__constantXprogramXX_optionXnone__constantXunknownXX
+ Given the modern graph
+ And the traversal of
+ """
+ g.V().choose(__.label()).
+ option("person", __.choose(__.values("age")).
+ option(P.lt(30), __.constant("young")).
+ option(P.gte(30), __.constant("old"))).
+ option("software", __.constant("program")).
+ option(Pick.none, __.constant("unknown"))
+ """
+ When iterated to list
+ Then the result should be unordered
+ | result |
+ | young |
+ | young |
+ | old |
+ | old |
+ | program |
+ | program |
+
+ Scenario: g_V_chooseXhasXname_vadasX__valuesXnameXX
+ Given the modern graph
+ And the traversal of
+ """
+ g.V().choose(__.has("name", "vadas"),
+ __.values("name"))
+ """
+ When iterated to list
+ Then the result should be unordered
+ | result |
+ | v[marko] |
+ | vadas |
+ | v[lop] |
+ | v[josh] |
+ | v[ripple] |
+ | v[peter] |
\ No newline at end of file