Skip to content

Commit a1f1680

Browse files
committed
Merge branch '3.7-dev' into 3.8-dev
2 parents 3d7960d + a20f386 commit a1f1680

File tree

7 files changed

+134
-2
lines changed

7 files changed

+134
-2
lines changed

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

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1999,6 +1999,79 @@ from the resulting `Map`.
19991999
See: link:https://github.com/apache/tinkerpop/tree/x.y.z/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/ProjectStep.java[source],
20002000
link:https://tinkerpop.apache.org/docs/x.y.z/reference/#project-step[reference]
20012001
2002+
[[repeat-step]]
2003+
=== repeat()
2004+
2005+
*Description:* Iteratively applies a traversal (the "loop body") to each incoming traverser until a stopping
2006+
condition is met. Optionally, it can emit traversers on each iteration according to an emit predicate. The
2007+
repeat step supports loop naming and a loop counter via `loops()`.
2008+
2009+
*Syntax:* `repeat(Traversal repeatTraversal)` | `repeat(String loopName, Traversal repeatTraversal)`
2010+
2011+
[width="100%",options="header"]
2012+
|=========================================================
2013+
|Start Step |Mid Step |Modulated |Domain |Range
2014+
|N |Y |`emit()`, `until()`, `times()` |`any` |`any`
2015+
|=========================================================
2016+
2017+
*Arguments:*
2018+
2019+
* `repeatTraversal` - The traversal that represents the loop body to apply on each iteration.
2020+
* `loopName` - Optional name used to identify the loop for nested loops and to access a specific counter via
2021+
`loops(loopName)`.
2022+
2023+
*Modulation:*
2024+
2025+
* `emit()` | `emit(Traversal<?, ?> emitTraversal)` | `emit(Predicate<Traverser<?>> emitPredicate)` - Controls if/when a
2026+
traverser is emitted to the downstream of repeat() in addition to being looped again. If supplied before `repeat(...)`
2027+
the predicate is evaluated prior to the first iteration (pre-emit). If supplied after `repeat(...)`, the predicate is
2028+
evaluated after each completed iteration (post-emit). Calling `emit()` without arguments is equivalent to a predicate
2029+
that always evaluates to true at the given check position.
2030+
* `until(Traversal<?, ?> untilTraversal)` | `until(Predicate<Traverser<?>> untilPredicate)` - Controls when repetition
2031+
stops. If supplied before `repeat(...)` the predicate is evaluated prior to the first iteration (pre-check). If the
2032+
predicate is true, the traverser will pass downstream without any loop iteration. If supplied after `repeat(...)`, the
2033+
predicate is evaluated after each completed iteration (post-check). When the predicate is true, the traverser stops
2034+
repeating and passes downstream.
2035+
* `times(int n)` - Convenience for a loop bound. Equivalent to `until(loops().is(n))` when placed after `repeat(...)`
2036+
(post-check), and equivalent to `until(loops().is(n))` placed before `repeat(...)` (pre-check) when specified before.
2037+
See Considerations for details and examples.
2038+
2039+
*Considerations:*
2040+
2041+
- Evaluation order matters. The placement of `emit()` and `until()` relative to `repeat()` controls whether their
2042+
predicates are evaluated before the first iteration (pre) or after each iteration (post) allowing for `while/do` or
2043+
`do/while` semantics respectively:
2044+
- Pre-check / pre-emit: when the modulator appears before `repeat(...)`.
2045+
- Post-check / post-emit: when the modulator appears after `repeat(...)`.
2046+
- Loop counter semantics:
2047+
- The loop counter for a given named or unnamed repeat is incremented once per completion of the loop body (i.e.,
2048+
after the body finishes), not before. Therefore, `loops()` reflects the number of completed iterations.
2049+
- `loops()` without arguments returns the counter for the closest (innermost) `repeat()`. `loops("name")` returns the
2050+
counter for the named loop.
2051+
- Re-queuing for the next iteration:
2052+
- After each iteration, if `until` is not satisfied at the post-check, the traverser is sent back into the loop body
2053+
for another iteration. If it is satisfied, the traverser exits the loop and proceeds downstream.
2054+
- Interaction of `times(n)`:
2055+
- `g.V().repeat(x).times(2)` applies `x` exactly twice; no values are emitted unless `emit()` is specified.
2056+
- `g.V().emit().repeat(x).times(2)` emits the original input (pre-emit) and then the results of each iteration.
2057+
- Placing `times(0)` before `repeat(...)` yields no iterations and passes the input downstream unchanged.
2058+
- Placing `times(0)` after `repeat(...)` yields the same as `times(1)` because of `do/while` semantics.
2059+
- Errors when `repeatTraversal` is missing:
2060+
- Using `emit()`, `until()`, or `times()` without an associated `repeat()` will raise an error at iteration time with a
2061+
message containing: `The repeat()-traversal was not defined`.
2062+
- Nested repeats and loop names:
2063+
- Nested `repeat()` steps maintain separate loop counters. Use `repeat("a", ...)` and `loops("a")` to reference a
2064+
specific counter inside nested loops.
2065+
2066+
*Exceptions*
2067+
2068+
* Using `emit()`, `until()`, or `times()` without a matching `repeat()` will raise an `IllegalStateException` at runtime
2069+
when the step is initialized during iteration with the message containing: `The repeat()-traversal was not defined`.
2070+
2071+
See: link:https://github.com/apache/tinkerpop/tree/x.y.z/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/branch/RepeatStep.java[source],
2072+
link:https://tinkerpop.apache.org/docs/x.y.z/reference/#repeat-step[reference],
2073+
link:https://github.com/apache/tinkerpop/tree/x.y.z/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/branch/Repeat.feature[tests]
2074+
20022075
[[replace-step]]
20032076
=== replace()
20042077

gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/service/ServiceRegistry.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@
2525
import org.apache.tinkerpop.shaded.jackson.core.JsonProcessingException;
2626
import org.apache.tinkerpop.shaded.jackson.databind.ObjectMapper;
2727

28-
import java.util.HashMap;
2928
import java.util.LinkedHashMap;
3029
import java.util.Map;
3130
import java.util.Objects;

gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,10 @@ private static IDictionary<string, List<Func<GraphTraversalSource, IDictionary<s
147147
{"g_V_emit", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Emit()}},
148148
{"g_V_untilXidentityX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Until(__.Identity())}},
149149
{"g_V_timesX5X", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Times(5)}},
150+
{"g_V_haxXperson_name_markoX_repeatXoutXcreatedXX_timesX1X_name", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Has("person", "name", "marko").Repeat(__.Out("created")).Times(1).Values<object>("name")}},
151+
{"g_V_haxXperson_name_markoX_repeatXoutXcreatedXX_timesX0X_name", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Has("person", "name", "marko").Repeat(__.Out("created")).Times(0).Values<object>("name")}},
152+
{"g_V_haxXperson_name_markoX_timesX1X_repeatXoutXcreatedXX_name", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Has("person", "name", "marko").Times(1).Repeat(__.Out("created")).Values<object>("name")}},
153+
{"g_V_haxXperson_name_markoX_timesX0X_repeatXoutXcreatedXX_name", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Has("person", "name", "marko").Times(0).Repeat(__.Out("created")).Values<object>("name")}},
150154
{"g_unionXX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Union<object>()}},
151155
{"g_unionXV_name", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Union<object>(__.V().Values<object>("name"))}},
152156
{"g_unionXVXv1X_VX4XX_name", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Union<object>(__.V(p["vid1"]), __.V(p["vid4"])).Values<object>("name")}},

gremlin-go/driver/cucumber/gremlin.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,10 @@ var translationMap = map[string][]func(g *gremlingo.GraphTraversalSource, p map[
117117
"g_V_emit": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Emit()}},
118118
"g_V_untilXidentityX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Until(gremlingo.T__.Identity())}},
119119
"g_V_timesX5X": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Times(5)}},
120+
"g_V_haxXperson_name_markoX_repeatXoutXcreatedXX_timesX1X_name": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Has("person", "name", "marko").Repeat(gremlingo.T__.Out("created")).Times(1).Values("name")}},
121+
"g_V_haxXperson_name_markoX_repeatXoutXcreatedXX_timesX0X_name": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Has("person", "name", "marko").Repeat(gremlingo.T__.Out("created")).Times(0).Values("name")}},
122+
"g_V_haxXperson_name_markoX_timesX1X_repeatXoutXcreatedXX_name": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Has("person", "name", "marko").Times(1).Repeat(gremlingo.T__.Out("created")).Values("name")}},
123+
"g_V_haxXperson_name_markoX_timesX0X_repeatXoutXcreatedXX_name": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Has("person", "name", "marko").Times(0).Repeat(gremlingo.T__.Out("created")).Values("name")}},
120124
"g_unionXX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Union()}},
121125
"g_unionXV_name": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Union(gremlingo.T__.V().Values("name"))}},
122126
"g_unionXVXv1X_VX4XX_name": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Union(gremlingo.T__.V(p["vid1"]), gremlingo.T__.V(p["vid4"])).Values("name")}},

