Skip to content

Commit aa15e39

Browse files
committed
Merge branch '3.8-dev'
2 parents 16bc514 + df775b0 commit aa15e39

File tree

20 files changed

+1127
-76
lines changed

20 files changed

+1127
-76
lines changed

CHANGELOG.asciidoc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,11 @@ This release also includes changes from <<release-3-7-XXX, 3.7.XXX>>.
129129
* Added integer overflow checks.
130130
* Added missing strategies to the `TraversalStrategies` global cache as well as `CoreImports` in `gremlin-groovy`.
131131
* Added missing strategies to `strategies.py` in `gremlin-python`.
132+
* Preferred use of `TokenTraversal` when using `T` with `choose` instead of `LambdaMapTraversal` which treats `T` more abstractly as a `Function`.
133+
* Preferred use of `is()` when using `P` with `choose` instead of a `PredicateTraverser` which allows `P` to be used more concretely rather than as a `Function`.
134+
* Changed `choose` to only use the first `option` matched.
135+
* Added `Pick.unproductive` to allow for matches on unproductive predicates.
136+
* Changed `choose` to pass through traversers of unproductive predicates and unmatched choices.
132137
* Updated `OptionsStrategy` in `gremlin-python` to take options directly as keyword arguments.
133138
* Added static `instance()` method to `ElementIdStrategy` to an instance with the default configuration.
134139
* Updated `ElementIdStrategy.getConfiguration()` to help with serialization.

docs/src/dev/provider/gremlin-semantics.asciidoc

