This is an automated email from the ASF dual-hosted git repository.
andreac pushed a commit to branch 3.8-dev
in repository https://gitbox.apache.org/repos/asf/tinkerpop.git
The following commit(s) were added to refs/heads/3.8-dev by this push:
new aea480543a Fix sample in repeat computer algorithm to retain sample
size (#3273)
aea480543a is described below
commit aea480543a4de599c06575f144f138ea497842ec
Author: andreachild <[email protected]>
AuthorDate: Fri Nov 7 12:14:13 2025 -0800
Fix sample in repeat computer algorithm to retain sample size (#3273)
Fixed SampleGlobalStep computer algorithm to return the correct sample size
when used in repeat by retaining only the traversers which have passed through
all loops when merging traverser sets. Prior to this fix the traverser sets
would be merged together and traversers which were excluded from the sample
would potentially be added to the sample in the next loop.
---
CHANGELOG.asciidoc | 1 +
.../traversal/step/filter/SampleGlobalStep.java | 47 ++++++++++++--
.../traversal/traverser/ProjectedTraverser.java | 5 ++
.../step/filter/SampleGlobalStepTest.java | 72 +++++++++++++++++++++-
.../Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs | 4 ++
gremlin-go/driver/cucumber/gremlin.go | 4 ++
.../gremlin-javascript/test/cucumber/gremlin.js | 4 ++
gremlin-python/src/main/python/radish/gremlin.py | 4 ++
.../gremlin/test/features/filter/Sample.feature | 38 ++++++++++++
9 files changed, 172 insertions(+), 7 deletions(-)
diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc
index 7506380a30..e140262567 100644
--- a/CHANGELOG.asciidoc
+++ b/CHANGELOG.asciidoc
@@ -136,6 +136,7 @@ This release also includes changes from <<release-3-7-XXX,
3.7.XXX>>.
* Modified `RepeatUnrollStrategy` to use a more conservative approach, only
unrolling repeat loops containing safe navigation and filtering steps.
* Modified `limit()`, `skip()`, range()` in `repeat()` to track per-iteration
counters.
* Moved `Traverser` loop logic into new interface `NL_SL_Traverser` and
changed loop-supporting `Traverser`s to extend the common interface.
+* Fixed `sample()` in `repeat()` computer algorithm to retain the configured
sample size per loop
== TinkerPop 3.7.0 (Gremfir Master of the Pan Flute)
diff --git
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/SampleGlobalStep.java
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/SampleGlobalStep.java
index fbc3e8d66b..c6f9a50d74 100644
---
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/SampleGlobalStep.java
+++
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/SampleGlobalStep.java
@@ -18,6 +18,14 @@
*/
package org.apache.tinkerpop.gremlin.process.traversal.step.filter;
+import java.io.Serializable;
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+import java.util.Random;
+import java.util.Set;
+import java.util.function.BinaryOperator;
+import org.apache.tinkerpop.gremlin.process.computer.MemoryComputeKey;
import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
import org.apache.tinkerpop.gremlin.process.traversal.Traverser;
import org.apache.tinkerpop.gremlin.process.traversal.lambda.ConstantTraversal;
@@ -32,12 +40,6 @@ import
org.apache.tinkerpop.gremlin.process.traversal.util.TraversalProduct;
import org.apache.tinkerpop.gremlin.process.traversal.util.TraversalUtil;
import org.apache.tinkerpop.gremlin.structure.util.StringFactory;
-import java.util.Collections;
-import java.util.List;
-import java.util.Optional;
-import java.util.Random;
-import java.util.Set;
-
/**
* @author Marko A. Rodriguez (http://markorodriguez.com)
*/
@@ -47,6 +49,7 @@ public final class SampleGlobalStep<S> extends
CollectingBarrierStep<S> implemen
private Traversal.Admin<S, Number> probabilityTraversal = new
ConstantTraversal<>(1.0d);;
private final int amountToSample;
private final Random random = new Random();
+ private final SampleGlobalStep.SampleBiOperator<S> traverserReducer = new
SampleBiOperator<>();
public SampleGlobalStep(final Traversal.Admin traversal, final int
amountToSample) {
super(traversal);
@@ -135,7 +138,39 @@ public final class SampleGlobalStep<S> extends
CollectingBarrierStep<S> implemen
traverserSet.addAll(sampledSet);
}
+ @Override
+ public MemoryComputeKey<TraverserSet<S>> getMemoryComputeKey() {
+ return MemoryComputeKey.of(this.getId(), traverserReducer, false,
true);
+ }
+
+ public static final class SampleBiOperator<S> implements
BinaryOperator<TraverserSet<S>>, Serializable {
+ @Override
+ public TraverserSet<S> apply(final TraverserSet<S> setA, final
TraverserSet<S> setB) {
+ int maxLoops = -1;
+ // if the traversers have gone through loops, only retain the ones
that have passed through the most loops
+ // if there are no loops, all traversers are retained
+ final TraverserSet<S> result = new TraverserSet<>();
+ maxLoops = processTraverserSet(setA, maxLoops, result);
+ processTraverserSet(setB, maxLoops, result);
+ return result;
+ }
+ private static <S> int processTraverserSet(final TraverserSet<S>
traverserSet, final int currentMaxLoops, final TraverserSet<S> result) {
+ int max = currentMaxLoops;
+ for (final Traverser.Admin<S> traverser : traverserSet) {
+ final int loops = traverser.getLoopNames().isEmpty() ? 0 :
traverser.loops();
+ if (loops > max) {
+ max = loops;
+ result.clear();
+ result.add(traverser);
+ } else if (loops == max) {
+ result.add(traverser);
+ }
+ }
+ return max;
+ }
+ }
+
private Optional<ProjectedTraverser<S, Number>>
createProjectedTraverser(final Traverser.Admin<S> traverser) {
final TraversalProduct product = TraversalUtil.produce(traverser,
this.probabilityTraversal);
if (product.isProductive()) {
diff --git
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/traverser/ProjectedTraverser.java
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/traverser/ProjectedTraverser.java
index 7cef844979..10ed1c55a0 100644
---
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/traverser/ProjectedTraverser.java
+++
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/traverser/ProjectedTraverser.java
@@ -176,6 +176,11 @@ public final class ProjectedTraverser<T, P> implements
Traverser.Admin<T> {
return this.baseTraverser.loops(loopName);
}
+ @Override
+ public Set<String> getLoopNames() {
+ return this.baseTraverser.getLoopNames();
+ }
+
@Override
public long bulk() {
return this.baseTraverser.bulk();
diff --git
a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/SampleGlobalStepTest.java
b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/SampleGlobalStepTest.java
index eebea0a40f..2698cace97 100644
---
a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/SampleGlobalStepTest.java
+++
b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/SampleGlobalStepTest.java
@@ -18,26 +18,48 @@
*/
package org.apache.tinkerpop.gremlin.process.traversal.step.filter;
-import org.apache.tinkerpop.gremlin.process.traversal.Scope;
+import java.util.stream.Collectors;
+import org.apache.tinkerpop.gremlin.process.traversal.Step;
import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
+import org.apache.tinkerpop.gremlin.process.traversal.Traverser;
import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__;
import org.apache.tinkerpop.gremlin.process.traversal.step.StepTest;
+import
org.apache.tinkerpop.gremlin.process.traversal.traverser.B_NL_O_S_SE_SL_Traverser;
+import
org.apache.tinkerpop.gremlin.process.traversal.traverser.B_O_S_SE_SL_Traverser;
+import
org.apache.tinkerpop.gremlin.process.traversal.traverser.util.TraverserSet;
+import org.apache.tinkerpop.gremlin.process.traversal.util.EmptyTraversal;
import org.apache.tinkerpop.gremlin.structure.T;
+import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.when;
/**
* @author Daniel Kuppitz (http://gremlin.guru)
*/
public class SampleGlobalStepTest extends StepTest {
+ @Rule
+ public MockitoRule mockitoRule = MockitoJUnit.rule();
+ @Mock
+ protected Step<String, ?> mockStep;
+
+ @Before
+ public void setup() {
+ when(mockStep.getTraversal()).thenReturn(EmptyTraversal.instance());
+ }
+
@Override
protected List<Traversal> getTraversals() {
return Arrays.asList(
@@ -65,4 +87,52 @@ public class SampleGlobalStepTest extends StepTest {
public void shouldThrowForMultipleByModulators() {
__.V().sample(1).by("age").by(T.id);
}
+
+ @Test
+ public void testSampleBiOperatorCombinesTraverserSetsWithLoops() {
+ final SampleGlobalStep.SampleBiOperator<String> operator = new
SampleGlobalStep.SampleBiOperator<>();
+
+ final TraverserSet<String> setA = new TraverserSet<>();
+ setA.add(createTraverser("a", 1));
+ setA.add(createTraverser("b", 2));
+
+ final TraverserSet<String> setB = new TraverserSet<>();
+ setB.add(createTraverser("c", 2));
+ setB.add(createTraverser("d", 1));
+ setB.add(createTraverser("e", 2));
+ setB.add(createTraverser("f", 0));
+
+ final TraverserSet<String> result = operator.apply(setA, setB);
+ assertEquals(3, result.size());
+
assertTrue(result.stream().map(Traverser::get).collect(Collectors.toList()).containsAll(List.of("b",
"c", "e")));
+ }
+
+ @Test
+ public void testSampleBiOperatorCombinesTraverserSetsWithoutLoops() {
+ final SampleGlobalStep.SampleBiOperator<String> operator = new
SampleGlobalStep.SampleBiOperator<>();
+
+ final TraverserSet<String> setA = new TraverserSet<>();
+ setA.add(createTraverser("a", 0));
+ setA.add(createTraverser("b", 0));
+
+ final TraverserSet<String> setB = new TraverserSet<>();
+ setB.add(createTraverser("c", 0));
+
+ final TraverserSet<String> result = operator.apply(setA, setB);
+ assertEquals(3, result.size());
+
assertTrue(result.stream().map(Traverser::get).collect(Collectors.toList()).containsAll(List.of("a",
"b", "c")));
+ }
+
+ private B_O_S_SE_SL_Traverser.Admin<String> createTraverser(final String
str, final int loops) {
+ final Traverser.Admin<String> traverser = new
B_NL_O_S_SE_SL_Traverser<>(str, mockStep, 1).asAdmin();
+ if (loops > 0) {
+ traverser.initialiseLoops("repeatStep1", null);
+ for (int i = 0; i < loops; i++) {
+ traverser.incrLoops();
+ }
+ }
+ return traverser;
+ }
+
+
}
diff --git a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs
b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs
index cfe8d03945..d575eb7c64 100644
--- a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs
+++ b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs
@@ -592,6 +592,10 @@ namespace Gremlin.Net.IntegrationTest.Gherkin
{"g_withStrategiesXSeedStrategyX_V_group_byXlabelX_byXbothE_weight_order_fold_sampleXlocal_5XXunfold",
new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>>
{(g,p) =>g.WithStrategies(new SeedStrategy(seed: 999999)).V().Group<object,
object>().By(T.Label).By(__.BothE().Values<object>("weight").Order().Fold().Sample(Scope.Local,
5)).Unfold<object>()}},
{"g_withStrategiesXSeedStrategyX_V_order_byXlabel_descX_sampleX1X_byXageX", new
List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>>
{(g,p) =>g.WithStrategies(new SeedStrategy(seed:
999999)).V().Order().By(T.Label, Order.Desc).Sample(1).By("age")}},
{"g_VX1X_valuesXageX_sampleXlocal_5X", new
List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>>
{(g,p) =>g.V(p["vid1"]).Values<object>("age").Sample(Scope.Local, 5)}},
+ {"g_V_repeatXsampleX2XX_timesX2X", new
List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>>
{(g,p) =>g.V().Repeat(__.Sample(2)).Times(2)}},
+ {"g_V_sampleX2X_sampleX2X", new List<Func<GraphTraversalSource,
IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Sample(2).Sample(2)}},
+
{"g_V3_repeatXout_order_byXperformancesX_sampleX2X_aggregateXxXX_untilXloops_isX2XX_capXxX_unfold",
new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>>
{(g,p)
=>g.V(p["vid3"]).Repeat(__.Out().Order().By("performances").Sample(2).Aggregate("x")).Until(__.Loops().Is(2)).Cap<object>("x").Unfold<object>()}},
+
{"g_V3_out_order_byXperformancesX_sampleX2X_aggregateXxX_out_order_byXperformancesX_sampleX2X_aggregateXxX_capXxX_unfold",
new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>>
{(g,p)
=>g.V(p["vid3"]).Out().Order().By("performances").Sample(2).Aggregate("x").Out().Order().By("performances").Sample(2).Aggregate("x").Cap<object>("x").Unfold<object>()}},
{"g_VX1X_outXcreatedX_inXcreatedX_simplePath", new
List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>>
{(g,p) =>g.V(p["vid1"]).Out("created").In("created").SimplePath()}},
{"g_V_repeatXboth_simplePathX_timesX3X_path", new
List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>>
{(g,p) =>g.V().Repeat(__.Both().SimplePath()).Times(3).Path()}},
{"g_V_asXaX_out_asXbX_out_asXcX_simplePath_byXlabelX_fromXbX_toXcX_path_byXnameX",
new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>>
{(g,p)
=>g.V().As("a").Out().As("b").Out().As("c").SimplePath().By(T.Label).From("b").To("c").Path().By("name")}},
diff --git a/gremlin-go/driver/cucumber/gremlin.go
b/gremlin-go/driver/cucumber/gremlin.go
index 63119f6df3..19fa18dbe8 100644
--- a/gremlin-go/driver/cucumber/gremlin.go
+++ b/gremlin-go/driver/cucumber/gremlin.go
@@ -562,6 +562,10 @@ var translationMap = map[string][]func(g
*gremlingo.GraphTraversalSource, p map[
"g_withStrategiesXSeedStrategyX_V_group_byXlabelX_byXbothE_weight_order_fold_sampleXlocal_5XXunfold":
{func(g *gremlingo.GraphTraversalSource, p map[string]interface{})
*gremlingo.GraphTraversal {return
g.WithStrategies(gremlingo.SeedStrategy(gremlingo.SeedStrategyConfig{Seed:
999999})).V().Group().By(gremlingo.T.Label).By(gremlingo.T__.BothE().Values("weight").Order().Fold().Sample(gremlingo.Scope.Local,
5)).Unfold()}},
"g_withStrategiesXSeedStrategyX_V_order_byXlabel_descX_sampleX1X_byXageX":
{func(g *gremlingo.GraphTraversalSource, p map[string]interface{})
*gremlingo.GraphTraversal {return
g.WithStrategies(gremlingo.SeedStrategy(gremlingo.SeedStrategyConfig{Seed:
999999})).V().Order().By(gremlingo.T.Label,
gremlingo.Order.Desc).Sample(1).By("age")}},
"g_VX1X_valuesXageX_sampleXlocal_5X": {func(g
*gremlingo.GraphTraversalSource, p map[string]interface{})
*gremlingo.GraphTraversal {return
g.V(p["vid1"]).Values("age").Sample(gremlingo.Scope.Local, 5)}},
+ "g_V_repeatXsampleX2XX_timesX2X": {func(g *gremlingo.GraphTraversalSource,
p map[string]interface{}) *gremlingo.GraphTraversal {return
g.V().Repeat(gremlingo.T__.Sample(2)).Times(2)}},
+ "g_V_sampleX2X_sampleX2X": {func(g *gremlingo.GraphTraversalSource, p
map[string]interface{}) *gremlingo.GraphTraversal {return
g.V().Sample(2).Sample(2)}},
+
"g_V3_repeatXout_order_byXperformancesX_sampleX2X_aggregateXxXX_untilXloops_isX2XX_capXxX_unfold":
{func(g *gremlingo.GraphTraversalSource, p map[string]interface{})
*gremlingo.GraphTraversal {return
g.V(p["vid3"]).Repeat(gremlingo.T__.Out().Order().By("performances").Sample(2).Aggregate("x")).Until(gremlingo.T__.Loops().Is(2)).Cap("x").Unfold()}},
+
"g_V3_out_order_byXperformancesX_sampleX2X_aggregateXxX_out_order_byXperformancesX_sampleX2X_aggregateXxX_capXxX_unfold":
{func(g *gremlingo.GraphTraversalSource, p map[string]interface{})
*gremlingo.GraphTraversal {return
g.V(p["vid3"]).Out().Order().By("performances").Sample(2).Aggregate("x").Out().Order().By("performances").Sample(2).Aggregate("x").Cap("x").Unfold()}},
"g_VX1X_outXcreatedX_inXcreatedX_simplePath": {func(g
*gremlingo.GraphTraversalSource, p map[string]interface{})
*gremlingo.GraphTraversal {return
g.V(p["vid1"]).Out("created").In("created").SimplePath()}},
"g_V_repeatXboth_simplePathX_timesX3X_path": {func(g
*gremlingo.GraphTraversalSource, p map[string]interface{})
*gremlingo.GraphTraversal {return
g.V().Repeat(gremlingo.T__.Both().SimplePath()).Times(3).Path()}},
"g_V_asXaX_out_asXbX_out_asXcX_simplePath_byXlabelX_fromXbX_toXcX_path_byXnameX":
{func(g *gremlingo.GraphTraversalSource, p map[string]interface{})
*gremlingo.GraphTraversal {return
g.V().As("a").Out().As("b").Out().As("c").SimplePath().By(gremlingo.T.Label).From("b").To("c").Path().By("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 5e0fc66f47..e022c7e336 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
@@ -593,6 +593,10 @@ const gremlins = {
g_withStrategiesXSeedStrategyX_V_group_byXlabelX_byXbothE_weight_order_fold_sampleXlocal_5XXunfold:
[function({g}) { return g.withStrategies(new SeedStrategy({seed:
999999})).V().group().by(T.label).by(__.bothE().values("weight").order().fold().sample(Scope.local,
5)).unfold() }],
g_withStrategiesXSeedStrategyX_V_order_byXlabel_descX_sampleX1X_byXageX:
[function({g}) { return g.withStrategies(new SeedStrategy({seed:
999999})).V().order().by(T.label, Order.desc).sample(1).by("age") }],
g_VX1X_valuesXageX_sampleXlocal_5X: [function({g, vid1}) { return
g.V(vid1).values("age").sample(Scope.local, 5) }],
+ g_V_repeatXsampleX2XX_timesX2X: [function({g}) { return
g.V().repeat(__.sample(2)).times(2) }],
+ g_V_sampleX2X_sampleX2X: [function({g}) { return g.V().sample(2).sample(2)
}],
+
g_V3_repeatXout_order_byXperformancesX_sampleX2X_aggregateXxXX_untilXloops_isX2XX_capXxX_unfold:
[function({g, vid3}) { return
g.V(vid3).repeat(__.out().order().by("performances").sample(2).aggregate("x")).until(__.loops().is(2)).cap("x").unfold()
}],
+
g_V3_out_order_byXperformancesX_sampleX2X_aggregateXxX_out_order_byXperformancesX_sampleX2X_aggregateXxX_capXxX_unfold:
[function({g, vid3}) { return
g.V(vid3).out().order().by("performances").sample(2).aggregate("x").out().order().by("performances").sample(2).aggregate("x").cap("x").unfold()
}],
g_VX1X_outXcreatedX_inXcreatedX_simplePath: [function({g, vid1}) { return
g.V(vid1).out("created").in_("created").simplePath() }],
g_V_repeatXboth_simplePathX_timesX3X_path: [function({g}) { return
g.V().repeat(__.both().simplePath()).times(3).path() }],
g_V_asXaX_out_asXbX_out_asXcX_simplePath_byXlabelX_fromXbX_toXcX_path_byXnameX:
[function({g}) { return
g.V().as("a").out().as("b").out().as("c").simplePath().by(T.label).from_("b").to("c").path().by("name")
}],
diff --git a/gremlin-python/src/main/python/radish/gremlin.py
b/gremlin-python/src/main/python/radish/gremlin.py
index f371334d8f..bd7cf96d74 100644
--- a/gremlin-python/src/main/python/radish/gremlin.py
+++ b/gremlin-python/src/main/python/radish/gremlin.py
@@ -565,6 +565,10 @@ world.gremlins = {
'g_withStrategiesXSeedStrategyX_V_group_byXlabelX_byXbothE_weight_order_fold_sampleXlocal_5XXunfold':
[(lambda
g:g.with_strategies(SeedStrategy(seed=999999)).V().group().by(T.label).by(__.both_e().values('weight').order().fold().sample(Scope.local,
5)).unfold())],
'g_withStrategiesXSeedStrategyX_V_order_byXlabel_descX_sampleX1X_byXageX':
[(lambda g:g.with_strategies(SeedStrategy(seed=999999)).V().order().by(T.label,
Order.desc).sample(1).by('age'))],
'g_VX1X_valuesXageX_sampleXlocal_5X': [(lambda g,
vid1=None:g.V(vid1).values('age').sample(Scope.local, 5))],
+ 'g_V_repeatXsampleX2XX_timesX2X': [(lambda
g:g.V().repeat(__.sample(2)).times(2))],
+ 'g_V_sampleX2X_sampleX2X': [(lambda g:g.V().sample(2).sample(2))],
+
'g_V3_repeatXout_order_byXperformancesX_sampleX2X_aggregateXxXX_untilXloops_isX2XX_capXxX_unfold':
[(lambda g,
vid3=None:g.V(vid3).repeat(__.out().order().by('performances').sample(2).aggregate('x')).until(__.loops().is_(2)).cap('x').unfold())],
+
'g_V3_out_order_byXperformancesX_sampleX2X_aggregateXxX_out_order_byXperformancesX_sampleX2X_aggregateXxX_capXxX_unfold':
[(lambda g,
vid3=None:g.V(vid3).out().order().by('performances').sample(2).aggregate('x').out().order().by('performances').sample(2).aggregate('x').cap('x').unfold())],
'g_VX1X_outXcreatedX_inXcreatedX_simplePath': [(lambda g,
vid1=None:g.V(vid1).out('created').in_('created').simple_path())],
'g_V_repeatXboth_simplePathX_timesX3X_path': [(lambda
g:g.V().repeat(__.both().simple_path()).times(3).path())],
'g_V_asXaX_out_asXbX_out_asXcX_simplePath_byXlabelX_fromXbX_toXcX_path_byXnameX':
[(lambda
g:g.V().as_('a').out().as_('b').out().as_('c').simple_path().by(T.label).from_('b').to('c').path().by('name'))],
diff --git
a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/filter/Sample.feature
b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/filter/Sample.feature
index 3706652ae7..f9b3bcd21f 100644
---
a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/filter/Sample.feature
+++
b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/filter/Sample.feature
@@ -107,3 +107,41 @@ Feature: Step - sample()
Then the result should be unordered
| result |
| d[29].i |
+
+ Scenario: g_V_repeatXsampleX2XX_timesX2X
+ Given the modern graph
+ And the traversal of
+ """
+ g.V().repeat(sample(2)).times(2)
+ """
+ When iterated to list
+ Then the result should have a count of 2
+
+ Scenario: g_V_sampleX2X_sampleX2X
+ Given the modern graph
+ And the traversal of
+ """
+ g.V().sample(2).sample(2)
+ """
+ When iterated to list
+ Then the result should have a count of 2
+
+ Scenario:
g_V3_repeatXout_order_byXperformancesX_sampleX2X_aggregateXxXX_untilXloops_isX2XX_capXxX_unfold
+ Given the grateful graph
+ And using the parameter vid3 defined as "v[NOT FADE AWAY].id"
+ And the traversal of
+ """
+
g.V(vid3).repeat(__.out().order().by("performances").sample(2).aggregate('x')).until(__.loops().is(2)).cap('x').unfold()
+ """
+ When iterated to list
+ Then the result should have a count of 4
+
+ Scenario:
g_V3_out_order_byXperformancesX_sampleX2X_aggregateXxX_out_order_byXperformancesX_sampleX2X_aggregateXxX_capXxX_unfold
+ Given the grateful graph
+ And using the parameter vid3 defined as "v[NOT FADE AWAY].id"
+ And the traversal of
+ """
+
g.V(vid3).out().order().by("performances").sample(2).aggregate('x').out().order().by("performances").sample(2).aggregate('x').cap('x').unfold()
+ """
+ When iterated to list
+ Then the result should have a count of 4
\ No newline at end of file