This is an automated email from the ASF dual-hosted git repository. kenhuuu pushed a commit to branch TINKERPOP-3218 in repository https://gitbox.apache.org/repos/asf/tinkerpop.git
commit 319d57588c6e42fece394368dffab27a3de5c076 Author: Ken Hu <[email protected]> AuthorDate: Fri Dec 19 13:50:01 2025 -0800 TINKERPOP-3218 Fix bug in pre-repeat emit()/until() --- .../process/traversal/step/branch/RepeatStep.java | 22 +++-- .../Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs | 5 ++ gremlin-go/driver/cucumber/gremlin.go | 5 ++ .../gremlin-javascript/test/cucumber/gremlin.js | 5 ++ gremlin-python/src/main/python/radish/gremlin.py | 5 ++ .../gremlin/test/features/branch/Repeat.feature | 98 ++++++++++++++++++++++ 6 files changed, 134 insertions(+), 6 deletions(-) diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/branch/RepeatStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/branch/RepeatStep.java index 74cb7851bb..36aac12847 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/branch/RepeatStep.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/branch/RepeatStep.java @@ -25,6 +25,7 @@ import org.apache.tinkerpop.gremlin.process.traversal.step.Barrier; import org.apache.tinkerpop.gremlin.process.traversal.step.TraversalParent; import org.apache.tinkerpop.gremlin.process.traversal.step.util.ComputerAwareStep; import org.apache.tinkerpop.gremlin.process.traversal.traverser.TraverserRequirement; +import org.apache.tinkerpop.gremlin.process.traversal.traverser.util.EmptyTraverser; import org.apache.tinkerpop.gremlin.process.traversal.util.FastNoSuchElementException; import org.apache.tinkerpop.gremlin.process.traversal.util.TraversalHelper; import org.apache.tinkerpop.gremlin.process.traversal.util.TraversalUtil; @@ -219,11 +220,20 @@ public final class RepeatStep<S> extends ComputerAwareStep<S, S> implements Trav // repeatTraversal before it is iterated so that RepeatStep always has "global" children. if (!this.starts.hasNext()) throw FastNoSuchElementException.instance(); + + final List<Traverser.Admin<S>> traversers = new ArrayList<>(); while (this.starts.hasNext()) { - processTraverser(this.starts.next()); + final Traverser.Admin<S> processedTraverser = processTraverser(this.starts.next()); + if (!processedTraverser.equals(EmptyTraverser.instance())) { + traversers.add(processedTraverser); + } } + if (!traversers.isEmpty()) return traversers.iterator(); } else { - return processTraverser(this.starts.next()); + final Traverser.Admin<S> processedTraverser = processTraverser(this.starts.next()); + if (!processedTraverser.equals(EmptyTraverser.instance())) { + return IteratorUtils.of(processedTraverser); + } } } } @@ -253,19 +263,19 @@ public final class RepeatStep<S> extends ComputerAwareStep<S, S> implements Trav } } - private Iterator<Traverser.Admin<S>> processTraverser(final Traverser.Admin<S> start) { + private Traverser.Admin<S> processTraverser(final Traverser.Admin<S> start) { start.initialiseLoops(this.getId(), this.loopName); if (doUntil(start, true)) { start.resetLoops(); - return IteratorUtils.of(start); + return start; } this.repeatTraversal.addStart(start); if (doEmit(start, true)) { final Traverser.Admin<S> emitSplit = start.split(); emitSplit.resetLoops(); - return IteratorUtils.of(emitSplit); + return emitSplit; } - return Collections.emptyIterator(); + return EmptyTraverser.instance(); } ///////////////////////// diff --git a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs index 4cfd7d1daf..0a6cfbc701 100644 --- a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs +++ b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs @@ -181,6 +181,11 @@ namespace Gremlin.Net.IntegrationTest.Gherkin {"g_VX3X_repeatXout_order_byXperformances_descX_limitX5X_tailX1XX_timesX2X_valuesXnameX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V(p["vid3"]).Repeat(__.Out().Order().By("performances", Order.Desc).Limit<object>(5).Tail<object>(1)).Times(2).Values<object>("name")}}, {"g_VX3X_repeatXoutE_order_byXweightX_tailX2X_inVX_timesX2X_valuesXnameX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V(p["vid3"]).Repeat(__.OutE().Order().By("weight").Tail<object>(2).InV()).Times(2).Values<object>("name")}}, {"g_VX3X_repeatXoutE_order_byXweight_descX_limitX2X_inVX_timesX2X_valuesXnameX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V(p["vid3"]).Repeat(__.OutE().Order().By("weight", Order.Desc).Limit<object>(2).InV()).Times(2).Values<object>("name")}}, + {"g_V_emit_repeatXout_order_byXnameXX_timesX2X_valuesXnameX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Emit().Repeat(__.Out().Order().By("name")).Times(2).Values<object>("name")}}, + {"g_V_emitXhasLabelXpersonXX_repeatXout_order_byXnameXX_timesX2X_valuesXnameX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Emit(__.HasLabel("person")).Repeat(__.Out().Order().By("name")).Times(2).Values<object>("name")}}, + {"g_V_untilXloops_isX2XX_repeatXout_order_byXnameXX_valuesXnameX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Until(__.Loops().Is(2)).Repeat(__.Out().Order().By("name")).Values<object>("name")}}, + {"g_V_emit_repeatXdedupX_timesX1X", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Emit().Repeat(__.Dedup()).Times(1)}}, + {"g_V_emit_repeatXdedupX_timesX2X", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Emit().Repeat(__.Dedup()).Times(2)}}, {"g_unionXX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Union<object>()}}, {"g_unionXV_name", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Union<object>(__.V().Values<object>("name"))}}, {"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")}}, diff --git a/gremlin-go/driver/cucumber/gremlin.go b/gremlin-go/driver/cucumber/gremlin.go index 2d7865a468..d975424664 100644 --- a/gremlin-go/driver/cucumber/gremlin.go +++ b/gremlin-go/driver/cucumber/gremlin.go @@ -151,6 +151,11 @@ var translationMap = map[string][]func(g *gremlingo.GraphTraversalSource, p map[ "g_VX3X_repeatXout_order_byXperformances_descX_limitX5X_tailX1XX_timesX2X_valuesXnameX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V(p["vid3"]).Repeat(gremlingo.T__.Out().Order().By("performances", gremlingo.Order.Desc).Limit(5).Tail(1)).Times(2).Values("name")}}, "g_VX3X_repeatXoutE_order_byXweightX_tailX2X_inVX_timesX2X_valuesXnameX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V(p["vid3"]).Repeat(gremlingo.T__.OutE().Order().By("weight").Tail(2).InV()).Times(2).Values("name")}}, "g_VX3X_repeatXoutE_order_byXweight_descX_limitX2X_inVX_timesX2X_valuesXnameX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V(p["vid3"]).Repeat(gremlingo.T__.OutE().Order().By("weight", gremlingo.Order.Desc).Limit(2).InV()).Times(2).Values("name")}}, + "g_V_emit_repeatXout_order_byXnameXX_timesX2X_valuesXnameX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Emit().Repeat(gremlingo.T__.Out().Order().By("name")).Times(2).Values("name")}}, + "g_V_emitXhasLabelXpersonXX_repeatXout_order_byXnameXX_timesX2X_valuesXnameX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Emit(gremlingo.T__.HasLabel("person")).Repeat(gremlingo.T__.Out().Order().By("name")).Times(2).Values("name")}}, + "g_V_untilXloops_isX2XX_repeatXout_order_byXnameXX_valuesXnameX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Until(gremlingo.T__.Loops().Is(2)).Repeat(gremlingo.T__.Out().Order().By("name")).Values("name")}}, + "g_V_emit_repeatXdedupX_timesX1X": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Emit().Repeat(gremlingo.T__.Dedup()).Times(1)}}, + "g_V_emit_repeatXdedupX_timesX2X": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Emit().Repeat(gremlingo.T__.Dedup()).Times(2)}}, "g_unionXX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Union()}}, "g_unionXV_name": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Union(gremlingo.T__.V().Values("name"))}}, "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")}}, 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 e2bb6f210d..e7e4dd0197 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 @@ -182,6 +182,11 @@ const gremlins = { g_VX3X_repeatXout_order_byXperformances_descX_limitX5X_tailX1XX_timesX2X_valuesXnameX: [function({g, vid3}) { return g.V(vid3).repeat(__.out().order().by("performances", Order.desc).limit(5).tail(1)).times(2).values("name") }], g_VX3X_repeatXoutE_order_byXweightX_tailX2X_inVX_timesX2X_valuesXnameX: [function({g, vid3}) { return g.V(vid3).repeat(__.outE().order().by("weight").tail(2).inV()).times(2).values("name") }], g_VX3X_repeatXoutE_order_byXweight_descX_limitX2X_inVX_timesX2X_valuesXnameX: [function({g, vid3}) { return g.V(vid3).repeat(__.outE().order().by("weight", Order.desc).limit(2).inV()).times(2).values("name") }], + g_V_emit_repeatXout_order_byXnameXX_timesX2X_valuesXnameX: [function({g}) { return g.V().emit().repeat(__.out().order().by("name")).times(2).values("name") }], + g_V_emitXhasLabelXpersonXX_repeatXout_order_byXnameXX_timesX2X_valuesXnameX: [function({g}) { return g.V().emit(__.hasLabel("person")).repeat(__.out().order().by("name")).times(2).values("name") }], + g_V_untilXloops_isX2XX_repeatXout_order_byXnameXX_valuesXnameX: [function({g}) { return g.V().until(__.loops().is(2)).repeat(__.out().order().by("name")).values("name") }], + g_V_emit_repeatXdedupX_timesX1X: [function({g}) { return g.V().emit().repeat(__.dedup()).times(1) }], + g_V_emit_repeatXdedupX_timesX2X: [function({g}) { return g.V().emit().repeat(__.dedup()).times(2) }], g_unionXX: [function({g}) { return g.union() }], g_unionXV_name: [function({g}) { return g.union(__.V().values("name")) }], g_unionXVXv1X_VX4XX_name: [function({g, vid4, vid1}) { return g.union(__.V(vid1), __.V(vid4)).values("name") }], diff --git a/gremlin-python/src/main/python/radish/gremlin.py b/gremlin-python/src/main/python/radish/gremlin.py index 94098b6c5c..d6456be0ac 100644 --- a/gremlin-python/src/main/python/radish/gremlin.py +++ b/gremlin-python/src/main/python/radish/gremlin.py @@ -154,6 +154,11 @@ world.gremlins = { 'g_VX3X_repeatXout_order_byXperformances_descX_limitX5X_tailX1XX_timesX2X_valuesXnameX': [(lambda g, vid3=None:g.V(vid3).repeat(__.out().order().by('performances', Order.desc).limit(5).tail(1)).times(2).values('name'))], 'g_VX3X_repeatXoutE_order_byXweightX_tailX2X_inVX_timesX2X_valuesXnameX': [(lambda g, vid3=None:g.V(vid3).repeat(__.out_e().order().by('weight').tail(2).in_v()).times(2).values('name'))], 'g_VX3X_repeatXoutE_order_byXweight_descX_limitX2X_inVX_timesX2X_valuesXnameX': [(lambda g, vid3=None:g.V(vid3).repeat(__.out_e().order().by('weight', Order.desc).limit(2).in_v()).times(2).values('name'))], + 'g_V_emit_repeatXout_order_byXnameXX_timesX2X_valuesXnameX': [(lambda g:g.V().emit().repeat(__.out().order().by('name')).times(2).values('name'))], + 'g_V_emitXhasLabelXpersonXX_repeatXout_order_byXnameXX_timesX2X_valuesXnameX': [(lambda g:g.V().emit(__.has_label('person')).repeat(__.out().order().by('name')).times(2).values('name'))], + 'g_V_untilXloops_isX2XX_repeatXout_order_byXnameXX_valuesXnameX': [(lambda g:g.V().until(__.loops().is_(2)).repeat(__.out().order().by('name')).values('name'))], + 'g_V_emit_repeatXdedupX_timesX1X': [(lambda g:g.V().emit().repeat(__.dedup()).times(1))], + 'g_V_emit_repeatXdedupX_timesX2X': [(lambda g:g.V().emit().repeat(__.dedup()).times(2))], 'g_unionXX': [(lambda g:g.union())], 'g_unionXV_name': [(lambda g:g.union(__.V().values('name')))], 'g_unionXVXv1X_VX4XX_name': [(lambda g, vid4=None,vid1=None:g.union(__.V(vid1), __.V(vid4)).values('name'))], diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/branch/Repeat.feature b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/branch/Repeat.feature index f0bc665737..07e6f23179 100644 --- a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/branch/Repeat.feature +++ b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/branch/Repeat.feature @@ -831,3 +831,101 @@ Feature: Step - repeat() | result | | SUGAR MAGNOLIA | | AROUND AND AROUND | + + Scenario: g_V_emit_repeatXout_order_byXnameXX_timesX2X_valuesXnameX + Given the modern graph + And the traversal of + """ + g.V().emit().repeat(__.out().order().by("name")).times(2).values("name") + """ + When iterated to list + Then the result should be unordered + | result | + | marko | + | josh | + | lop | + | ripple | + | lop | + | vadas | + | vadas | + | lop | + | josh | + | lop | + | ripple | + | ripple | + | peter | + | lop | + + Scenario: g_V_emitXhasLabelXpersonXX_repeatXout_order_byXnameXX_timesX2X_valuesXnameX + Given the modern graph + And the traversal of + """ + g.V().emit(__.hasLabel("person")).repeat(__.out().order().by("name")).times(2).values("name") + """ + When iterated to list + Then the result should be unordered + | result | + | marko | + | josh | + | lop | + | ripple | + | vadas | + | vadas | + | josh | + | peter | + + Scenario: g_V_untilXloops_isX2XX_repeatXout_order_byXnameXX_valuesXnameX + Given the modern graph + And the traversal of + """ + g.V().until(__.loops().is(2)).repeat(__.out().order().by("name")).values("name") + """ + When iterated to list + Then the result should be unordered + | result | + | lop | + | ripple | + + Scenario: g_V_emit_repeatXdedupX_timesX1X + Given the modern graph + And the traversal of + """ + g.V().emit().repeat(__.dedup()).times(1) + """ + When iterated to list + Then the result should be unordered + | result | + | v[marko] | + | v[marko] | + | v[vadas] | + | v[vadas] | + | v[lop] | + | v[lop] | + | v[josh] | + | v[josh] | + | v[ripple] | + | v[ripple] | + | v[peter] | + | v[peter] | + + Scenario: g_V_emit_repeatXdedupX_timesX2X + Given the modern graph + And the traversal of + """ + g.V().emit().repeat(__.dedup()).times(2) + """ + When iterated to list + Then the result should be unordered + | result | + | v[marko] | + | v[marko] | + | v[vadas] | + | v[vadas] | + | v[lop] | + | v[lop] | + | v[josh] | + | v[josh] | + | v[ripple] | + | v[ripple] | + | v[peter] | + | v[peter] |
