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 f62b22c1f34..249e95f6ed8 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 @@ -19,6 +19,7 @@ package org.apache.tinkerpop.gremlin.process.traversal.step.branch; import org.apache.tinkerpop.gremlin.process.traversal.Step; +import org.apache.tinkerpop.gremlin.process.traversal.step.Barrier; import org.apache.tinkerpop.gremlin.process.traversal.Traversal; import org.apache.tinkerpop.gremlin.process.traversal.Traverser; import org.apache.tinkerpop.gremlin.process.traversal.step.TraversalParent; @@ -31,6 +32,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; +import java.util.LinkedList; import java.util.List; import java.util.NoSuchElementException; import java.util.Set; @@ -187,17 +189,40 @@ public int hashCode() { return result; } + private final LinkedList> stashedStarts = new LinkedList<>(); + + private Traverser.Admin nextStart(final boolean useDfs) { + if (!useDfs) { + return this.starts.next(); + } else { + if (this.starts.hasNext()) { + return this.starts.next(); + } else { + return this.stashedStarts.pop(); + } + } + } + @Override protected Iterator> standardAlgorithm() throws NoSuchElementException { if (null == this.repeatTraversal) throw new IllegalStateException("The repeat()-traversal was not defined: " + this); - + final List steps = this.repeatTraversal.getSteps(); + final Step stepBeforeRepeatEndStep = steps.get(steps.size() - 2); + final boolean useDfs = !(stepBeforeRepeatEndStep instanceof Barrier); while (true) { if (this.repeatTraversal.getEndStep().hasNext()) { return this.repeatTraversal.getEndStep(); } else { - final Traverser.Admin start = this.starts.next(); + final Traverser.Admin start = nextStart(useDfs); start.initialiseLoops(this.getId(), this.loopName); + if (useDfs) { + final List> localStarts = new ArrayList<>(); + while (this.starts.hasNext()) { + localStarts.add(this.starts.next()); + } + stashedStarts.addAll(0, localStarts); + } if (doUntil(start, true)) { start.resetLoops(); return IteratorUtils.of(start); @@ -240,10 +265,12 @@ protected Iterator> computerAlgorithm() throws NoSuchElementE public static > C addRepeatToTraversal(final C traversal, final Traversal.Admin repeatTraversal) { final Step step = traversal.asAdmin().getEndStep(); + boolean setBfs = false; if (step instanceof RepeatStep && null == ((RepeatStep) step).repeatTraversal) { ((RepeatStep) step).setRepeatTraversal(repeatTraversal); } else { final RepeatStep repeatStep = new RepeatStep<>(traversal.asAdmin()); + List steps = repeatTraversal.getSteps(); repeatStep.setRepeatTraversal(repeatTraversal); traversal.asAdmin().addStep(repeatStep); } @@ -289,11 +316,41 @@ public RepeatEndStep(final Traversal.Admin traversal) { super(traversal); } + final LinkedList> stashedStarts = new LinkedList<>(); + + private Traverser.Admin nextStart(RepeatStep repeatStep, boolean useDfs) { + if (!useDfs) { + return this.starts.next(); + } else { + if (this.starts.hasNext()) { + return this.starts.next(); + } else { + return this.stashedStarts.pop(); + } + } + } + + @Override + public boolean hasNext() { + return super.hasNext() || !this.stashedStarts.isEmpty(); + } + @Override protected Iterator> standardAlgorithm() throws NoSuchElementException { final RepeatStep repeatStep = (RepeatStep) this.getTraversal().getParent(); + final List steps = repeatStep.repeatTraversal.getSteps(); + final Step stepBeforeRepeatEndStep = steps.get(steps.size() - 2); + final boolean useDfs = !(stepBeforeRepeatEndStep instanceof Barrier); while (true) { - final Traverser.Admin start = this.starts.next(); + final Traverser.Admin start = nextStart(repeatStep, useDfs); + start.incrLoops(); + if (useDfs) { + final List> localStarts = new ArrayList<>(); + while (this.starts.hasNext()) { + localStarts.add(this.starts.next()); + } + stashedStarts.addAll(0, localStarts); + } start.incrLoops(); if (repeatStep.doUntil(start, false)) { start.resetLoops(); diff --git a/gremlin-test/features/branch/Repeat.feature b/gremlin-test/features/branch/Repeat.feature index 33470d0a90c..139e7430de9 100644 --- a/gremlin-test/features/branch/Repeat.feature +++ b/gremlin-test/features/branch/Repeat.feature @@ -354,3 +354,17 @@ Scenario: g_VX6X_repeatXa_bothXcreatedX_simplePathX_emitXrepeatXb_bothXknowsXX_u Then the result should be unordered | result | | josh | + Scenario: g_V_hasXname_markoX_repeatXoutE_order_byXweight_decrX_inVX_emit + Given the modern graph + And the traversal of + """ + g.V().has("name", "marko").repeat(__.outE().order().by("weight", Order.decr).inV()).emit() + """ + When iterated to list + Then the result should be ordered + | result | + | josh | + | ripple | + | lop | + | vadas | + | lop | diff --git a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/branch/RepeatTest.java b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/branch/RepeatTest.java index e833acfd2ae..94465f0e74b 100644 --- a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/branch/RepeatTest.java +++ b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/branch/RepeatTest.java @@ -47,6 +47,7 @@ import static org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__.loops; import static org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__.out; import static org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__.outE; +import static org.apache.tinkerpop.gremlin.process.traversal.Order.decr; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.core.Is.is; import static org.hamcrest.collection.IsIterableContainingInOrder.contains; @@ -122,6 +123,8 @@ public abstract class RepeatTest extends AbstractGremlinProcessTest { public abstract Traversal get_g_VX6X_repeatXa_bothXcreatedX_simplePathX_emitXrepeatXb_bothXknowsXX_untilXloopsXbX_asXb_whereXloopsXaX_asXbX_hasXname_vadasXX_dedup_name(final Object v6Id); + public abstract Traversal get_g_V_hasXname_markoX_repeatXoutE_order_byXweight_decrX_inVX_emit(); + @Test @LoadGraphWith(MODERN) public void g_V_repeatXoutX_timesX2X_emit_path() { @@ -455,6 +458,18 @@ public void g_VX6X_repeatXa_bothXcreatedX_simplePathX_emitXrepeatXb_bothXknowsXX assertFalse(traversal.hasNext()); } + @Test + @LoadGraphWith(MODERN) + public void g_V_hasXname_markoX_repeatXoutE_order_byXweight_decrX_inVX_emit() { + final List vertices = get_g_V_hasXname_markoX_repeatXoutE_order_byXweight_decrX_inVX_emit().toList(); + assertEquals(5, vertices.size()); + assertEquals("josh", vertices.get(0).values("name").next()); + assertEquals("ripple", vertices.get(1).values("name").next()); + assertEquals("lop", vertices.get(2).values("name").next()); + assertEquals("vadas", vertices.get(3).values("name").next()); + assertEquals("lop", vertices.get(4).values("name").next()); + } + public static class Traversals extends RepeatTest { @Override @@ -577,5 +592,10 @@ public Traversal get_g_VX6X_repeatXa_bothXcreatedX_simplePathX_e public Traversal get_g_VX1X_repeatXrepeatXunionXout_uses_out_traversesXX_whereXloops_isX0X_timesX1X_timeX2X_name(final Object v1Id) { return g.V(v1Id).repeat(__.repeat(__.union(out("uses"), out("traverses")).where(__.loops().is(0))).times(1)).times(2).values("name"); } + + @Override + public Traversal get_g_V_hasXname_markoX_repeatXoutE_order_byXweight_decrX_inVX_emit() { + return g.V().has("name", "marko").repeat(__.outE().order().by("weight", decr).inV()).emit(); + } } }