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 a6f77163ff https://issues.apache.org/jira/browse/TINKERPOP-3192 (#3212)
a6f77163ff is described below
commit a6f77163ffe5d1639aef3f2f3b897ee4b53679c1
Author: andreachild <[email protected]>
AuthorDate: Fri Sep 26 13:27:46 2025 -0700
https://issues.apache.org/jira/browse/TINKERPOP-3192 (#3212)
This changeset modifies the RepeatUnrollStrategy to use a more conservative
approach for determining which repeat traversals are safe to unroll.
Previously, the strategy would attempt to unroll most repeat loops. This caused
unintentional traversal semantic changes when some steps were unrolled
(especially barrier steps) when they were no longer executed in a child
traversal within a parent traversal.
Now, the strategy only applies to repeat traversals that contain specific
steps: out(), in(), both(), inV(), outV(), otherV(), has(key, value). Repeat
traversals containing other steps will no longer be unrolled.
---
CHANGELOG.asciidoc | 1 +
docs/src/upgrade/release-3.8.x.asciidoc | 89 +++++++++
.../optimization/RepeatUnrollStrategy.java | 49 +++--
.../process/traversal/util/TraversalHelper.java | 18 ++
.../optimization/RepeatUnrollStrategyTest.java | 140 ++++++++-----
.../traversal/util/TraversalHelperTest.java | 49 ++++-
.../Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs | 16 ++
gremlin-go/driver/cucumber/gremlin.go | 16 ++
.../gremlin-javascript/test/cucumber/gremlin.js | 16 ++
gremlin-python/src/main/python/radish/gremlin.py | 16 ++
.../integrated/RepeatUnrollStrategy.feature | 221 +++++++++++++++++++++
11 files changed, 564 insertions(+), 67 deletions(-)
diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc
index cff757fff7..b7e9040d93 100644
--- a/CHANGELOG.asciidoc
+++ b/CHANGELOG.asciidoc
@@ -109,6 +109,7 @@ This release also includes changes from <<release-3-7-XXX,
3.7.XXX>>.
* Updated `asString()` step to throw `IllegalArgumentException` with `null`
inputs for casting step consistency
* Renamed `MergeElementStep` to `MergeElementStep` as it is a base class to
`mergeV()` and `mergeE()`.
* Renamed `MergeStep` of `merge()` to `MergeElementStep` for consistency.
+* Modified `RepeatUnrollStrategy` to use a more conservative approach, only
unrolling repeat loops containing safe navigation and filtering steps.
== TinkerPop 3.7.0 (Gremfir Master of the Pan Flute)
diff --git a/docs/src/upgrade/release-3.8.x.asciidoc
b/docs/src/upgrade/release-3.8.x.asciidoc
index 8c42d99271..f55b5cac5e 100644
--- a/docs/src/upgrade/release-3.8.x.asciidoc
+++ b/docs/src/upgrade/release-3.8.x.asciidoc
@@ -733,6 +733,95 @@ some of the interfaces in the gremlin traversal engine.
This has led to several
As of 3.8.0 `with()` modulation of the following steps will no longer work:
`addV()`, `addE()`, `property()`, `drop()`,
`mergeV()`, and `mergeE()`.
+==== Stricter RepeatUnrollStrategy
+
+The `RepeatUnrollStrategy` has been updated to use a more conservative
approach for determining which repeat traversals
+are safe to unroll. Previously, the strategy would attempt to unroll most
usages of `repeat()` used with `times()`
+without `emit()`. This caused unintentional traversal semantic changes when
some steps were unrolled (especially barrier
+steps).
+
+As of 3.8.0, the strategy will still only be applied if `repeat()` is used
with `times()` without `emit()` but now only
+applies to repeat traversals that contain exclusively safe, well-understood
steps: `out()`, `in()`, `both()`, `inV()`,
+`outV()`, `otherV()`, `has(key, value)`.
+
+Repeat traversals containing other steps will no longer be unrolled. There may
be some performance differences for
+traversals that previously benefited from automatic unrolling but the
consistency of semantics outweighs the performance
+impact.
+
+===== Examples of Affected Traversals =====
+
+Usage of `limit()` inside `repeat()` is an example of a traversal that will no
longer be unrolled. The following results
+returned from the `modern` graph demonstrate the change of semantics if the
`limit()` in `repeat()` were to be unrolled:
+
+[source,text]
+----
+gremlin> g.V().both().limit(1).both().limit(1)
+==>v[1]
+gremlin>
g.withoutStrategies(RepeatUnrollStrategy).V().repeat(both().limit()).times(2)
+gremlin>
+----
+
+Another example is the usage of `aggregate()` inside `repeat()`. The following
results returned from the `modern` graph
+demonstrate the change of semantics if the `aggregate()` in `repeat()` were to
be unrolled:
+
+[source,text]
+----
+gremlin> g.V().both().aggregate('x').both().aggregate('x').limit(10)
+==>v[1]
+==>v[1]
+==>v[1]
+==>v[1]
+==>v[1]
+==>v[1]
+==>v[1]
+==>v[4]
+==>v[4]
+==>v[4]
+gremlin>
g.withoutStrategies(RepeatUnrollStrategy).V().repeat(both().aggregate('x')).times(2).limit(10)
+==>v[1]
+==>v[1]
+==>v[1]
+==>v[4]
+==>v[6]
+==>v[5]
+==>v[3]
+==>v[3]
+==>v[2]
+==>v[4]
+----
+
+Other examples of affected traversals include (but are not limited to):
+
+[source,groovy]
+----
+g.V().repeat(out().limit(10)).times(3)
+g.V().repeat(in().order().by("name")).times(2)
+g.V().repeat(both().simplePath()).times(4)
+g.V().repeat(both().sample(1)).times(2)
+----
+
+===== Migration Strategies
+
+Before upgrading, analyze existing traversals which use `repeat()` with any
steps other than `out()`, `in()`, `both()`,
+`inV()`, `outV()`, `otherV()`, `has(key, value)` and determine if the
semantics of these traversals are as expected when
+the `RepeatUnrollStrategy` is disabled using
`withoutStrategies(RepeatUnrollStrategy)`. If the semantics are unexpected
+the traversal should be restructured to no longer use `repeat()` by manually
unrolling the steps inside `repeat()` or by
+moving affected steps outside the `repeat()`.
+
+Example:
+
+[source,groovy]
+----
+// original traversal
+g.V().repeat(both().dedup()).times(2)
+// can be manually unrolled to
+g.V().both().dedup().both().dedup()
+// or dedup can be moved outside of repeat
+g.V().repeat(both()).times(2).dedup()
+----
+
+See: link:https://issues.apache.org/jira/browse/TINKERPOP-3192[TINKERPOP-3192]
+
=== Upgrading for Providers
==== Graph System Providers
diff --git
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/strategy/optimization/RepeatUnrollStrategy.java
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/strategy/optimization/RepeatUnrollStrategy.java
index 18481cd2c6..24af90837f 100644
---
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/strategy/optimization/RepeatUnrollStrategy.java
+++
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/strategy/optimization/RepeatUnrollStrategy.java
@@ -19,34 +19,40 @@
package org.apache.tinkerpop.gremlin.process.traversal.strategy.optimization;
-import org.apache.tinkerpop.gremlin.process.traversal.Scope;
+import java.util.Set;
import org.apache.tinkerpop.gremlin.process.traversal.Step;
import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
import org.apache.tinkerpop.gremlin.process.traversal.TraversalStrategy;
import org.apache.tinkerpop.gremlin.process.traversal.lambda.LoopTraversal;
import org.apache.tinkerpop.gremlin.process.traversal.step.Barrier;
-import org.apache.tinkerpop.gremlin.process.traversal.step.LambdaHolder;
import org.apache.tinkerpop.gremlin.process.traversal.step.branch.RepeatStep;
-import
org.apache.tinkerpop.gremlin.process.traversal.step.filter.DedupGlobalStep;
-import org.apache.tinkerpop.gremlin.process.traversal.step.map.LoopsStep;
+import org.apache.tinkerpop.gremlin.process.traversal.step.filter.HasStep;
+import
org.apache.tinkerpop.gremlin.process.traversal.step.map.EdgeOtherVertexStep;
+import org.apache.tinkerpop.gremlin.process.traversal.step.map.EdgeVertexStep;
import org.apache.tinkerpop.gremlin.process.traversal.step.map.NoOpBarrierStep;
+import
org.apache.tinkerpop.gremlin.process.traversal.step.map.VertexStepContract;
import
org.apache.tinkerpop.gremlin.process.traversal.strategy.AbstractTraversalStrategy;
import org.apache.tinkerpop.gremlin.process.traversal.util.TraversalHelper;
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.Set;
-
/**
* {@code RepeatUnrollStrategy} is an OLTP-only strategy that unrolls any
{@link RepeatStep} if it uses a constant
- * number of loops ({@code times(x)}) and doesn't emit intermittent elements.
If any of the following 3 steps appears
- * within the repeat-traversal, the strategy will not be applied:
+ * number of loops ({@code times(x)}) and doesn't emit intermittent elements.
The strategy only applies to repeat
+ * traversals that contain exclusively safe, stateless steps from the allowed
set:
* <p/>
* <ul>
- * <li>{@link DedupGlobalStep}</li>
- * <li>{@link LoopsStep}</li>
- * <li>{@link LambdaHolder}</li>
+ * <li>{@link VertexStepContract}</li>
+ * <li>{@link EdgeVertexStep}</li>
+ * <li>{@link EdgeOtherVertexStep}</li>
+ * <li>{@link HasStep}</li>
+ * <li>{@link RepeatStep} and {@link RepeatStep.RepeatEndStep}</li>
* </ul>
+ * <p/>
+ * Steps that are not in this allowed set will prevent the strategy from being
applied. This allowed set of steps is
+ * intentionally conservative as there have been unintentional traversal
semantics changes in the past when allowing a
+ * large variety of steps (especially barriers). Only steps that can be safely
executed outside the repeat (and not
+ * modify semantics of the traversal) should be included. The allowed set can
be expanded in future versions as
+ * additional steps are verified to be safe.
+ * <p/>
*
* @author Marko A. Rodriguez (http://markorodriguez.com)
* @example <pre>
@@ -56,8 +62,14 @@ import java.util.Set;
public final class RepeatUnrollStrategy extends
AbstractTraversalStrategy<TraversalStrategy.OptimizationStrategy> implements
TraversalStrategy.OptimizationStrategy {
private static final RepeatUnrollStrategy INSTANCE = new
RepeatUnrollStrategy();
- protected static final int MAX_BARRIER_SIZE = 2500;
- private static final Set<Class> INVALIDATING_STEPS = new
HashSet<>(Arrays.asList(LambdaHolder.class, LoopsStep.class));
+ static final int MAX_BARRIER_SIZE = 2500;
+ private static final Set<Class> ALLOWED_STEP_CLASSES = Set.of(
+ VertexStepContract.class,
+ EdgeVertexStep.class,
+ EdgeOtherVertexStep.class,
+ HasStep.class,
+ RepeatStep.class,
+ RepeatStep.RepeatEndStep.class);
private RepeatUnrollStrategy() {
}
@@ -73,10 +85,10 @@ public final class RepeatUnrollStrategy extends
AbstractTraversalStrategy<Traver
for (int i = 0; i < traversal.getSteps().size(); i++) {
if (traversal.getSteps().get(i) instanceof RepeatStep) {
final RepeatStep<?> repeatStep = (RepeatStep)
traversal.getSteps().get(i);
+
if (null == repeatStep.getEmitTraversal() && null !=
repeatStep.getRepeatTraversal() &&
repeatStep.getUntilTraversal() instanceof
LoopTraversal && ((LoopTraversal) repeatStep.getUntilTraversal()).getMaxLoops()
> 0 &&
-
!TraversalHelper.hasStepOfAssignableClassRecursively(Scope.global,
DedupGlobalStep.class, repeatStep.getRepeatTraversal()) &&
-
!TraversalHelper.hasStepOfAssignableClassRecursively(INVALIDATING_STEPS,
repeatStep.getRepeatTraversal())) {
+
TraversalHelper.hasOnlyStepsOfAssignableClassesRecursively(ALLOWED_STEP_CLASSES,
repeatStep.getRepeatTraversal())) {
final Traversal.Admin<?, ?> repeatTraversal =
repeatStep.getGlobalChildren().get(0);
repeatTraversal.removeStep(repeatTraversal.getSteps().size() - 1); // removes
the RepeatEndStep
@@ -90,7 +102,7 @@ public final class RepeatUnrollStrategy extends
AbstractTraversalStrategy<Traver
// the addition of barriers is determined by the
existence of LazyBarrierStrategy
if (lazyBarrierStrategyInstalled) {
- // only add a final NoOpBarrier is subsequent step
is not a barrier
+ // only add a final NoOpBarrier if subsequent step
is not a barrier
// Don't add a barrier if this step is a barrier
(prevents nested repeat adding the barrier multiple times)
Step step = traversal.getSteps().get(insertIndex);
if ((j != (loops - 1) || !(step.getNextStep()
instanceof Barrier)) && !(step instanceof NoOpBarrierStep)) {
@@ -110,7 +122,6 @@ public final class RepeatUnrollStrategy extends
AbstractTraversalStrategy<Traver
}
}
-
public static RepeatUnrollStrategy instance() {
return INSTANCE;
}
diff --git
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/util/TraversalHelper.java
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/util/TraversalHelper.java
index b66f3efca4..156f92bdb1 100644
---
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/util/TraversalHelper.java
+++
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/util/TraversalHelper.java
@@ -640,6 +640,24 @@ public final class TraversalHelper {
return false;
}
+ /**
+ * Checks if the traversal only has steps that are equal to or assignable
from the given step classes.
+ *
+ * @param stepClasses the collection of allowed step classes
+ * @param traversal the traversal to check
+ * @return true if all steps in the traversal are equal to or assignable
from the given classes
+ */
+ public static boolean hasOnlyStepsOfAssignableClassesRecursively(final
Collection<Class> stepClasses, final Traversal.Admin<?, ?> traversal) {
+ for (final Step<?, ?> step :
getStepsOfAssignableClassRecursively(Step.class, traversal)) {
+ final boolean isSupported = stepClasses.stream()
+ .anyMatch(allowedClass ->
allowedClass.isAssignableFrom(step.getClass()));
+ if (!isSupported) {
+ return false;
+ }
+ }
+ return true;
+ }
+
/**
* Determine if any step in {@link Traversal} or its children match the
step given the provided {@link Predicate}.
*
diff --git
a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/strategy/optimization/RepeatUnrollStrategyTest.java
b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/strategy/optimization/RepeatUnrollStrategyTest.java
index 1b1243c8f0..1bd2675346 100644
---
a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/strategy/optimization/RepeatUnrollStrategyTest.java
+++
b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/strategy/optimization/RepeatUnrollStrategyTest.java
@@ -43,6 +43,7 @@ import java.util.Collections;
import java.util.List;
import java.util.function.Predicate;
+import static org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__.both;
import static org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__.in;
import static org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__.out;
import static org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__.path;
@@ -58,50 +59,17 @@ public class RepeatUnrollStrategyTest {
@RunWith(Parameterized.class)
public static class StandardTest {
@Parameterized.Parameter(value = 0)
- public Traversal.Admin<?,?> original;
+ public Traversal.Admin<?, ?> original;
@Parameterized.Parameter(value = 1)
- public Traversal<?,?> optimized;
+ public Traversal<?, ?> expectedOptimized;
@Parameterized.Parameter(value = 2)
public Collection<TraversalStrategy<?>> otherStrategies;
@Test
- public void doTest() {
- final String repr =
translator.translate(original.getBytecode()).getScript();
- final TraversalStrategies strategiesWithLazy = new
DefaultTraversalStrategies();
- strategiesWithLazy.addStrategies(RepeatUnrollStrategy.instance());
- Traversal.Admin<?, ?> clonedOriginal = this.original.clone();
-
- // adding LazyBarrierStrategy as RepeatUnrollStrategy adds
barriers and the existence of this strategy
- // triggers those additions. if they are not there they will not
be present and most of these assertions
- // assume this strategy present
- strategiesWithLazy.addStrategies(LazyBarrierStrategy.instance());
- for (final TraversalStrategy<?> strategy : this.otherStrategies) {
- strategiesWithLazy.addStrategies(strategy);
- }
- clonedOriginal.setStrategies(strategiesWithLazy);
- clonedOriginal.applyStrategies();
- assertEquals("With LazyBarrierStrategy: " + repr, this.optimized,
clonedOriginal);
-
- final TraversalStrategies strategiesWithoutLazy = new
DefaultTraversalStrategies();
-
strategiesWithoutLazy.addStrategies(RepeatUnrollStrategy.instance());
- for (final TraversalStrategy<?> strategy : this.otherStrategies) {
- strategiesWithoutLazy.addStrategies(strategy);
- }
- clonedOriginal = this.original.clone();
- clonedOriginal.setStrategies(strategiesWithoutLazy);
- clonedOriginal.applyStrategies();
- final Traversal.Admin<?, ?> optimizedWithoutBarriers =
this.optimized.asAdmin().clone();
-
- // remove all the barriers as LazyBarrierStrategy is not present
and therefore RepeatUnrollStrategy should
- // not add any
-
TraversalHelper.getStepsOfAssignableClassRecursively(NoOpBarrierStep.class,
optimizedWithoutBarriers).forEach(s -> {
- TraversalHelper.copyLabels(s, s.getPreviousStep(), true);
- s.getTraversal().removeStep(s);
- });
- assertEquals("Without LazyBarrierStrategy: " + repr,
optimizedWithoutBarriers, clonedOriginal);
-
+ public void testStrategyApplied() {
+ testRepeatUnrollStrategy(original, expectedOptimized,
otherStrategies);
}
@Parameterized.Parameters(name = "{0}")
@@ -111,33 +79,68 @@ public class RepeatUnrollStrategyTest {
final int repeatBarrierSize =
RepeatUnrollStrategy.MAX_BARRIER_SIZE;
final Predicate<Traverser<Vertex>> predicate = t -> t.loops() > 5;
return Arrays.asList(new Object[][]{
- {__.repeat(out()).times(0), __.repeat(out()).times(0),
Collections.emptyList()},
- {__.<Vertex>times(0).repeat(out()),
__.<Vertex>times(0).repeat(out()), Collections.emptyList()},
- {__.identity(), __.identity(), Collections.emptyList()},
{out().as("a").in().repeat(__.outE("created").bothV()).times(2).in(),
out().as("a").in().outE("created").bothV().barrier(repeatBarrierSize).outE("created").bothV().barrier(repeatBarrierSize).in(),
Collections.emptyList()},
{out().repeat(__.outE("created").bothV()).times(1).in(),
out().outE("created").bothV().barrier(repeatBarrierSize).in(),
Collections.emptyList()},
{__.repeat(__.outE("created").bothV()).times(1).in(),
__.outE("created").bothV().barrier(repeatBarrierSize).in(),
Collections.emptyList()},
{__.repeat(out()).times(2).as("x").repeat(in().as("b")).times(3),
out().barrier(repeatBarrierSize).out().barrier(repeatBarrierSize).as("x").in().as("b").barrier(repeatBarrierSize).in().as("b").barrier(repeatBarrierSize).in().as("b").barrier(repeatBarrierSize),
Collections.emptyList()},
{__.repeat(__.outE("created").inV()).times(2),
__.outE("created").inV().barrier(repeatBarrierSize).outE("created").inV().barrier(repeatBarrierSize),
Collections.emptyList()},
{__.repeat(out()).times(3),
out().barrier(repeatBarrierSize).out().barrier(repeatBarrierSize).out().barrier(repeatBarrierSize),
Collections.emptyList()},
-
{__.repeat(__.local(__.select("a").out("knows"))).times(2),
__.local(__.select("a").out("knows")).barrier(repeatBarrierSize).local(__.select("a").out("knows")).barrier(repeatBarrierSize),
Collections.emptyList()},
{__.<Vertex>times(2).repeat(out()),
out().barrier(repeatBarrierSize).out().barrier(repeatBarrierSize),
Collections.emptyList()},
{__.<Vertex>out().times(2).repeat(out().as("a")).as("x"),
out().out().as("a").barrier(repeatBarrierSize).out().as("a").barrier(repeatBarrierSize).as("x"),
Collections.emptyList()},
- {__.repeat(out()).emit().times(2),
__.repeat(out()).emit().times(2), Collections.emptyList()},
- {__.repeat(out()).until(predicate),
__.repeat(out()).until(predicate), Collections.emptyList()},
{__.repeat(out()).until(predicate).repeat(out()).times(2),
__.repeat(out()).until(predicate).out().barrier(repeatBarrierSize).out().barrier(repeatBarrierSize),
Collections.emptyList()},
- {__.repeat(__.union(__.both(),
__.identity())).times(2).out(), __.union(__.both(),
__.identity()).barrier(repeatBarrierSize).union(__.both(),
__.identity()).barrier(repeatBarrierSize).out(), Collections.emptyList()},
{in().repeat(out("knows")).times(3).as("a").count().is(0),
in().out("knows").barrier(repeatBarrierSize).out("knows").barrier(repeatBarrierSize).out("knows").as("a").count().is(0),
Collections.emptyList()},
//
{__.repeat(__.outE().inV()).times(2),
__.outE().inV().barrier(repeatBarrierSize).outE().inV().barrier(repeatBarrierSize),
Collections.emptyList()},
- {__.repeat(__.outE().filter(path()).inV()).times(2),
__.outE().filter(path()).inV().barrier(repeatBarrierSize).outE().filter(path()).inV().barrier(repeatBarrierSize),
Collections.singletonList(IncidentToAdjacentStrategy.instance())},
{__.repeat(__.outE().inV()).times(2),
__.out().barrier(repeatBarrierSize).out().barrier(repeatBarrierSize),
List.of(IncidentToAdjacentStrategy.instance(),
GValueReductionStrategy.instance())},
// Nested Loop tests
{__.repeat(out().repeat(out()).times(0)).times(1),
__.out().repeat(out()).times(0).barrier(repeatBarrierSize),
Collections.emptyList()},
{__.repeat(out().repeat(out()).times(1)).times(1),
__.out().out().barrier(repeatBarrierSize), Collections.emptyList()},
-
{__.repeat(out()).until(__.repeat(out()).until(predicate)),
__.repeat(out()).until(__.repeat(out()).until(predicate)),
Collections.emptyList()},
{__.repeat(__.repeat(out()).times(2)).until(predicate),
__.repeat(__.out().barrier(repeatBarrierSize).out().barrier(repeatBarrierSize)).until(predicate),
Collections.emptyList()},
- {__.repeat(__.repeat(out("created")).until(__.has("name",
"ripple"))), __.repeat(__.repeat(out("created")).until(__.has("name",
"ripple"))), Collections.emptyList()},
+ // HasStep tests
+ {__.repeat(out().has("name", "lop")).times(2),
out().has("name",
"lop").barrier(repeatBarrierSize).out().barrier(repeatBarrierSize).has("name",
"lop").barrier(repeatBarrierSize), Collections.emptyList()},
+ {__.repeat(in().has("age", 27)).times(3), in().has("age",
27).barrier(repeatBarrierSize).in().barrier(repeatBarrierSize).has("age",
27).barrier(repeatBarrierSize).in().barrier(repeatBarrierSize).has("age",
27).barrier(repeatBarrierSize), Collections.emptyList()},
+ {__.repeat(both().has("name", "marko")).times(2),
both().has("name",
"marko").barrier(repeatBarrierSize).both().barrier(repeatBarrierSize).has("name",
"marko").barrier(repeatBarrierSize), Collections.emptyList()},
+ // EdgeVertexStep tests
+ {__.repeat(__.outE().inV()).times(2),
__.outE().inV().barrier(repeatBarrierSize).outE().inV().barrier(repeatBarrierSize),
Collections.emptyList()},
+ {__.repeat(__.inE().outV()).times(2),
__.inE().outV().barrier(repeatBarrierSize).inE().outV().barrier(repeatBarrierSize),
Collections.emptyList()},
+ {__.repeat(__.bothE().otherV()).times(2),
__.bothE().otherV().barrier(repeatBarrierSize).bothE().otherV().barrier(repeatBarrierSize),
Collections.emptyList()},
+ {__.repeat(__.outE("knows").otherV()).times(2),
__.outE("knows").otherV().barrier(repeatBarrierSize).outE("knows").otherV().barrier(repeatBarrierSize),
Collections.emptyList()},
+ // Combined tests
+ {__.repeat(__.outE("knows").inV().has("name",
"josh")).times(2), __.outE("knows").inV().has("name",
"josh").barrier(repeatBarrierSize).outE("knows").inV().barrier(repeatBarrierSize).has("name",
"josh").barrier(repeatBarrierSize), Collections.emptyList()},
+ });
+ }
+ }
+
+ @RunWith(Parameterized.class)
+ public static class StrategyNotAppliedTest {
+ @Parameterized.Parameter(value = 0)
+ public Traversal.Admin<?, ?> original;
+
+ @Parameterized.Parameter(value = 1)
+ public Collection<TraversalStrategy<?>> otherStrategies;
+
+ @Test
+ public void testStrategyNotApplied() {
+ // since strategy should not have been applied, the optimized
traversal is expected to be the same as the original
+ testRepeatUnrollStrategy(original, original, otherStrategies);
+ }
+
+ @Parameterized.Parameters(name = "{0}")
+ public static Iterable<Object[]> generateTestParameters() {
+ return Arrays.asList(new Object[][]{
+ // zero loops
+ {__.repeat(out()).times(0), Collections.emptyList()},
+ {__.<Vertex>times(0).repeat(out()),
Collections.emptyList()},
+ // no repeat
+ {__.identity(), Collections.emptyList()},
+ // steps in repeat not candidates for unrolling
+
{__.repeat(__.local(__.select("a").out("knows"))).times(2),
Collections.emptyList()},
+ {__.repeat(__.union(__.both(),
__.identity())).times(2).out(), Collections.emptyList()},
+ {__.repeat(__.outE().filter(path()).inV()).times(2),
Collections.singletonList(IncidentToAdjacentStrategy.instance())},
+ // emit traversal is non-null
+ {__.repeat(out()).emit().times(2),
Collections.emptyList()},
+ // until is not LoopsTraversal
+ {__.repeat(out()).until(t -> t.loops() > 5),
Collections.emptyList()},
});
}
}
@@ -178,4 +181,47 @@ public class RepeatUnrollStrategyTest {
.variablesArePreserved();
}
}
+
+ /**
+ * Applies RepeatUnrollStrategy (with and without LazyBarrierStrategy) to
the given original traversal and verifies the expected optimized traversal.
+ *
+ * @param original the original traversal to apply strategies to
+ * @param expectedOptimized the expected optimized traversal
+ * @param otherStrategies any other strategies that should also be
applied
+ */
+ private static void testRepeatUnrollStrategy(Traversal.Admin<?, ?>
original, Traversal<?, ?> expectedOptimized, Collection<TraversalStrategy<?>>
otherStrategies) {
+ final String repr =
translator.translate(original.getBytecode()).getScript();
+ final TraversalStrategies strategiesWithLazy = new
DefaultTraversalStrategies();
+ strategiesWithLazy.addStrategies(RepeatUnrollStrategy.instance());
+ Traversal.Admin<?, ?> clonedOriginal = original.clone();
+
+ // adding LazyBarrierStrategy as RepeatUnrollStrategy adds barriers
and the existence of this strategy
+ // triggers those additions. if they are not there they will not be
present and most of these assertions
+ // assume this strategy present
+ strategiesWithLazy.addStrategies(LazyBarrierStrategy.instance());
+ for (final TraversalStrategy<?> strategy : otherStrategies) {
+ strategiesWithLazy.addStrategies(strategy);
+ }
+ clonedOriginal.setStrategies(strategiesWithLazy);
+ clonedOriginal.applyStrategies();
+ assertEquals("With LazyBarrierStrategy: " + repr, expectedOptimized,
clonedOriginal);
+
+ final TraversalStrategies strategiesWithoutLazy = new
DefaultTraversalStrategies();
+ strategiesWithoutLazy.addStrategies(RepeatUnrollStrategy.instance());
+ for (final TraversalStrategy<?> strategy : otherStrategies) {
+ strategiesWithoutLazy.addStrategies(strategy);
+ }
+ clonedOriginal = original.clone();
+ clonedOriginal.setStrategies(strategiesWithoutLazy);
+ clonedOriginal.applyStrategies();
+ final Traversal.Admin<?, ?> optimizedWithoutBarriers =
expectedOptimized.asAdmin().clone();
+
+ // remove all the barriers as LazyBarrierStrategy is not present and
therefore RepeatUnrollStrategy should
+ // not add any
+
TraversalHelper.getStepsOfAssignableClassRecursively(NoOpBarrierStep.class,
optimizedWithoutBarriers).forEach(s -> {
+ TraversalHelper.copyLabels(s, s.getPreviousStep(), true);
+ s.getTraversal().removeStep(s);
+ });
+ assertEquals("Without LazyBarrierStrategy: " + repr,
optimizedWithoutBarriers, clonedOriginal);
+ }
}
\ No newline at end of file
diff --git
a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/util/TraversalHelperTest.java
b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/util/TraversalHelperTest.java
index 36b3673413..bacf17622e 100644
---
a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/util/TraversalHelperTest.java
+++
b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/util/TraversalHelperTest.java
@@ -39,10 +39,13 @@ import
org.apache.tinkerpop.gremlin.process.traversal.step.filter.WhereTraversal
import org.apache.tinkerpop.gremlin.process.traversal.step.filter.NotStep;
import org.apache.tinkerpop.gremlin.process.traversal.step.map.FlatMapStep;
import org.apache.tinkerpop.gremlin.process.traversal.step.map.FoldStep;
+import org.apache.tinkerpop.gremlin.process.traversal.step.map.GraphStep;
+import
org.apache.tinkerpop.gremlin.process.traversal.step.map.GraphStepContract;
import org.apache.tinkerpop.gremlin.process.traversal.step.map.PropertiesStep;
import
org.apache.tinkerpop.gremlin.process.traversal.step.map.TraversalFlatMapStep;
import
org.apache.tinkerpop.gremlin.process.traversal.step.map.TraversalMapStep;
import org.apache.tinkerpop.gremlin.process.traversal.step.map.VertexStep;
+import
org.apache.tinkerpop.gremlin.process.traversal.step.map.VertexStepContract;
import
org.apache.tinkerpop.gremlin.process.traversal.step.sideEffect.IdentityStep;
import org.apache.tinkerpop.gremlin.process.traversal.step.util.EmptyStep;
import org.apache.tinkerpop.gremlin.structure.PropertyType;
@@ -52,7 +55,6 @@ import org.junit.Test;
import org.mockito.Mockito;
import java.util.ArrayList;
-import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
@@ -600,4 +602,49 @@ public class TraversalHelperTest {
final List<Step<?,?>> steps = (List)
TraversalHelper.getStepsOfClass(Step.class, t);
assertEquals(0, steps.size());
}
+
+ @Test
+ public void hasOnlyShouldReturnTrueWhenAllStepsAreAssignable() {
+ assertThat(TraversalHelper.hasOnlyStepsOfAssignableClassesRecursively(
+ Set.of(GraphStep.class, VertexStep.class, HasStep.class),
+ __.V().out().has("name", "marko").asAdmin()), is(true));
+ }
+
+ @Test
+ public void hasOnlyShouldReturnFalseWhenStepNotAssignable() {
+ assertThat(TraversalHelper.hasOnlyStepsOfAssignableClassesRecursively(
+ Set.of(GraphStep.class, VertexStep.class),
+ __.V().out().count().asAdmin()), is(false));
+ }
+
+ @Test
+ public void hasOnlyShouldWorkWithInterfaceClasses() {
+ assertThat(TraversalHelper.hasOnlyStepsOfAssignableClassesRecursively(
+ Set.of(GraphStepContract.class, VertexStepContract.class),
+ __.V().out().asAdmin()), is(true));
+ }
+
+ @Test
+ public void hasOnlyShouldWorkRecursivelyWithNestedTraversals() {
+ assertThat(TraversalHelper.hasOnlyStepsOfAssignableClassesRecursively(
+ Set.of(GraphStep.class, RepeatStep.class,
RepeatStep.RepeatEndStep.class, VertexStep.class, HasStep.class),
+ __.V().repeat(__.out().has("name",
"marko")).times(2).asAdmin()), is(true));
+ }
+
+ @Test
+ public void hasOnlyShouldReturnFalseForNestedTraversalWithDisallowedStep()
{
+ assertThat(TraversalHelper.hasOnlyStepsOfAssignableClassesRecursively(
+ Set.of(GraphStep.class, RepeatStep.class, VertexStep.class),
+ __.V().repeat(__.out().limit(1)).times(2).asAdmin()),
is(false));
+ }
+
+ @Test
+ public void hasOnlyShouldReturnTrueForEmptyTraversal() {
+
assertThat(TraversalHelper.hasOnlyStepsOfAssignableClassesRecursively(Set.of(IdentityStep.class),
__.identity().asAdmin()), is(true));
+ }
+
+ @Test
+ public void hasOnlyShouldReturnFalseForEmptyAllowedClasses() {
+
assertThat(TraversalHelper.hasOnlyStepsOfAssignableClassesRecursively(Set.of(),
__.V().out().asAdmin()), is(false));
+ }
}
diff --git a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs
b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs
index 278919abbf..032054180f 100644
--- a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs
+++ b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs
@@ -582,6 +582,22 @@ namespace Gremlin.Net.IntegrationTest.Gherkin
{"g_withoutStrategiesXReferenceElementStrategyX_V", new
List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>>
{(g,p) =>g.WithoutStrategies(typeof(ReferenceElementStrategy)).V()}},
{"g_withStrategiesXRepeatUnrollStrategyX_V_repeatXoutX_timesX2X", new
List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>>
{(g,p) =>g.WithStrategies(new
RepeatUnrollStrategy()).V().Repeat(__.Out()).Times(2)}},
{"g_withoutStrategiesXRepeatUnrollStrategyX_V_repeatXoutX_timesX2X", new
List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>>
{(g,p)
=>g.WithoutStrategies(typeof(RepeatUnrollStrategy)).V().Repeat(__.Out()).Times(2)}},
+
{"g_withStrategiesXRepeatUnrollStrategyX_V_repeatXinX_timesX2X", new
List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>>
{(g,p) =>g.WithStrategies(new
RepeatUnrollStrategy()).V().Repeat(__.In()).Times(2)}},
+
{"g_withoutStrategiesXRepeatUnrollStrategyX_V_repeatXinX_timesX2X", new
List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>>
{(g,p)
=>g.WithoutStrategies(typeof(RepeatUnrollStrategy)).V().Repeat(__.In()).Times(2)}},
+
{"g_withStrategiesXRepeatUnrollStrategyX_V_repeatXout_hasXname_notStartingWithXzXXX_timesX2X",
new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>>
{(g,p) =>g.WithStrategies(new
RepeatUnrollStrategy()).V().Repeat(__.Out().Has("name",
TextP.NotStartingWith("z"))).Times(2)}},
+
{"g_withoutStrategiesXRepeatUnrollStrategyX_V_repeatXout_hasXname_notStartingWithXzXXX_timesX2X",
new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>>
{(g,p)
=>g.WithoutStrategies(typeof(RepeatUnrollStrategy)).V().Repeat(__.Out().Has("name",
TextP.NotStartingWith("z"))).Times(2)}},
+
{"g_withStrategiesXRepeatUnrollStrategyX_V_repeatXin_hasXage_gtX20XXX_timesX2X",
new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>>
{(g,p) =>g.WithStrategies(new
RepeatUnrollStrategy()).V().Repeat(__.In().Has("age", P.Gt(20))).Times(2)}},
+
{"g_withoutStrategiesXRepeatUnrollStrategyX_V_repeatXin_hasXage_gtX20XXX_timesX2X",
new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>>
{(g,p)
=>g.WithoutStrategies(typeof(RepeatUnrollStrategy)).V().Repeat(__.In().Has("age",
P.Gt(20))).Times(2)}},
+
{"g_withStrategiesXRepeatUnrollStrategyX_V_repeatXboth_hasXage_ltX30XXX_timesX2X",
new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>>
{(g,p) =>g.WithStrategies(new
RepeatUnrollStrategy()).V().Repeat(__.Both().Has("age", P.Lt(30))).Times(2)}},
+
{"g_withoutStrategiesXRepeatUnrollStrategyX_V_repeatXboth_hasXage_ltX30XXX_timesX2X",
new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>>
{(g,p)
=>g.WithoutStrategies(typeof(RepeatUnrollStrategy)).V().Repeat(__.Both().Has("age",
P.Lt(30))).Times(2)}},
+
{"g_withStrategiesXRepeatUnrollStrategyX_V_repeatXbothE_otherV_hasXage_ltX30XXX_timesX2X",
new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>>
{(g,p) =>g.WithStrategies(new
RepeatUnrollStrategy()).V().Repeat(__.BothE().OtherV().Has("age",
P.Lt(30))).Times(2)}},
+
{"g_withoutStrategiesXRepeatUnrollStrategyX_V_repeatXbothE_otherV_hasXage_ltX30XXX_timesX2X",
new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>>
{(g,p)
=>g.WithoutStrategies(typeof(RepeatUnrollStrategy)).V().Repeat(__.BothE().OtherV().Has("age",
P.Lt(30))).Times(2)}},
+
{"g_withStrategiesXRepeatUnrollStrategyX_V_repeatXboth_limitX1XX_timesX2X", new
List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>>
{(g,p) =>g.WithStrategies(new
RepeatUnrollStrategy()).V().Repeat(__.Both().Limit<object>(1)).Times(2)}},
+
{"g_withoutStrategiesXRepeatUnrollStrategyX_V_repeatXboth_limitX1XX_timesX2X",
new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>>
{(g,p)
=>g.WithoutStrategies(typeof(RepeatUnrollStrategy)).V().Repeat(__.Both().Limit<object>(1)).Times(2)}},
+
{"g_withStrategiesXRepeatUnrollStrategyX_V_repeatXboth_aggregateXxXX_timesX2X_limitX5X",
new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>>
{(g,p) =>g.WithStrategies(new
RepeatUnrollStrategy()).V().Order().Repeat(__.Both().Order().Aggregate("x")).Times(2).Limit<object>(5)}},
+
{"g_withoutStrategiesXRepeatUnrollStrategyX_V_repeatXboth_aggregateXxXX_timesX2X_limitX5X",
new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>>
{(g,p)
=>g.WithoutStrategies(typeof(RepeatUnrollStrategy)).V().Order().Repeat(__.Both().Order().Aggregate("x")).Times(2).Limit<object>(5)}},
+
{"g_withStrategiesXRepeatUnrollStrategyX_V_repeatXboth_sampleX1XX_timesX2X",
new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>>
{(g,p) =>g.WithStrategies(new
RepeatUnrollStrategy()).V().Repeat(__.Both().Sample(1)).Times(2)}},
+
{"g_withoutStrategiesXRepeatUnrollStrategyX_V_repeatXboth_sampleX1XX_timesX2X",
new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>>
{(g,p)
=>g.WithoutStrategies(typeof(RepeatUnrollStrategy)).V().Repeat(__.Both().Sample(1)).Times(2)}},
{"g_withStrategiesXReservedKeysVerificationStrategyXthrowException_trueXX_addVXpersonX_propertyXid_123X_propertyXname_markoX",
new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>>
{(g,p) =>g.WithStrategies(new ReservedKeysVerificationStrategy(throwException:
true)).AddV("person").Property("id", 123).Property("name", "marko")}},
{"g_withStrategiesXReservedKeysVerificationStrategyXthrowException_trueXX_addVXpersonX_propertyXage_29X_propertyXname_markoX",
new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>>
{(g,p) =>g.WithStrategies(new ReservedKeysVerificationStrategy(throwException:
true, keys: new HashSet<string> { "age" })).AddV("person").Property("age",
29).Property("name", "marko")}},
{"g_withoutStrategiesXReservedKeysVerificationStrategyX_addVXpersonX_propertyXid_123X_propertyXname_markoX",
new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>>
{(g,p)
=>g.WithoutStrategies(typeof(ReservedKeysVerificationStrategy)).AddV("person").Property("id",
123).Property("name", "marko").Values<object>()}},
diff --git a/gremlin-go/driver/cucumber/gremlin.go
b/gremlin-go/driver/cucumber/gremlin.go
index abe0614643..4dd6c5663f 100644
--- a/gremlin-go/driver/cucumber/gremlin.go
+++ b/gremlin-go/driver/cucumber/gremlin.go
@@ -552,6 +552,22 @@ var translationMap = map[string][]func(g
*gremlingo.GraphTraversalSource, p map[
"g_withoutStrategiesXReferenceElementStrategyX_V": {func(g
*gremlingo.GraphTraversalSource, p map[string]interface{})
*gremlingo.GraphTraversal {return
g.WithoutStrategies(gremlingo.ReferenceElementStrategy()).V()}},
"g_withStrategiesXRepeatUnrollStrategyX_V_repeatXoutX_timesX2X": {func(g
*gremlingo.GraphTraversalSource, p map[string]interface{})
*gremlingo.GraphTraversal {return
g.WithStrategies(gremlingo.RepeatUnrollStrategy()).V().Repeat(gremlingo.T__.Out()).Times(2)}},
"g_withoutStrategiesXRepeatUnrollStrategyX_V_repeatXoutX_timesX2X":
{func(g *gremlingo.GraphTraversalSource, p map[string]interface{})
*gremlingo.GraphTraversal {return
g.WithoutStrategies(gremlingo.RepeatUnrollStrategy()).V().Repeat(gremlingo.T__.Out()).Times(2)}},
+ "g_withStrategiesXRepeatUnrollStrategyX_V_repeatXinX_timesX2X": {func(g
*gremlingo.GraphTraversalSource, p map[string]interface{})
*gremlingo.GraphTraversal {return
g.WithStrategies(gremlingo.RepeatUnrollStrategy()).V().Repeat(gremlingo.T__.In()).Times(2)}},
+ "g_withoutStrategiesXRepeatUnrollStrategyX_V_repeatXinX_timesX2X": {func(g
*gremlingo.GraphTraversalSource, p map[string]interface{})
*gremlingo.GraphTraversal {return
g.WithoutStrategies(gremlingo.RepeatUnrollStrategy()).V().Repeat(gremlingo.T__.In()).Times(2)}},
+
"g_withStrategiesXRepeatUnrollStrategyX_V_repeatXout_hasXname_notStartingWithXzXXX_timesX2X":
{func(g *gremlingo.GraphTraversalSource, p map[string]interface{})
*gremlingo.GraphTraversal {return
g.WithStrategies(gremlingo.RepeatUnrollStrategy()).V().Repeat(gremlingo.T__.Out().Has("name",
gremlingo.TextP.NotStartingWith("z"))).Times(2)}},
+
"g_withoutStrategiesXRepeatUnrollStrategyX_V_repeatXout_hasXname_notStartingWithXzXXX_timesX2X":
{func(g *gremlingo.GraphTraversalSource, p map[string]interface{})
*gremlingo.GraphTraversal {return
g.WithoutStrategies(gremlingo.RepeatUnrollStrategy()).V().Repeat(gremlingo.T__.Out().Has("name",
gremlingo.TextP.NotStartingWith("z"))).Times(2)}},
+
"g_withStrategiesXRepeatUnrollStrategyX_V_repeatXin_hasXage_gtX20XXX_timesX2X":
{func(g *gremlingo.GraphTraversalSource, p map[string]interface{})
*gremlingo.GraphTraversal {return
g.WithStrategies(gremlingo.RepeatUnrollStrategy()).V().Repeat(gremlingo.T__.In().Has("age",
gremlingo.P.Gt(20))).Times(2)}},
+
"g_withoutStrategiesXRepeatUnrollStrategyX_V_repeatXin_hasXage_gtX20XXX_timesX2X":
{func(g *gremlingo.GraphTraversalSource, p map[string]interface{})
*gremlingo.GraphTraversal {return
g.WithoutStrategies(gremlingo.RepeatUnrollStrategy()).V().Repeat(gremlingo.T__.In().Has("age",
gremlingo.P.Gt(20))).Times(2)}},
+
"g_withStrategiesXRepeatUnrollStrategyX_V_repeatXboth_hasXage_ltX30XXX_timesX2X":
{func(g *gremlingo.GraphTraversalSource, p map[string]interface{})
*gremlingo.GraphTraversal {return
g.WithStrategies(gremlingo.RepeatUnrollStrategy()).V().Repeat(gremlingo.T__.Both().Has("age",
gremlingo.P.Lt(30))).Times(2)}},
+
"g_withoutStrategiesXRepeatUnrollStrategyX_V_repeatXboth_hasXage_ltX30XXX_timesX2X":
{func(g *gremlingo.GraphTraversalSource, p map[string]interface{})
*gremlingo.GraphTraversal {return
g.WithoutStrategies(gremlingo.RepeatUnrollStrategy()).V().Repeat(gremlingo.T__.Both().Has("age",
gremlingo.P.Lt(30))).Times(2)}},
+
"g_withStrategiesXRepeatUnrollStrategyX_V_repeatXbothE_otherV_hasXage_ltX30XXX_timesX2X":
{func(g *gremlingo.GraphTraversalSource, p map[string]interface{})
*gremlingo.GraphTraversal {return
g.WithStrategies(gremlingo.RepeatUnrollStrategy()).V().Repeat(gremlingo.T__.BothE().OtherV().Has("age",
gremlingo.P.Lt(30))).Times(2)}},
+
"g_withoutStrategiesXRepeatUnrollStrategyX_V_repeatXbothE_otherV_hasXage_ltX30XXX_timesX2X":
{func(g *gremlingo.GraphTraversalSource, p map[string]interface{})
*gremlingo.GraphTraversal {return
g.WithoutStrategies(gremlingo.RepeatUnrollStrategy()).V().Repeat(gremlingo.T__.BothE().OtherV().Has("age",
gremlingo.P.Lt(30))).Times(2)}},
+ "g_withStrategiesXRepeatUnrollStrategyX_V_repeatXboth_limitX1XX_timesX2X":
{func(g *gremlingo.GraphTraversalSource, p map[string]interface{})
*gremlingo.GraphTraversal {return
g.WithStrategies(gremlingo.RepeatUnrollStrategy()).V().Repeat(gremlingo.T__.Both().Limit(1)).Times(2)}},
+
"g_withoutStrategiesXRepeatUnrollStrategyX_V_repeatXboth_limitX1XX_timesX2X":
{func(g *gremlingo.GraphTraversalSource, p map[string]interface{})
*gremlingo.GraphTraversal {return
g.WithoutStrategies(gremlingo.RepeatUnrollStrategy()).V().Repeat(gremlingo.T__.Both().Limit(1)).Times(2)}},
+
"g_withStrategiesXRepeatUnrollStrategyX_V_repeatXboth_aggregateXxXX_timesX2X_limitX5X":
{func(g *gremlingo.GraphTraversalSource, p map[string]interface{})
*gremlingo.GraphTraversal {return
g.WithStrategies(gremlingo.RepeatUnrollStrategy()).V().Order().Repeat(gremlingo.T__.Both().Order().Aggregate("x")).Times(2).Limit(5)}},
+
"g_withoutStrategiesXRepeatUnrollStrategyX_V_repeatXboth_aggregateXxXX_timesX2X_limitX5X":
{func(g *gremlingo.GraphTraversalSource, p map[string]interface{})
*gremlingo.GraphTraversal {return
g.WithoutStrategies(gremlingo.RepeatUnrollStrategy()).V().Order().Repeat(gremlingo.T__.Both().Order().Aggregate("x")).Times(2).Limit(5)}},
+
"g_withStrategiesXRepeatUnrollStrategyX_V_repeatXboth_sampleX1XX_timesX2X":
{func(g *gremlingo.GraphTraversalSource, p map[string]interface{})
*gremlingo.GraphTraversal {return
g.WithStrategies(gremlingo.RepeatUnrollStrategy()).V().Repeat(gremlingo.T__.Both().Sample(1)).Times(2)}},
+
"g_withoutStrategiesXRepeatUnrollStrategyX_V_repeatXboth_sampleX1XX_timesX2X":
{func(g *gremlingo.GraphTraversalSource, p map[string]interface{})
*gremlingo.GraphTraversal {return
g.WithoutStrategies(gremlingo.RepeatUnrollStrategy()).V().Repeat(gremlingo.T__.Both().Sample(1)).Times(2)}},
"g_withStrategiesXReservedKeysVerificationStrategyXthrowException_trueXX_addVXpersonX_propertyXid_123X_propertyXname_markoX":
{func(g *gremlingo.GraphTraversalSource, p map[string]interface{})
*gremlingo.GraphTraversal {return
g.WithStrategies(gremlingo.ReservedKeysVerificationStrategy(gremlingo.ReservedKeysVerificationStrategyConfig{ThrowException:
true})).AddV("person").Property("id", 123).Property("name", "marko")}},
"g_withStrategiesXReservedKeysVerificationStrategyXthrowException_trueXX_addVXpersonX_propertyXage_29X_propertyXname_markoX":
{func(g *gremlingo.GraphTraversalSource, p map[string]interface{})
*gremlingo.GraphTraversal {return
g.WithStrategies(gremlingo.ReservedKeysVerificationStrategy(gremlingo.ReservedKeysVerificationStrategyConfig{ThrowException:
true, Keys: gremlingo.NewSimpleSet("age")})).AddV("person").Property("age",
29).Property("name", "marko")}},
"g_withoutStrategiesXReservedKeysVerificationStrategyX_addVXpersonX_propertyXid_123X_propertyXname_markoX":
{func(g *gremlingo.GraphTraversalSource, p map[string]interface{})
*gremlingo.GraphTraversal {return
g.WithoutStrategies(gremlingo.ReservedKeysVerificationStrategy()).AddV("person").Property("id",
123).Property("name", "marko").Values()}},
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 3230228b74..3dfb44225b 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
@@ -583,6 +583,22 @@ const gremlins = {
g_withoutStrategiesXReferenceElementStrategyX_V: [function({g}) { return
g.withoutStrategies(ReferenceElementStrategy).V() }],
g_withStrategiesXRepeatUnrollStrategyX_V_repeatXoutX_timesX2X:
[function({g}) { return g.withStrategies(new
RepeatUnrollStrategy()).V().repeat(__.out()).times(2) }],
g_withoutStrategiesXRepeatUnrollStrategyX_V_repeatXoutX_timesX2X:
[function({g}) { return
g.withoutStrategies(RepeatUnrollStrategy).V().repeat(__.out()).times(2) }],
+ g_withStrategiesXRepeatUnrollStrategyX_V_repeatXinX_timesX2X:
[function({g}) { return g.withStrategies(new
RepeatUnrollStrategy()).V().repeat(__.in_()).times(2) }],
+ g_withoutStrategiesXRepeatUnrollStrategyX_V_repeatXinX_timesX2X:
[function({g}) { return
g.withoutStrategies(RepeatUnrollStrategy).V().repeat(__.in_()).times(2) }],
+
g_withStrategiesXRepeatUnrollStrategyX_V_repeatXout_hasXname_notStartingWithXzXXX_timesX2X:
[function({g}) { return g.withStrategies(new
RepeatUnrollStrategy()).V().repeat(__.out().has("name",
TextP.notStartingWith("z"))).times(2) }],
+
g_withoutStrategiesXRepeatUnrollStrategyX_V_repeatXout_hasXname_notStartingWithXzXXX_timesX2X:
[function({g}) { return
g.withoutStrategies(RepeatUnrollStrategy).V().repeat(__.out().has("name",
TextP.notStartingWith("z"))).times(2) }],
+
g_withStrategiesXRepeatUnrollStrategyX_V_repeatXin_hasXage_gtX20XXX_timesX2X:
[function({g}) { return g.withStrategies(new
RepeatUnrollStrategy()).V().repeat(__.in_().has("age", P.gt(20))).times(2) }],
+
g_withoutStrategiesXRepeatUnrollStrategyX_V_repeatXin_hasXage_gtX20XXX_timesX2X:
[function({g}) { return
g.withoutStrategies(RepeatUnrollStrategy).V().repeat(__.in_().has("age",
P.gt(20))).times(2) }],
+
g_withStrategiesXRepeatUnrollStrategyX_V_repeatXboth_hasXage_ltX30XXX_timesX2X:
[function({g}) { return g.withStrategies(new
RepeatUnrollStrategy()).V().repeat(__.both().has("age", P.lt(30))).times(2) }],
+
g_withoutStrategiesXRepeatUnrollStrategyX_V_repeatXboth_hasXage_ltX30XXX_timesX2X:
[function({g}) { return
g.withoutStrategies(RepeatUnrollStrategy).V().repeat(__.both().has("age",
P.lt(30))).times(2) }],
+
g_withStrategiesXRepeatUnrollStrategyX_V_repeatXbothE_otherV_hasXage_ltX30XXX_timesX2X:
[function({g}) { return g.withStrategies(new
RepeatUnrollStrategy()).V().repeat(__.bothE().otherV().has("age",
P.lt(30))).times(2) }],
+
g_withoutStrategiesXRepeatUnrollStrategyX_V_repeatXbothE_otherV_hasXage_ltX30XXX_timesX2X:
[function({g}) { return
g.withoutStrategies(RepeatUnrollStrategy).V().repeat(__.bothE().otherV().has("age",
P.lt(30))).times(2) }],
+ g_withStrategiesXRepeatUnrollStrategyX_V_repeatXboth_limitX1XX_timesX2X:
[function({g}) { return g.withStrategies(new
RepeatUnrollStrategy()).V().repeat(__.both().limit(1)).times(2) }],
+
g_withoutStrategiesXRepeatUnrollStrategyX_V_repeatXboth_limitX1XX_timesX2X:
[function({g}) { return
g.withoutStrategies(RepeatUnrollStrategy).V().repeat(__.both().limit(1)).times(2)
}],
+
g_withStrategiesXRepeatUnrollStrategyX_V_repeatXboth_aggregateXxXX_timesX2X_limitX5X:
[function({g}) { return g.withStrategies(new
RepeatUnrollStrategy()).V().order().repeat(__.both().order().aggregate("x")).times(2).limit(5)
}],
+
g_withoutStrategiesXRepeatUnrollStrategyX_V_repeatXboth_aggregateXxXX_timesX2X_limitX5X:
[function({g}) { return
g.withoutStrategies(RepeatUnrollStrategy).V().order().repeat(__.both().order().aggregate("x")).times(2).limit(5)
}],
+ g_withStrategiesXRepeatUnrollStrategyX_V_repeatXboth_sampleX1XX_timesX2X:
[function({g}) { return g.withStrategies(new
RepeatUnrollStrategy()).V().repeat(__.both().sample(1)).times(2) }],
+
g_withoutStrategiesXRepeatUnrollStrategyX_V_repeatXboth_sampleX1XX_timesX2X:
[function({g}) { return
g.withoutStrategies(RepeatUnrollStrategy).V().repeat(__.both().sample(1)).times(2)
}],
g_withStrategiesXReservedKeysVerificationStrategyXthrowException_trueXX_addVXpersonX_propertyXid_123X_propertyXname_markoX:
[function({g}) { return g.withStrategies(new
ReservedKeysVerificationStrategy({throwException:
true})).addV("person").property("id", 123).property("name", "marko") }],
g_withStrategiesXReservedKeysVerificationStrategyXthrowException_trueXX_addVXpersonX_propertyXage_29X_propertyXname_markoX:
[function({g}) { return g.withStrategies(new
ReservedKeysVerificationStrategy({throwException: true, keys: new
Set(["age"])})).addV("person").property("age", 29).property("name", "marko")
}],
g_withoutStrategiesXReservedKeysVerificationStrategyX_addVXpersonX_propertyXid_123X_propertyXname_markoX:
[function({g}) { return
g.withoutStrategies(ReservedKeysVerificationStrategy).addV("person").property("id",
123).property("name", "marko").values() }],
diff --git a/gremlin-python/src/main/python/radish/gremlin.py
b/gremlin-python/src/main/python/radish/gremlin.py
index f5abcb82c4..f20bf16759 100644
--- a/gremlin-python/src/main/python/radish/gremlin.py
+++ b/gremlin-python/src/main/python/radish/gremlin.py
@@ -555,6 +555,22 @@ world.gremlins = {
'g_withoutStrategiesXReferenceElementStrategyX_V': [(lambda
g:g.without_strategies(*[GremlinType('org.apache.tinkerpop.gremlin.process.traversal.strategy.finalization.ReferenceElementStrategy')]).V())],
'g_withStrategiesXRepeatUnrollStrategyX_V_repeatXoutX_timesX2X': [(lambda
g:g.with_strategies(RepeatUnrollStrategy()).V().repeat(__.out()).times(2))],
'g_withoutStrategiesXRepeatUnrollStrategyX_V_repeatXoutX_timesX2X':
[(lambda
g:g.without_strategies(*[GremlinType('org.apache.tinkerpop.gremlin.process.traversal.strategy.optimization.RepeatUnrollStrategy')]).V().repeat(__.out()).times(2))],
+ 'g_withStrategiesXRepeatUnrollStrategyX_V_repeatXinX_timesX2X': [(lambda
g:g.with_strategies(RepeatUnrollStrategy()).V().repeat(__.in_()).times(2))],
+ 'g_withoutStrategiesXRepeatUnrollStrategyX_V_repeatXinX_timesX2X':
[(lambda
g:g.without_strategies(*[GremlinType('org.apache.tinkerpop.gremlin.process.traversal.strategy.optimization.RepeatUnrollStrategy')]).V().repeat(__.in_()).times(2))],
+
'g_withStrategiesXRepeatUnrollStrategyX_V_repeatXout_hasXname_notStartingWithXzXXX_timesX2X':
[(lambda
g:g.with_strategies(RepeatUnrollStrategy()).V().repeat(__.out().has('name',
TextP.not_starting_with('z'))).times(2))],
+
'g_withoutStrategiesXRepeatUnrollStrategyX_V_repeatXout_hasXname_notStartingWithXzXXX_timesX2X':
[(lambda
g:g.without_strategies(*[GremlinType('org.apache.tinkerpop.gremlin.process.traversal.strategy.optimization.RepeatUnrollStrategy')]).V().repeat(__.out().has('name',
TextP.not_starting_with('z'))).times(2))],
+
'g_withStrategiesXRepeatUnrollStrategyX_V_repeatXin_hasXage_gtX20XXX_timesX2X':
[(lambda
g:g.with_strategies(RepeatUnrollStrategy()).V().repeat(__.in_().has('age',
P.gt(20))).times(2))],
+
'g_withoutStrategiesXRepeatUnrollStrategyX_V_repeatXin_hasXage_gtX20XXX_timesX2X':
[(lambda
g:g.without_strategies(*[GremlinType('org.apache.tinkerpop.gremlin.process.traversal.strategy.optimization.RepeatUnrollStrategy')]).V().repeat(__.in_().has('age',
P.gt(20))).times(2))],
+
'g_withStrategiesXRepeatUnrollStrategyX_V_repeatXboth_hasXage_ltX30XXX_timesX2X':
[(lambda
g:g.with_strategies(RepeatUnrollStrategy()).V().repeat(__.both().has('age',
P.lt(30))).times(2))],
+
'g_withoutStrategiesXRepeatUnrollStrategyX_V_repeatXboth_hasXage_ltX30XXX_timesX2X':
[(lambda
g:g.without_strategies(*[GremlinType('org.apache.tinkerpop.gremlin.process.traversal.strategy.optimization.RepeatUnrollStrategy')]).V().repeat(__.both().has('age',
P.lt(30))).times(2))],
+
'g_withStrategiesXRepeatUnrollStrategyX_V_repeatXbothE_otherV_hasXage_ltX30XXX_timesX2X':
[(lambda
g:g.with_strategies(RepeatUnrollStrategy()).V().repeat(__.both_e().other_v().has('age',
P.lt(30))).times(2))],
+
'g_withoutStrategiesXRepeatUnrollStrategyX_V_repeatXbothE_otherV_hasXage_ltX30XXX_timesX2X':
[(lambda
g:g.without_strategies(*[GremlinType('org.apache.tinkerpop.gremlin.process.traversal.strategy.optimization.RepeatUnrollStrategy')]).V().repeat(__.both_e().other_v().has('age',
P.lt(30))).times(2))],
+ 'g_withStrategiesXRepeatUnrollStrategyX_V_repeatXboth_limitX1XX_timesX2X':
[(lambda
g:g.with_strategies(RepeatUnrollStrategy()).V().repeat(__.both().limit(1)).times(2))],
+
'g_withoutStrategiesXRepeatUnrollStrategyX_V_repeatXboth_limitX1XX_timesX2X':
[(lambda
g:g.without_strategies(*[GremlinType('org.apache.tinkerpop.gremlin.process.traversal.strategy.optimization.RepeatUnrollStrategy')]).V().repeat(__.both().limit(1)).times(2))],
+
'g_withStrategiesXRepeatUnrollStrategyX_V_repeatXboth_aggregateXxXX_timesX2X_limitX5X':
[(lambda
g:g.with_strategies(RepeatUnrollStrategy()).V().order().repeat(__.both().order().aggregate('x')).times(2).limit(5))],
+
'g_withoutStrategiesXRepeatUnrollStrategyX_V_repeatXboth_aggregateXxXX_timesX2X_limitX5X':
[(lambda
g:g.without_strategies(*[GremlinType('org.apache.tinkerpop.gremlin.process.traversal.strategy.optimization.RepeatUnrollStrategy')]).V().order().repeat(__.both().order().aggregate('x')).times(2).limit(5))],
+
'g_withStrategiesXRepeatUnrollStrategyX_V_repeatXboth_sampleX1XX_timesX2X':
[(lambda
g:g.with_strategies(RepeatUnrollStrategy()).V().repeat(__.both().sample(1)).times(2))],
+
'g_withoutStrategiesXRepeatUnrollStrategyX_V_repeatXboth_sampleX1XX_timesX2X':
[(lambda
g:g.without_strategies(*[GremlinType('org.apache.tinkerpop.gremlin.process.traversal.strategy.optimization.RepeatUnrollStrategy')]).V().repeat(__.both().sample(1)).times(2))],
'g_withStrategiesXReservedKeysVerificationStrategyXthrowException_trueXX_addVXpersonX_propertyXid_123X_propertyXname_markoX':
[(lambda
g:g.with_strategies(ReservedKeysVerificationStrategy(throw_exception=True)).add_v('person').property('id',
123).property('name', 'marko'))],
'g_withStrategiesXReservedKeysVerificationStrategyXthrowException_trueXX_addVXpersonX_propertyXage_29X_propertyXname_markoX':
[(lambda
g:g.with_strategies(ReservedKeysVerificationStrategy(throw_exception=True,
keys={'age'})).add_v('person').property('age', 29).property('name', 'marko'))],
'g_withoutStrategiesXReservedKeysVerificationStrategyX_addVXpersonX_propertyXid_123X_propertyXname_markoX':
[(lambda
g:g.without_strategies(*[GremlinType('org.apache.tinkerpop.gremlin.process.traversal.strategy.verification.ReservedKeysVerificationStrategy')]).add_v('person').property('id',
123).property('name', 'marko').values())],
diff --git
a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/integrated/RepeatUnrollStrategy.feature
b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/integrated/RepeatUnrollStrategy.feature
index 13fd68a8c8..9ca44f3f0f 100644
---
a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/integrated/RepeatUnrollStrategy.feature
+++
b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/integrated/RepeatUnrollStrategy.feature
@@ -42,3 +42,224 @@ Feature: Step - RepeatUnrollStrategy
| result |
| v[lop] |
| v[ripple] |
+
+ @WithRepeatUnrollStrategy
+ @GraphComputerVerificationStrategyNotSupported
+ Scenario: g_withStrategiesXRepeatUnrollStrategyX_V_repeatXinX_timesX2X
+ Given the modern graph
+ And the traversal of
+ """
+ g.withStrategies(RepeatUnrollStrategy).V().repeat(in()).times(2)
+ """
+ When iterated to list
+ Then the result should be unordered
+ | result |
+ | v[marko] |
+ | v[marko] |
+
+ @GraphComputerVerificationStrategyNotSupported
+ Scenario: g_withoutStrategiesXRepeatUnrollStrategyX_V_repeatXinX_timesX2X
+ Given the modern graph
+ And the traversal of
+ """
+ g.withoutStrategies(RepeatUnrollStrategy).V().repeat(in()).times(2)
+ """
+ When iterated to list
+ Then the result should be unordered
+ | result |
+ | v[marko] |
+ | v[marko] |
+
+ @WithRepeatUnrollStrategy
+ @GraphComputerVerificationStrategyNotSupported
+ Scenario:
g_withStrategiesXRepeatUnrollStrategyX_V_repeatXout_hasXname_notStartingWithXzXXX_timesX2X
+ Given the modern graph
+ And the traversal of
+ """
+ g.withStrategies(RepeatUnrollStrategy).V().repeat(out().has("name",
notStartingWith("z"))).times(2)
+ """
+ When iterated to list
+ Then the result should be unordered
+ | result |
+ | v[lop] |
+ | v[ripple] |
+
+ @GraphComputerVerificationStrategyNotSupported
+ Scenario:
g_withoutStrategiesXRepeatUnrollStrategyX_V_repeatXout_hasXname_notStartingWithXzXXX_timesX2X
+ Given the modern graph
+ And the traversal of
+ """
+ g.withoutStrategies(RepeatUnrollStrategy).V().repeat(out().has("name",
notStartingWith("z"))).times(2)
+ """
+ When iterated to list
+ Then the result should be unordered
+ | result |
+ | v[lop] |
+ | v[ripple] |
+
+ @WithRepeatUnrollStrategy
+ @GraphComputerVerificationStrategyNotSupported
+ Scenario:
g_withStrategiesXRepeatUnrollStrategyX_V_repeatXin_hasXage_gtX20XXX_timesX2X
+ Given the modern graph
+ And the traversal of
+ """
+ g.withStrategies(RepeatUnrollStrategy).V().repeat(in().has("age",
gt(20))).times(2)
+ """
+ When iterated to list
+ Then the result should be unordered
+ | result |
+ | v[marko] |
+ | v[marko] |
+
+ @GraphComputerVerificationStrategyNotSupported
+ Scenario:
g_withoutStrategiesXRepeatUnrollStrategyX_V_repeatXin_hasXage_gtX20XXX_timesX2X
+ Given the modern graph
+ And the traversal of
+ """
+ g.withoutStrategies(RepeatUnrollStrategy).V().repeat(in().has("age",
gt(20))).times(2)
+ """
+ When iterated to list
+ Then the result should be unordered
+ | result |
+ | v[marko] |
+ | v[marko] |
+
+ @WithRepeatUnrollStrategy
+ @GraphComputerVerificationStrategyNotSupported
+ Scenario:
g_withStrategiesXRepeatUnrollStrategyX_V_repeatXboth_hasXage_ltX30XXX_timesX2X
+ Given the modern graph
+ And the traversal of
+ """
+ g.withStrategies(RepeatUnrollStrategy).V().repeat(both().has("age",
lt(30))).times(2)
+ """
+ When iterated to list
+ Then the result should be unordered
+ | result |
+ | v[marko] |
+ | v[vadas] |
+ | v[vadas] |
+ | v[vadas] |
+
+ @GraphComputerVerificationStrategyNotSupported
+ Scenario:
g_withoutStrategiesXRepeatUnrollStrategyX_V_repeatXboth_hasXage_ltX30XXX_timesX2X
+ Given the modern graph
+ And the traversal of
+ """
+ g.withoutStrategies(RepeatUnrollStrategy).V().repeat(both().has("age",
lt(30))).times(2)
+ """
+ When iterated to list
+ Then the result should be unordered
+ | result |
+ | v[marko] |
+ | v[vadas] |
+ | v[vadas] |
+ | v[vadas] |
+
+ @WithRepeatUnrollStrategy
+ @GraphComputerVerificationStrategyNotSupported
+ Scenario:
g_withStrategiesXRepeatUnrollStrategyX_V_repeatXbothE_otherV_hasXage_ltX30XXX_timesX2X
+ Given the modern graph
+ And the traversal of
+ """
+
g.withStrategies(RepeatUnrollStrategy).V().repeat(bothE().otherV().has("age",
lt(30))).times(2)
+ """
+ When iterated to list
+ Then the result should be unordered
+ | result |
+ | v[marko] |
+ | v[vadas] |
+ | v[vadas] |
+ | v[vadas] |
+
+ @GraphComputerVerificationStrategyNotSupported
+ Scenario:
g_withoutStrategiesXRepeatUnrollStrategyX_V_repeatXbothE_otherV_hasXage_ltX30XXX_timesX2X
+ Given the modern graph
+ And the traversal of
+ """
+
g.withoutStrategies(RepeatUnrollStrategy).V().repeat(bothE().otherV().has("age",
lt(30))).times(2)
+ """
+ When iterated to list
+ Then the result should be unordered
+ | result |
+ | v[marko] |
+ | v[vadas] |
+ | v[vadas] |
+ | v[vadas] |
+
+ # this traversal is not expected to be unrolled by the strategy but should
have consistent semantics compared to traversal without the strategy applied
+ @WithRepeatUnrollStrategy
+ @GraphComputerVerificationStrategyNotSupported
+ Scenario:
g_withStrategiesXRepeatUnrollStrategyX_V_repeatXboth_limitX1XX_timesX2X
+ Given the modern graph
+ And the traversal of
+ """
+
g.withStrategies(RepeatUnrollStrategy).V().repeat(both().limit(1)).times(2)
+ """
+ When iterated to list
+ Then the result should be empty
+
+ @GraphComputerVerificationStrategyNotSupported
+ Scenario:
g_withoutStrategiesXRepeatUnrollStrategyX_V_repeatXboth_limitX1XX_timesX2X
+ Given the modern graph
+ And the traversal of
+ """
+
g.withoutStrategies(RepeatUnrollStrategy).V().repeat(both().limit(1)).times(2)
+ """
+ When iterated to list
+ Then the result should be empty
+
+ # this traversal is not expected to be unrolled by the strategy but should
have consistent semantics compared to traversal without the strategy applied
+ @WithRepeatUnrollStrategy
+ @GraphComputerVerificationStrategyNotSupported
+ Scenario:
g_withStrategiesXRepeatUnrollStrategyX_V_repeatXboth_aggregateXxXX_timesX2X_limitX5X
+ Given the modern graph
+ And the traversal of
+ """
+
g.withStrategies(RepeatUnrollStrategy).V().order().repeat(both().order().aggregate('x')).times(2).limit(5)
+ """
+ When iterated to list
+ Then the result should be unordered
+ | result |
+ | v[marko] |
+ | v[marko] |
+ | v[marko] |
+ | v[lop] |
+ | v[josh] |
+
+ @GraphComputerVerificationStrategyNotSupported
+ Scenario:
g_withoutStrategiesXRepeatUnrollStrategyX_V_repeatXboth_aggregateXxXX_timesX2X_limitX5X
+ Given the modern graph
+ And the traversal of
+ """
+
g.withoutStrategies(RepeatUnrollStrategy).V().order().repeat(both().order().aggregate('x')).times(2).limit(5)
+ """
+ When iterated to list
+ Then the result should be unordered
+ | result |
+ | v[marko] |
+ | v[marko] |
+ | v[marko] |
+ | v[lop] |
+ | v[josh] |
+
+ # this traversal is not expected to be unrolled by the strategy but should
have consistent semantics compared to traversal without the strategy applied
+ @WithRepeatUnrollStrategy
+ @GraphComputerVerificationStrategyNotSupported
+ Scenario:
g_withStrategiesXRepeatUnrollStrategyX_V_repeatXboth_sampleX1XX_timesX2X
+ Given the modern graph
+ And the traversal of
+ """
+
g.withStrategies(RepeatUnrollStrategy).V().repeat(both().sample(1)).times(2)
+ """
+ When iterated to list
+ Then the result should have a count of 6
+
+ @GraphComputerVerificationStrategyNotSupported
+ Scenario:
g_withoutStrategiesXRepeatUnrollStrategyX_V_repeatXboth_sampleX1XX_timesX2X
+ Given the modern graph
+ And the traversal of
+ """
+
g.withoutStrategies(RepeatUnrollStrategy).V().repeat(both().sample(1)).times(2)
+ """
+ When iterated to list
+ Then the result should have a count of 6