Lines changed: 92 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -827,6 +827,37 @@ Incoming date remains unchanged.
827827
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],
828828
link:https://tinkerpop.apache.org/docs/x.y.z/reference/#asDate-step[reference]
829829
830+
[[asString-step]]
831+
=== asString()
832+
833+
*Description:* Returns the value of incoming traverser as strings, or if `Scope.local` is specified, returns each element inside
834+
incoming list traverser as string.
835+
836+
*Syntax:* `asString()` | `asString(Scope scope)`
837+
838+
[width="100%",options="header"]
839+
|=========================================================
840+
|Start Step |Mid Step |Modulated |Domain |Range
841+
|N |Y |N |`any` |`String`/`List`
842+
|=========================================================
843+
844+
*Arguments:*
845+
846+
* `scope` - Determines the type of traverser it operates on. Both scopes will operate on the level of individual traversers.
847+
The `global` scope will operate on individual traverser, casting all (except `null`) to string. The `local` scope will behave like
848+
`global` for everything except lists, where it will cast individual non-`null` elements inside the list into string and return a
849+
list of string instead.
850+
851+
Null values from the incoming traverser are not processed and remain as null when returned.
852+
853+
*Exceptions*
854+
None
855+
856+
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],
857+
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)],
858+
link:https://tinkerpop.apache.org/docs/x.y.z/reference/#asString-step[reference]
859+
860+
830861
[[barrier-step]]
831862
=== barrier()
832863
@@ -1030,36 +1061,6 @@ None
10301061
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],
10311062
link:https://tinkerpop.apache.org/docs/x.y.z/reference/#cap-step[reference]
10321063
1033-
[[asString-step]]
1034-
=== asString()
1035-
1036-
*Description:* Returns the value of incoming traverser as strings, or if `Scope.local` is specified, returns each element inside
1037-
incoming list traverser as string.
1038-
1039-
*Syntax:* `asString()` | `asString(Scope scope)`
1040-
1041-
[width="100%",options="header"]
1042-
|=========================================================
1043-
|Start Step |Mid Step |Modulated |Domain |Range
1044-
|N |Y |N |`any` |`String`/`List`
1045-
|=========================================================
1046-
1047-
*Arguments:*
1048-
1049-
* `scope` - Determines the type of traverser it operates on. Both scopes will operate on the level of individual traversers.
1050-
The `global` scope will operate on individual traverser, casting all (except `null`) to string. The `local` scope will behave like
1051-
`global` for everything except lists, where it will cast individual non-`null` elements inside the list into string and return a
1052-
list of string instead.
1053-
1054-
Null values from the incoming traverser are not processed and remain as null when returned.
1055-
1056-
*Exceptions*
1057-
None
1058-
1059-
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],
1060-
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)],
1061-
link:https://tinkerpop.apache.org/docs/x.y.z/reference/#asString-step[reference]
1062-
10631064
[[call-step]]
10641065
=== call()
10651066
@@ -1145,6 +1146,67 @@ link:https://github.com/apache/tinkerpop/tree/x.y.z/gremlin-core/src/main/java/o
11451146
link:https://github.com/apache/tinkerpop/tree/x.y.z/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/service/ServiceRegistry.java[ServiceRegistry],
11461147
link:https://tinkerpop.apache.org/docs/x.y.z/reference/#call-step[reference]
11471148
1149+
[[choose-step]]
1150+
=== choose()
1151+
1152+
*Description:* A branch step that routes the traverser to different paths based on a choice criterion.
1153+
1154+
*Syntax:* `choose(Traversal|T choice)` | `choose(Traversal|P choice, Traversal trueChoice)` |`choose(Traversal|P choice, Traversal trueChoice, Traversal falseChoice)`
1155+
1156+
[width="100%",options="header"]
1157+
|=========================================================
1158+
|Start Step |Mid Step |Modulated |Domain |Range
1159+
|N |Y |Y |`any` |`any`
1160+
|=========================================================
1161+
1162+
*Arguments:*
1163+
1164+
* `choice` - A `Traversal`, or `T` that produces a value used to determine which option to take. In the `if-then` forms, this value may be a `P` to determine `true` or `false`.
1165+
* `trueChoice` - The traversal to take if the predicate traversal returns a value (has a next element).
1166+
* `falseChoice` - The traversal to take if the predicate traversal returns no value (has no next element).
1167+
1168+
*Modulation:*
1169+
1170+
* `option(pickToken, traversalOption)` - Adds a traversal option to the `choose` step. The `pickToken` is matched
1171+
against the result of the choice traversal. The `pickToken` may be a literal value, a predicate `P`, a `Traversal`
1172+
(whose first returned value is used for matching) or a `Pick` enum value. If a match is found, the traverser is routed
1173+
to the corresponding `traversalOption`.
1174+
1175+
*Considerations:*
1176+
1177+
The `choose()` step is a branch step that routes the traverser to different paths based on a choice criterion. There are
1178+
two main forms of the `choose()` step:
1179+
1180+
1. *if-then form*: `choose(predicate, trueChoice, falseChoice)` - If the predicate traversal or `P` returns a value
1181+
(has a next element), the traverser is routed to the trueChoice traversal. Otherwise, it is routed to the falseChoice
1182+
traversal. If the predicate is unproductive or if the falseChoice is not specified, then the traverser passes through.
1183+
1184+
2. *switch form*: `choose(choice).option(pickValue, resultTraversal)` - The choice which may be a `Traversal` or
1185+
`T` produces a value that is matched against the pickValue of each option. If a match is found, the traverser is routed
1186+
to the corresponding resultTraversal and no further matches are attempted. If no match is found then the traverser
1187+
passes through by default or can be matched on `Pick.none`. If the choiceTraversal is unproductive, then the traverser
1188+
passes through by default or can be matched on `Pick.unproductive`.
1189+
1190+
`choose` does not allow more than one traversal to be assigned to a single `Pick`. The first `Pick` assigned via
1191+
`option` is the one that will be used, similar to how the first pickValue match that is found is used.
1192+
1193+
The `choose()` step ensures that only one option is selected for each traverser, unlike other branch steps like
1194+
`union()` that can route a traverser to multiple paths. As it is like `union()`, note that each `option` stream will
1195+
behave like one:
1196+
1197+
[gremlin-groovy,modern]
1198+
----
1199+
g.V().union(__.has("name", "vadas").values('age').fold(), __.has('name',neq('vadas')).values('name').fold())
1200+
g.V().choose(__.has("name", "vadas"), __.values('age').fold(), __.values('name').fold())
1201+
----
1202+
1203+
*Exceptions*
1204+
1205+
* `IllegalArgumentException` - If `Pick.any` is used as an option token, as only one option per traverser is allowed.
1206+
1207+
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],
1208+
link:https://tinkerpop.apache.org/docs/x.y.z/reference/#choose-step[reference]
1209+
11481210
[[combine-step]]
11491211
=== combine()
11501212