gremlin-javascript/src/main/javascript/gremlin-javascript/test/cucumber/gremlin.js

Lines changed: 4 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

gremlin-python/src/main/python/radish/gremlin.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,10 @@
120120
'g_V_emit': [(lambda g:g.V().emit())],
121121
'g_V_untilXidentityX': [(lambda g:g.V().until(__.identity()))],
122122
'g_V_timesX5X': [(lambda g:g.V().times(5))],
123+
'g_V_haxXperson_name_markoX_repeatXoutXcreatedXX_timesX1X_name': [(lambda g:g.V().has('person', 'name', 'marko').repeat(__.out('created')).times(1).values('name'))],
124+
'g_V_haxXperson_name_markoX_repeatXoutXcreatedXX_timesX0X_name': [(lambda g:g.V().has('person', 'name', 'marko').repeat(__.out('created')).times(0).values('name'))],
125+
'g_V_haxXperson_name_markoX_timesX1X_repeatXoutXcreatedXX_name': [(lambda g:g.V().has('person', 'name', 'marko').times(1).repeat(__.out('created')).values('name'))],
126+
'g_V_haxXperson_name_markoX_timesX0X_repeatXoutXcreatedXX_name': [(lambda g:g.V().has('person', 'name', 'marko').times(0).repeat(__.out('created')).values('name'))],
123127
'g_unionXX': [(lambda g:g.union())],
124128
'g_unionXV_name': [(lambda g:g.union(__.V().values('name')))],
125129
'g_unionXVXv1X_VX4XX_name': [(lambda g, vid4=None,vid1=None:g.union(__.V(vid1), __.V(vid4)).values('name'))],

gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/branch/Repeat.feature

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -385,4 +385,48 @@ Feature: Step - repeat()
385385
g.V().times(5)
386386
"""
387387
When iterated to list
388-
Then the traversal will raise an error with message containing text of "The repeat()-traversal was not defined"
388+
Then the traversal will raise an error with message containing text of "The repeat()-traversal was not defined"
389+
390+
Scenario: g_V_haxXperson_name_markoX_repeatXoutXcreatedXX_timesX1X_name
391+
Given the modern graph
392+
And the traversal of
393+
"""
394+
g.V().has("person","name","marko").repeat(__.out("created")).times(1).values("name")
395+
"""
396+
When iterated to list
397+
Then the result should be unordered
398+
| result |
399+
| lop |
400+
401+
Scenario: g_V_haxXperson_name_markoX_repeatXoutXcreatedXX_timesX0X_name
402+
Given the modern graph
403+
And the traversal of
404+
"""
405+
g.V().has("person","name","marko").repeat(out("created")).times(0).values("name")
406+
"""
407+
When iterated to list
408+
Then the result should be unordered
409+
| result |
410+
| lop |
411+
412+
Scenario: g_V_haxXperson_name_markoX_timesX1X_repeatXoutXcreatedXX_name
413+
Given the modern graph
414+
And the traversal of
415+
"""
416+
g.V().has("person","name","marko").times(1).repeat(out("created")).values("name")
417+
"""
418+
When iterated to list
419+
Then the result should be unordered
420+
| result |
421+
| lop |
422+
423+
Scenario: g_V_haxXperson_name_markoX_timesX0X_repeatXoutXcreatedXX_name
424+
Given the modern graph
425+
And the traversal of
426+
"""
427+
g.V().has("person","name","marko").times(0).repeat(out("created")).values("name")
428+
"""
429+
When iterated to list
430+
Then the result should be unordered
431+
| result |
432+
| marko |

0 commit comments

Comments
 (0)