This is an automated email from the ASF dual-hosted git repository. colegreer pushed a commit to branch repeatLimit in repository https://gitbox.apache.org/repos/asf/tinkerpop.git
commit 78b223f8cd6a08beceaf08352e95205059514098 Author: Andrea Child <[email protected]> AuthorDate: Sun Aug 24 22:56:07 2025 -0700 Q's first attempt to change limit in repeat to per-iteration counters. --- .../process/traversal/step/branch/RepeatStep.java | 28 +++++++++++++++ .../traversal/step/filter/RangeGlobalStep.java | 40 +++++++++++++++++----- 2 files changed, 60 insertions(+), 8 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 17b8a2ef4e..3f27e71e54 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 @@ -23,6 +23,7 @@ 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; import org.apache.tinkerpop.gremlin.process.traversal.step.util.ComputerAwareStep; +import org.apache.tinkerpop.gremlin.process.traversal.step.filter.RangeGlobalStep; import org.apache.tinkerpop.gremlin.process.traversal.traverser.TraverserRequirement; import org.apache.tinkerpop.gremlin.process.traversal.util.TraversalUtil; import org.apache.tinkerpop.gremlin.structure.util.StringFactory; @@ -67,6 +68,9 @@ public final class RepeatStep<S> extends ComputerAwareStep<S, S> implements Trav this.repeatTraversal = repeatTraversal; // .clone(); this.repeatTraversal.addStep(new RepeatEndStep(this.repeatTraversal)); this.integrateChild(this.repeatTraversal); + + // Enable per-iteration counters on any RangeGlobalStep instances within the repeat traversal + this.enablePerIterationCountersOnRangeSteps(this.repeatTraversal); } @@ -125,6 +129,26 @@ public final class RepeatStep<S> extends ComputerAwareStep<S, S> implements Trav return emitFirst == this.emitFirst && null != this.emitTraversal && TraversalUtil.test(traverser, this.emitTraversal); } + /** + * Recursively enables per-iteration counters on all RangeGlobalStep instances within a traversal. + */ + private void enablePerIterationCountersOnRangeSteps(final Traversal.Admin<?, ?> traversal) { + for (final Step<?, ?> step : traversal.getSteps()) { + if (step instanceof RangeGlobalStep) { + ((RangeGlobalStep<?>) step).enablePerIterationCounters(); + } + if (step instanceof TraversalParent) { + final TraversalParent parent = (TraversalParent) step; + for (final Traversal.Admin<?, ?> childTraversal : parent.getGlobalChildren()) { + this.enablePerIterationCountersOnRangeSteps(childTraversal); + } + for (final Traversal.Admin<?, ?> childTraversal : parent.getLocalChildren()) { + this.enablePerIterationCountersOnRangeSteps(childTraversal); + } + } + } + } + @Override public String toString() { if (this.untilFirst && this.emitFirst) @@ -169,6 +193,10 @@ public final class RepeatStep<S> extends ComputerAwareStep<S, S> implements Trav clone.untilTraversal = this.untilTraversal.clone(); if (null != this.emitTraversal) clone.emitTraversal = this.emitTraversal.clone(); + + // Enable per-iteration counters on the cloned repeat traversal + clone.enablePerIterationCountersOnRangeSteps(clone.repeatTraversal); + return clone; } diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/RangeGlobalStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/RangeGlobalStep.java index 792ab2d645..5d6dbbab7c 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/RangeGlobalStep.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/RangeGlobalStep.java @@ -32,6 +32,8 @@ import org.apache.tinkerpop.gremlin.util.iterator.IteratorUtils; import java.io.Serializable; import java.util.Collections; +import java.util.HashMap; +import java.util.Map; import java.util.NoSuchElementException; import java.util.Set; import java.util.concurrent.atomic.AtomicLong; @@ -47,6 +49,10 @@ public final class RangeGlobalStep<S> extends FilterStep<S> implements Ranging, private long high; private AtomicLong counter = new AtomicLong(0l); private boolean bypass; + + // Per-iteration counter tracking for repeat steps + private Map<String, AtomicLong> perIterationCounters = new HashMap<>(); + private boolean usePerIterationCounters = false; public RangeGlobalStep(final Traversal.Admin traversal, final long low, final long high) { super(traversal); @@ -61,31 +67,38 @@ public final class RangeGlobalStep<S> extends FilterStep<S> implements Ranging, protected boolean filter(final Traverser.Admin<S> traverser) { if (this.bypass) return true; - if (this.high != -1 && this.counter.get() >= this.high) { + // Determine which counter to use + AtomicLong currentCounter = this.counter; + if (usePerIterationCounters && traverser.loops() > 0) { + String iterationKey = traverser.getStepId() + ":" + traverser.loops(); + currentCounter = perIterationCounters.computeIfAbsent(iterationKey, k -> new AtomicLong(0L)); + } + + if (this.high != -1 && currentCounter.get() >= this.high) { throw FastNoSuchElementException.instance(); } long avail = traverser.bulk(); - if (this.counter.get() + avail <= this.low) { + if (currentCounter.get() + avail <= this.low) { // Will not surpass the low w/ this traverser. Skip and filter the whole thing. - this.counter.getAndAdd(avail); + currentCounter.getAndAdd(avail); return false; } // Skip for the low and trim for the high. Both can happen at once. long toSkip = 0; - if (this.counter.get() < this.low) { - toSkip = this.low - this.counter.get(); + if (currentCounter.get() < this.low) { + toSkip = this.low - currentCounter.get(); } long toTrim = 0; - if (this.high != -1 && this.counter.get() + avail >= this.high) { - toTrim = this.counter.get() + avail - this.high; + if (this.high != -1 && currentCounter.get() + avail >= this.high) { + toTrim = currentCounter.get() + avail - this.high; } long toEmit = avail - toSkip - toTrim; - this.counter.getAndAdd(toSkip + toEmit); + currentCounter.getAndAdd(toSkip + toEmit); traverser.setBulk(toEmit); return true; @@ -95,6 +108,15 @@ public final class RangeGlobalStep<S> extends FilterStep<S> implements Ranging, public void reset() { super.reset(); this.counter.set(0l); + this.perIterationCounters.clear(); + } + + /** + * Enables per-iteration counter tracking for use within repeat steps. + * When enabled, separate counters are maintained for each repeat iteration. + */ + public void enablePerIterationCounters() { + this.usePerIterationCounters = true; } @Override @@ -116,6 +138,8 @@ public final class RangeGlobalStep<S> extends FilterStep<S> implements Ranging, public RangeGlobalStep<S> clone() { final RangeGlobalStep<S> clone = (RangeGlobalStep<S>) super.clone(); clone.counter = new AtomicLong(0l); + clone.perIterationCounters = new HashMap<>(); + clone.usePerIterationCounters = this.usePerIterationCounters; return clone; }