docs/src/reference/the-traversal.asciidoc

Lines changed: 98 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1081,24 +1081,33 @@ link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gre
10811081
image::choose-step.png[width=700]
10821082
10831083
The `choose()`-step (*branch*) routes the current traverser to a particular traversal branch option. With `choose()`,
1084-
it is possible to implement if/then/else-semantics as well as more complicated selections.
1084+
it is possible to implement two different types of semantics: if-then-else (conditional branching) and switch
1085+
(value-based selection).
1086+
1087+
==== If-Then-Else
1088+
1089+
The if-the-else semantics of `choose()` evaluate a predicate traversal and route the traverser to either the "true"
1090+
branch or the "false" branch based on the result.
10851091
10861092
[gremlin-groovy,modern]
10871093
----
10881094
g.V().hasLabel('person').
10891095
choose(values('age').is(lte(30)),
1090-
__.in(),
1091-
__.out()).values('name') <1>
1096+
__.in(),
1097+
__.out()).values('name') <1>
10921098
g.V().hasLabel('person').
1093-
choose(values('age')).
1094-
option(27, __.in()).
1095-
option(32, __.out()).values('name') <2>
1099+
choose(outE('knows').count().is(gt(0)),
1100+
__.out('knows'),
1101+
__.identity()).values('name') <2>
10961102
----
10971103
1098-
<1> If the traversal yields an element, then do `in`, else do `out` (i.e. true/false-based option selection).
1099-
<2> Use the result of the traversal as a key to the map of traversal options (i.e. value-based option selection).
1104+
<1> If the person's age is less than or equal to 30, then traverse to incoming vertices, else traverse to outgoing
1105+
vertices.
1106+
<2> If the person has outgoing "knows" edges, then traverse to those known vertices, else return the person vertex
1107+
itself.
11001108
1101-
If the "false"-branch is not provided, then if/then-semantics are implemented.
1109+
If the "false"-branch is not provided, then simple if-then-semantics are implemented, where traversers that don't match
1110+
the condition are passed through unchanged.
11021111
11031112
[gremlin-groovy,modern]
11041113
----
@@ -1107,9 +1116,12 @@ g.V().choose(hasLabel('person'), out('created'), identity()).values('name') <2>
11071116
----
11081117
11091118
<1> If the vertex is a person, emit the vertices they created, else emit the vertex.
1110-
<2> If/then/else with an `identity()` on the false-branch is equivalent to if/then with no false-branch.
1119+
<2> if-the-else with an `identity()` on the false-branch is equivalent to if-then with no false-branch.
11111120
1112-
Note that `choose()` can have an arbitrary number of options and moreover, can take an anonymous traversal as its choice function.
1121+
==== Switch
1122+
1123+
The switch semantics of `choose()` use the result of a traversal as a key to select from multiple traversal options.
1124+
This allows for more complex branching logic beyond simple true/false conditions.
11131125
11141126
[gremlin-groovy,modern]
11151127
----
@@ -1118,19 +1130,89 @@ g.V().hasLabel('person').
11181130
option('marko', values('age')).
11191131
option('josh', values('name')).
11201132
option('vadas', elementMap()).
1121-
option('peter', label())
1133+
option('peter', label()) <1>
1134+
g.V().hasLabel('person').
1135+
choose(values('age')).
1136+
option(27, __.in().values('name')).
1137+
option(32, __.out().values('name')) <2>
11221138
----
11231139
1124-
The `choose()`-step can leverage the `Pick.none` option match. For anything that does not match a specified option, the `none`-option is taken.
1140+
<1> Use the person's name to select which property or operation to return.
1141+
<2> Use the person's age value to select which traversal to apply, noting that traversers matching no age values simply
1142+
pass through.
1143+
1144+
The `choose()`-step can use predicates with options to match ranges of values or other conditions.
11251145
11261146
[gremlin-groovy,modern]
11271147
----
11281148
g.V().hasLabel('person').
1129-
choose(values('name')).
1130-
option('marko', values('age')).
1131-
option(none, values('name'))
1149+
choose(values('age')).
1150+
option(P.between(26, 30), constant('younger')).
1151+
option(P.gt(30), constant('older')).
1152+
option(Pick.none, constant('unknown')) <1>
1153+
----
1154+
1155+
<1> If the person's age is between 26 and 30, classify them as 'younger', if greater than 30, classify as 'older',
1156+
otherwise 'unknown'.
1157+
1158+
The token `T.label` can be used as shorthand for `__.label()` when selecting options based on element labels.
1159+
1160+
[gremlin-groovy,modern]
1161+
----
1162+
g.V().choose(T.label).
1163+
option('person', out('created')).
1164+
option('software', in('created')).
1165+
values('name') <1>
11321166
----
11331167
1168+
<1> For person vertices, traverse to the software they created; for software vertices, traverse to the people who
1169+
created them.
1170+
1171+
The `Pick` enum was introduced in an example earlier to handle non-matching scenarios. The following `Pick` options may
1172+
be used with `choose()`:
1173+
1174+
* `Pick.none` - Matches when no other options match
1175+
* `Pick.unproductive` - Matches when the choice in `choose()` produces no results
1176+
1177+
[gremlin-groovy,modern]
1178+
----
1179+
g.V().choose(values('age')).
1180+
option(P.between(26, 30), values('name')).
1181+
option(Pick.none, values('name')).
1182+
option(Pick.unproductive, label()) <1>
1183+
g.V().hasLabel('person').
1184+
choose(out('knows').count()).
1185+
option(0, constant('noFriends')).
1186+
option(Pick.none, constant('hasFriends')) <2>
1187+
g.V().choose(values('age')).
1188+
option(27, __.in().values('name')).
1189+
option(32, __.out().values('name')).
1190+
option(Pick.unproductive, discard()).
1191+
option(Pick.none, discard()) <3>
1192+
----
1193+
1194+
<1> For vertices with age between 26-30, return the name. For vertices with age outside that range, return the name.
1195+
For vertices without an age property, return the label.
1196+
<2> For people with no outgoing "knows" edges, return 'noFriends', otherwise return 'hasFriends'.
1197+
<3> Use `none()` step in combination with `Pick.none` and `Pick.unproductive` to filter unproductive traversals and
1198+
unmatched values.
1199+
1200+
IMPORTANT: It is important to think of `choose()` as a branching step and not a filter. The if-then semantics can
1201+
intuitively lead to thinking the latter, where no match would mean to remove the traverser from the stream. As shown in
1202+
the examples, this is not what happens.
1203+
1204+
The `choose()`-step can be used within a `map()` step to apply the branching logic to each element in a collection.
1205+
1206+
[gremlin-groovy,modern]
1207+
----
1208+
g.V().hasLabel('person').
1209+
map(choose(values('age')).
1210+
option(P.between(26, 30), values('name').fold()).
1211+
option(Pick.none, values('name').fold())) <1>
1212+
----
1213+
1214+
<1> For each person, create a list containing their name, using the same traversal regardless of age.
1215+
11341216
*Additional References*
11351217
11361218
link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#choose(java.util.function.Function)++[`choose(Function)`],

0 commit comments

Comments
 (0)