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

Reply via email to