This is an automated email from the ASF dual-hosted git repository. kenhuuu pushed a commit to branch TINKERPOP-3196 in repository https://gitbox.apache.org/repos/asf/tinkerpop.git
commit 9b8cf3e02e72d034d686bae396b4c3e4c31dd839 Author: Ken Hu <[email protected]> AuthorDate: Sun Oct 19 21:15:50 2025 -0700 TINKERPOP-3196 Split bulked traversers for LocalStep Local was behaving as a per traverser traversal when it should be a per object traversal. This means that there should be no bulked traversers that are added as starts to local traversals. --- .../process/traversal/step/branch/LocalStep.java | 22 +++++- .../gremlin/test/features/branch/Local.feature | 89 +++++++++++++++++++++- 2 files changed, 107 insertions(+), 4 deletions(-) diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/branch/LocalStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/branch/LocalStep.java index f0f08a5ee4..ba9475f784 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/branch/LocalStep.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/branch/LocalStep.java @@ -23,6 +23,7 @@ import org.apache.tinkerpop.gremlin.process.traversal.Traverser; import org.apache.tinkerpop.gremlin.process.traversal.step.TraversalParent; import org.apache.tinkerpop.gremlin.process.traversal.step.util.AbstractStep; 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.structure.util.StringFactory; @@ -38,6 +39,7 @@ public final class LocalStep<S, E> extends AbstractStep<S, E> implements Travers private Traversal.Admin<S, E> localTraversal; private boolean first = true; + private Traverser.Admin<S> currentStart = EmptyTraverser.instance(); public LocalStep(final Traversal.Admin traversal, final Traversal.Admin<S, E> localTraversal) { super(traversal); @@ -58,20 +60,34 @@ public final class LocalStep<S, E> extends AbstractStep<S, E> implements Travers protected Traverser.Admin<E> processNextStart() throws NoSuchElementException { if (this.first) { this.first = false; - this.localTraversal.addStart(this.starts.next()); + this.localTraversal.addStart(nextStart()); } while (true) { if (this.localTraversal.hasNext()) return this.localTraversal.nextTraverser(); - else if (this.starts.hasNext()) { + else if (hasStartRemaining()) { this.localTraversal.reset(); - this.localTraversal.addStart(this.starts.next()); + this.localTraversal.addStart(nextStart()); } else { throw FastNoSuchElementException.instance(); } } } + private boolean hasStartRemaining() { + return (currentStart.bulk() > 0L) || this.starts.hasNext(); + } + + private Traverser.Admin<S> nextStart() throws NoSuchElementException { + if (currentStart.bulk() == 0L) { + currentStart = starts.next(); + } + final Traverser.Admin<S> split = currentStart.split(); + split.setBulk(1L); + currentStart.setBulk(currentStart.bulk() - 1L); + return split; + } + @Override public void reset() { super.reset(); diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/branch/Local.feature b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/branch/Local.feature index 703e1e549e..b946d1a4a0 100644 --- a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/branch/Local.feature +++ b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/branch/Local.feature @@ -183,4 +183,91 @@ Feature: Step - local() | m[{"name":"marko","project":"lop"}] | | m[{"name":"josh","project":"lop"}] | | m[{"name":"peter","project":"lop"}] | - | m[{"name":"josh","project":"ripple"}] | \ No newline at end of file + | m[{"name":"josh","project":"ripple"}] | + + # Barrier should show that bulked traversers don't affect local() traversal + Scenario: g_V_in_barrier_localXcountX + Given the modern graph + And the traversal of + """ + g.V().in().barrier().local(__.count()) + """ + When iterated to list + Then the result should be unordered + | result | + | d[1].l | + | d[1].l | + | d[1].l | + | d[1].l | + | d[1].l | + | d[1].l | + + # Path of traversers isn't hidden in local + @GraphComputerVerificationStarGraphExceeded + Scenario: g_V_localXout_in_simplePathX_path + Given the modern graph + And the traversal of + """ + g.V().local(__.out().in().simplePath()).path() + """ + When iterated to list + Then the result should be unordered + | result | + | p[v[josh],v[lop],v[marko]] | + | p[v[josh],v[lop],v[peter]] | + | p[v[marko],v[lop],v[josh]] | + | p[v[marko],v[lop],v[peter]] | + | p[v[peter],v[lop],v[marko]] | + | p[v[peter],v[lop],v[josh]] | + + # Traverser's sack value should be carried over from local traversal + Scenario: g_withSackX0LX_V_in_barrier_localXsackXsumX_byXageXX_sack + Given the modern graph + And the traversal of + """ + g.withSack(0L).V().in().barrier().local(__.sack(sum).by("age")).sack() + """ + When iterated to list + Then the result should be unordered + | result | + | d[29].l | + | d[29].l | + | d[29].l | + | d[32].l | + | d[32].l | + | d[35].l | + + # Nested local should return proper local count + Scenario: g_V_localXout_localXcountXX + Given the modern graph + And the traversal of + """ + g.V().local(__.out().local(__.count())) + """ + When iterated to list + Then the result should be unordered + | result | + | d[1].l | + | d[1].l | + | d[1].l | + | d[1].l | + | d[1].l | + | d[1].l | + + # Local should be applied to union's global child + Scenario: g_V_unionXoutE_count_localXinE_countXX + Given the modern graph + And the traversal of + """ + g.V().union(__.outE().count(), __.local(inE().count())) + """ + When iterated to list + Then the result should be unordered + | result | + | d[6].l | + | d[0].l | + | d[1].l | + | d[3].l | + | d[1].l | + | d[1].l | + | d[0].l |
