You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tinkerpop.apache.org by sp...@apache.org on 2020/06/13 17:46:09 UTC
[tinkerpop] 01/01: TINKERPOP-2014 SeedStrategy to specify the seed
for traversal randoms
This is an automated email from the ASF dual-hosted git repository.
spmallette pushed a commit to branch TINKERPOP-2014
in repository https://gitbox.apache.org/repos/asf/tinkerpop.git
commit d5508e17812893dca3ea01d18d145f453f2fcc57
Author: Stephen Mallette <sp...@genoprime.com>
AuthorDate: Thu Jun 11 12:33:53 2020 -0400
TINKERPOP-2014 SeedStrategy to specify the seed for traversal randoms
---
CHANGELOG.asciidoc | 1 +
docs/src/reference/the-traversal.asciidoc | 38 ++++++++
docs/src/upgrade/release-3.5.x.asciidoc | 22 +++++
.../tinkerpop/gremlin/process/traversal/Order.java | 6 +-
.../gremlin/process/traversal/step/Seedable.java | 32 +++++++
.../process/traversal/step/filter/CoinStep.java | 36 ++-----
.../traversal/step/filter/SampleGlobalStep.java | 12 ++-
.../traversal/step/map/OrderGlobalStep.java | 24 ++++-
.../process/traversal/step/map/OrderLocalStep.java | 22 +++--
.../traversal/step/map/SampleLocalStep.java | 14 ++-
.../strategy/decoration/SeedStrategy.java | 79 ++++++++++++++++
.../traversal/traverser/util/TraverserSet.java | 6 +-
.../structure/io/graphson/GraphSONModule.java | 5 +
.../gremlin/structure/io/gryo/GryoVersion.java | 7 +-
.../traverser/util/IndexedTraverserSetTest.java | 3 +-
.../Traversal/Strategy/Decoration/SeedStrategy.cs | 51 ++++++++++
.../DriverRemoteConnection/GraphTraversalTests.cs | 14 +++
.../python/gremlin_python/process/strategies.py | 6 ++
.../tests/driver/test_driver_remote_connection.py | 10 +-
.../gremlin/process/ProcessComputerSuite.java | 2 +
.../gremlin/process/ProcessStandardSuite.java | 2 +
.../decoration/SeedStrategyProcessTest.java | 105 +++++++++++++++++++++
22 files changed, 442 insertions(+), 55 deletions(-)
diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc
index 33ede9d..abe9b78 100644
--- a/CHANGELOG.asciidoc
+++ b/CHANGELOG.asciidoc
@@ -31,6 +31,7 @@ This release also includes changes from <<release-3-4-3, 3.4.3>>.
* Allowed `property(T.label,Object)` to be used if no value was supplied to `addV(String)`.
* Added a `Graph.Feature` for `supportsNullPropertyValues`.
* Modified `TokenTraversal` to support `Property` thus `by(key)` and `by(value)` can now apply to `Edge` and meta-properties.
+* Added `SeedStrategy` to allow deterministic behavior for `coin()`, `sample()` and `Order.shuffle`.
* Added `Grouping` step interface.
* Added `TraversalParent.replaceTraversal()` which can replace a direct child traversal.
* Added `ByModulatorOptimizationStrategy` which replaces certain standard traversals w/ optimized traversals (e.g. `TokenTraversal`).
diff --git a/docs/src/reference/the-traversal.asciidoc b/docs/src/reference/the-traversal.asciidoc
index 6b6f965..297d65c 100644
--- a/docs/src/reference/the-traversal.asciidoc
+++ b/docs/src/reference/the-traversal.asciidoc
@@ -4075,6 +4075,44 @@ Type ':help' or ':h' for help.
Display stack trace? [yN]
----
+=== SeedStrategy
+
+There are number of components of the Gremlin language that, by design, can produce non-deterministic results:
+
+* <<coin-step,coin()>>
+* <<order-step,order()>> when `Order.shuffle` is used
+* <<sample-step,sample()>>
+
+To get these steps to return deterministic results, `SeedStrategy` allows assignment of a seed value to the `Random`
+operations of the steps. The following example demonstrates the random nature of `shuffle`:
+
+[gremlin-groovy,modern]
+----
+g.V().values('name').fold().order(local).by(shuffle)
+g.V().values('name').fold().order(local).by(shuffle)
+g.V().values('name').fold().order(local).by(shuffle)
+g.V().values('name').fold().order(local).by(shuffle)
+g.V().values('name').fold().order(local).by(shuffle)
+----
+
+With `SeedStrategy` in place, however, the same order is applied each time:
+
+[gremlin-groovy,modern]
+----
+seedStrategy = new SeedStrategy(999998L)
+g.withStategy(seedStrategy).V().values('name').fold().order(local).by(shuffle)
+g.withStategy(seedStrategy).V().values('name').fold().order(local).by(shuffle)
+g.withStategy(seedStrategy).V().values('name').fold().order(local).by(shuffle)
+g.withStategy(seedStrategy).V().values('name').fold().order(local).by(shuffle)
+g.withStategy(seedStrategy).V().values('name').fold().order(local).by(shuffle)
+----
+
+IMPORTANT: `SeedStrategy` only makes specific steps behave in a deterministic fashion and does not necessarily make
+the entire traversal deterministic itself. If the underlying graph database or processing engine happens to not
+guarantee iteration order, then it is possible that the final result of the traversal will appear to be
+non-deterministic. In these cases, it would be necessary to enforce a deterministic iteration with `order()` prior to
+these steps that make use of randomness to return results.
+
=== SubgraphStrategy
`SubgraphStrategy` is similar to `PartitionStrategy` in that it constrains a `Traversal` to certain vertices, edges,
diff --git a/docs/src/upgrade/release-3.5.x.asciidoc b/docs/src/upgrade/release-3.5.x.asciidoc
index 56b7757..03bbe7d 100644
--- a/docs/src/upgrade/release-3.5.x.asciidoc
+++ b/docs/src/upgrade/release-3.5.x.asciidoc
@@ -235,6 +235,28 @@ be replaced by `by(id)`, thus replacing a step-based traversal with a token-base
See: link:https://issues.apache.org/jira/browse/TINKERPOP-1682[TINKERPOP-1682]
+==== SeedStrategy
+
+The new `SeedStrategy` allows the user to set a seed value for steps that make use of `Random` so that the traversal
+has the ability to return deterministic results. While this feature is useful for testing and debugging purposes,
+there are also some practical applications as well.
+
+[source,text]
+----
+gremlin> g.V().values('name').fold().order(local).by(shuffle)
+==>[josh,marko,vadas,peter,ripple,lop]
+gremlin> g.V().values('name').fold().order(local).by(shuffle)
+==>[vadas,lop,marko,peter,josh,ripple]
+gremlin> g.V().values('name').fold().order(local).by(shuffle)
+==>[peter,ripple,josh,lop,marko,vadas]
+gremlin> g.withStrategies(new SeedStrategy(22323)).V().values('name').fold().order(local).by(shuffle)
+==>[lop,peter,josh,marko,vadas,ripple]
+gremlin> g.withStrategies(new SeedStrategy(22323)).V().values('name').fold().order(local).by(shuffle)
+==>[lop,peter,josh,marko,vadas,ripple]
+gremlin> g.withStrategies(new SeedStrategy(22323)).V().values('name').fold().order(local).by(shuffle)
+==>[lop,peter,josh,marko,vadas,ripple]
+----
+
==== by(T) for Property
The `Property` interface is not included in the hierarchy of `Element`. This means that an edge property or a
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Order.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Order.java
index 1616898..d4d55b0 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Order.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Order.java
@@ -32,14 +32,16 @@ import org.apache.tinkerpop.gremlin.util.NumberHelper;
public enum Order implements Comparator<Object> {
/**
- * Order in a random fashion.
+ * Order in a random fashion. While this enum implements {@code Comparator}, the {@code compare(a,b)} method is not
+ * supported as a direct call. This change to the implementation of {@code compare(a,b)} occurred at 3.5.0 but
+ * this implementation was never used directly within the TinkerPop code base.
*
* @since 3.0.0-incubating
*/
shuffle {
@Override
public int compare(final Object first, final Object second) {
- return RANDOM.nextBoolean() ? -1 : 1;
+ throw new UnsupportedOperationException("Order.shuffle should not be used as an actual Comparator - it is a marker only");
}
@Override
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/Seedable.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/Seedable.java
new file mode 100644
index 0000000..29d1cb5
--- /dev/null
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/Seedable.java
@@ -0,0 +1,32 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.tinkerpop.gremlin.process.traversal.step;
+
+import org.apache.tinkerpop.gremlin.process.traversal.strategy.decoration.SeedStrategy;
+
+/**
+ * An interface implemented by steps that have some form of {@code Random} usage that can be configured by way of a
+ * {@code seed} to allow for deterministic behavior of the step.
+ *
+ * @see SeedStrategy
+ */
+public interface Seedable {
+
+ public void resetSeed(final long seed);
+}
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/CoinStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/CoinStep.java
index db2480d..a7d86cb 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/CoinStep.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/CoinStep.java
@@ -20,6 +20,7 @@ package org.apache.tinkerpop.gremlin.process.traversal.step.filter;
import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
import org.apache.tinkerpop.gremlin.process.traversal.Traverser;
+import org.apache.tinkerpop.gremlin.process.traversal.step.Seedable;
import org.apache.tinkerpop.gremlin.process.traversal.traverser.TraverserRequirement;
import org.apache.tinkerpop.gremlin.structure.util.StringFactory;
@@ -30,9 +31,9 @@ import java.util.Set;
/**
* @author Marko A. Rodriguez (http://markorodriguez.com)
*/
-public final class CoinStep<S> extends FilterStep<S> {
+public final class CoinStep<S> extends FilterStep<S> implements Seedable {
- private static final Random RANDOM = new Random();
+ private final Random random = new Random();
private final double probability;
public CoinStep(final Traversal.Admin traversal, final double probability) {
@@ -41,19 +42,18 @@ public final class CoinStep<S> extends FilterStep<S> {
}
@Override
+ public void resetSeed(final long seed) {
+ random.setSeed(seed);
+ }
+
+ @Override
protected boolean filter(final Traverser.Admin<S> traverser) {
long newBulk = 0l;
if (traverser.bulk() < 100) {
for (int i = 0; i < traverser.bulk(); i++) {
- if (this.probability >= RANDOM.nextDouble())
+ if (this.probability >= random.nextDouble())
newBulk++;
}
- /*} else if (traverser.bulk() < 1000000) {
- final double cumulative = RANDOM.nextDouble();
- final long current = Double.valueOf(traverser.bulk() / 2.0d).longValue();
- final double next = choose(traverser.bulk(), current) * Math.pow(this.probability,current) * Math.pow(1.0d - this.probability,traverser.bulk() - current);
- if()
- */
} else {
newBulk = Math.round(this.probability * traverser.bulk());
}
@@ -76,22 +76,4 @@ public final class CoinStep<S> extends FilterStep<S> {
public Set<TraverserRequirement> getRequirements() {
return Collections.singleton(TraverserRequirement.BULK);
}
-
- //////
-
- private static double choose(long x, long y) {
- if (y < 0 || y > x) return 0;
- if (y > x / 2) {
- // choose(n,k) == choose(n,n-k),
- // so this could save a little effort
- y = x - y;
- }
-
- double denominator = 1.0, numerator = 1.0;
- for (long i = 1; i <= y; i++) {
- denominator *= i;
- numerator *= (x + 1 - i);
- }
- return numerator / denominator;
- }
}
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 fe8fb03..e0f772a 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
@@ -22,6 +22,7 @@ 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;
import org.apache.tinkerpop.gremlin.process.traversal.step.ByModulating;
+import org.apache.tinkerpop.gremlin.process.traversal.step.Seedable;
import org.apache.tinkerpop.gremlin.process.traversal.step.TraversalParent;
import org.apache.tinkerpop.gremlin.process.traversal.step.util.CollectingBarrierStep;
import org.apache.tinkerpop.gremlin.process.traversal.traverser.ProjectedTraverser;
@@ -38,11 +39,11 @@ import java.util.Set;
/**
* @author Marko A. Rodriguez (http://markorodriguez.com)
*/
-public final class SampleGlobalStep<S> extends CollectingBarrierStep<S> implements TraversalParent, ByModulating {
+public final class SampleGlobalStep<S> extends CollectingBarrierStep<S> implements TraversalParent, ByModulating, Seedable {
private Traversal.Admin<S, Number> probabilityTraversal = new ConstantTraversal<>(1.0d);
private final int amountToSample;
- private static final Random RANDOM = new Random();
+ private final Random random = new Random();
public SampleGlobalStep(final Traversal.Admin traversal, final int amountToSample) {
super(traversal);
@@ -50,6 +51,11 @@ public final class SampleGlobalStep<S> extends CollectingBarrierStep<S> implemen
}
@Override
+ public void resetSeed(final long seed) {
+ random.setSeed(seed);
+ }
+
+ @Override
public List<Traversal.Admin<S, Number>> getLocalChildren() {
return Collections.singletonList(this.probabilityTraversal);
}
@@ -99,7 +105,7 @@ public final class SampleGlobalStep<S> extends CollectingBarrierStep<S> implemen
final double currentWeight = ((ProjectedTraverser<S, Number>) s).getProjections().get(0).doubleValue();
for (int i = 0; i < (s.bulk() - sampleBulk); i++) {
runningWeight = runningWeight + currentWeight;
- if (RANDOM.nextDouble() <= ((runningWeight / totalWeight))) {
+ if (random.nextDouble() <= ((runningWeight / totalWeight))) {
final Traverser.Admin<S> split = s.split();
split.setBulk(1L);
sampledSet.add(split);
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/OrderGlobalStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/OrderGlobalStep.java
index 1780139..e255fc6 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/OrderGlobalStep.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/OrderGlobalStep.java
@@ -25,6 +25,7 @@ import org.apache.tinkerpop.gremlin.process.traversal.Traverser;
import org.apache.tinkerpop.gremlin.process.traversal.lambda.IdentityTraversal;
import org.apache.tinkerpop.gremlin.process.traversal.step.ByModulating;
import org.apache.tinkerpop.gremlin.process.traversal.step.ComparatorHolder;
+import org.apache.tinkerpop.gremlin.process.traversal.step.Seedable;
import org.apache.tinkerpop.gremlin.process.traversal.step.TraversalParent;
import org.apache.tinkerpop.gremlin.process.traversal.step.util.CollectingBarrierStep;
import org.apache.tinkerpop.gremlin.process.traversal.traverser.ProjectedTraverser;
@@ -41,6 +42,7 @@ import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
+import java.util.Random;
import java.util.Set;
import java.util.function.BinaryOperator;
import java.util.stream.Collectors;
@@ -48,22 +50,28 @@ import java.util.stream.Collectors;
/**
* @author Marko A. Rodriguez (http://markorodriguez.com)
*/
-public final class OrderGlobalStep<S, C extends Comparable> extends CollectingBarrierStep<S> implements ComparatorHolder<S, C>, TraversalParent, ByModulating {
+public final class OrderGlobalStep<S, C extends Comparable> extends CollectingBarrierStep<S> implements ComparatorHolder<S, C>, TraversalParent, ByModulating, Seedable {
private List<Pair<Traversal.Admin<S, C>, Comparator<C>>> comparators = new ArrayList<>();
private MultiComparator<C> multiComparator = null;
private long limit = Long.MAX_VALUE;
+ private final Random random = new Random();
public OrderGlobalStep(final Traversal.Admin traversal) {
super(traversal);
}
@Override
+ public void resetSeed(long seed) {
+ this.random.setSeed(seed);
+ }
+
+ @Override
public void barrierConsumer(final TraverserSet<S> traverserSet) {
if (null == this.multiComparator) this.multiComparator = this.createMultiComparator();
//
if (this.multiComparator.isShuffle())
- traverserSet.shuffle();
+ traverserSet.shuffle(random);
else
traverserSet.sort((Comparator) this.multiComparator);
}
@@ -159,10 +167,14 @@ public final class OrderGlobalStep<S, C extends Comparable> extends CollectingBa
@Override
public MemoryComputeKey<TraverserSet<S>> getMemoryComputeKey() {
if (null == this.multiComparator) this.multiComparator = this.createMultiComparator();
- return MemoryComputeKey.of(this.getId(), new OrderBiOperator<>(this.limit, this.multiComparator), false, true);
+ return MemoryComputeKey.of(this.getId(), new OrderBiOperator<>(this.limit, this.multiComparator, this.random), false, true);
}
private final ProjectedTraverser<S, Object> createProjectedTraverser(final Traverser.Admin<S> traverser) {
+ // this was ProjectedTraverser<S, C> but the projection may not be C in the case of a lambda where a
+ // Comparable may not be expected but rather an object that can be compared in any way given a lambda.
+ // not sure why this is suddenly an issue but Intellij would not let certain tests pass without this
+ // adjustment here.
final List<Object> projections = new ArrayList<>(this.comparators.size());
for (final Pair<Traversal.Admin<S, C>, Comparator<C>> pair : this.comparators) {
projections.add(TraversalUtil.apply(traverser, pair.getValue0()));
@@ -184,14 +196,16 @@ public final class OrderGlobalStep<S, C extends Comparable> extends CollectingBa
private long limit;
private MultiComparator comparator;
+ private Random random;
private OrderBiOperator() {
// for serializers that need a no-arg constructor
}
- public OrderBiOperator(final long limit, final MultiComparator multiComparator) {
+ public OrderBiOperator(final long limit, final MultiComparator multiComparator, final Random random) {
this.limit = limit;
this.comparator = multiComparator;
+ this.random = random;
}
@Override
@@ -199,7 +213,7 @@ public final class OrderGlobalStep<S, C extends Comparable> extends CollectingBa
setA.addAll(setB);
if (this.limit != -1 && setA.bulkSize() > this.limit) {
if (this.comparator.isShuffle())
- setA.shuffle();
+ setA.shuffle(random);
else
setA.sort(this.comparator);
long counter = 0L;
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/OrderLocalStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/OrderLocalStep.java
index 6597418..eb3a5f8 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/OrderLocalStep.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/OrderLocalStep.java
@@ -24,6 +24,7 @@ import org.apache.tinkerpop.gremlin.process.traversal.Traverser;
import org.apache.tinkerpop.gremlin.process.traversal.lambda.IdentityTraversal;
import org.apache.tinkerpop.gremlin.process.traversal.step.ByModulating;
import org.apache.tinkerpop.gremlin.process.traversal.step.ComparatorHolder;
+import org.apache.tinkerpop.gremlin.process.traversal.step.Seedable;
import org.apache.tinkerpop.gremlin.process.traversal.step.TraversalParent;
import org.apache.tinkerpop.gremlin.process.traversal.traverser.TraverserRequirement;
import org.apache.tinkerpop.gremlin.structure.util.StringFactory;
@@ -37,30 +38,37 @@ import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
+import java.util.Random;
import java.util.Set;
import java.util.stream.Collectors;
/**
* @author Marko A. Rodriguez (http://markorodriguez.com)
*/
-public final class OrderLocalStep<S, C extends Comparable> extends ScalarMapStep<S, S> implements ComparatorHolder<S, C>, ByModulating, TraversalParent {
+public final class OrderLocalStep<S, C extends Comparable> extends ScalarMapStep<S, S> implements ComparatorHolder<S, C>, ByModulating, TraversalParent, Seedable {
private List<Pair<Traversal.Admin<S, C>, Comparator<C>>> comparators = new ArrayList<>();
private ChainedComparator<S, C> chainedComparator = null;
+ private final Random random = new Random();
public OrderLocalStep(final Traversal.Admin traversal) {
super(traversal);
}
@Override
+ public void resetSeed(long seed) {
+ this.random.setSeed(seed);
+ }
+
+ @Override
protected S map(final Traverser.Admin<S> traverser) {
if (null == this.chainedComparator)
this.chainedComparator = new ChainedComparator<>(false, this.comparators);
final S start = traverser.get();
if (start instanceof Collection)
- return (S) OrderLocalStep.sortCollection((Collection) start, this.chainedComparator);
+ return (S) sortCollection((Collection) start, this.chainedComparator);
else if (start instanceof Map)
- return (S) OrderLocalStep.sortMap((Map) start, this.chainedComparator);
+ return (S) sortMap((Map) start, this.chainedComparator);
else
return start;
}
@@ -142,10 +150,10 @@ public final class OrderLocalStep<S, C extends Comparable> extends ScalarMapStep
/////////////
- private static final <A> List<A> sortCollection(final Collection<A> collection, final ChainedComparator comparator) {
+ private <A> List<A> sortCollection(final Collection<A> collection, final ChainedComparator comparator) {
if (collection instanceof List) {
if (comparator.isShuffle())
- Collections.shuffle((List) collection);
+ Collections.shuffle((List) collection, random);
else
Collections.sort((List) collection, comparator);
return (List<A>) collection;
@@ -154,10 +162,10 @@ public final class OrderLocalStep<S, C extends Comparable> extends ScalarMapStep
}
}
- private static final <K, V> Map<K, V> sortMap(final Map<K, V> map, final ChainedComparator comparator) {
+ private <K, V> Map<K, V> sortMap(final Map<K, V> map, final ChainedComparator comparator) {
final List<Map.Entry<K, V>> entries = new ArrayList<>(map.entrySet());
if (comparator.isShuffle())
- Collections.shuffle(entries);
+ Collections.shuffle(entries, random);
else
Collections.sort(entries, comparator);
final LinkedHashMap<K, V> sortedMap = new LinkedHashMap<>();
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/SampleLocalStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/SampleLocalStep.java
index ef65202..052ca15 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/SampleLocalStep.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/SampleLocalStep.java
@@ -21,6 +21,7 @@ package org.apache.tinkerpop.gremlin.process.traversal.step.map;
import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
import org.apache.tinkerpop.gremlin.process.traversal.Traverser;
+import org.apache.tinkerpop.gremlin.process.traversal.step.Seedable;
import org.apache.tinkerpop.gremlin.process.traversal.traverser.TraverserRequirement;
import java.util.ArrayList;
@@ -36,9 +37,9 @@ import java.util.Set;
* @author Daniel Kuppitz (http://gremlin.guru)
* @author Marko A. Rodriguez (http://markorodriguez.com)
*/
-public final class SampleLocalStep<S> extends ScalarMapStep<S, S> {
+public final class SampleLocalStep<S> extends ScalarMapStep<S, S> implements Seedable {
- private static final Random RANDOM = new Random();
+ private final Random random = new Random();
private final int amountToSample;
@@ -48,6 +49,11 @@ public final class SampleLocalStep<S> extends ScalarMapStep<S, S> {
}
@Override
+ public void resetSeed(final long seed) {
+ this.random.setSeed(seed);
+ }
+
+ @Override
protected S map(final Traverser.Admin<S> traverser) {
final S start = traverser.get();
if (start instanceof Map) {
@@ -66,7 +72,7 @@ public final class SampleLocalStep<S> extends ScalarMapStep<S, S> {
final List<S> original = new ArrayList<>(collection);
final List<S> target = new ArrayList<>();
while (target.size() < this.amountToSample) {
- target.add(original.remove(RANDOM.nextInt(original.size())));
+ target.add(original.remove(random.nextInt(original.size())));
}
return (S) target;
}
@@ -78,7 +84,7 @@ public final class SampleLocalStep<S> extends ScalarMapStep<S, S> {
final List<Map.Entry> original = new ArrayList<>(map.entrySet());
final Map target = new LinkedHashMap<>(this.amountToSample);
while (target.size() < this.amountToSample) {
- final Map.Entry entry = original.remove(RANDOM.nextInt(original.size()));
+ final Map.Entry entry = original.remove(random.nextInt(original.size()));
target.put(entry.getKey(), entry.getValue());
}
return (S) target;
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/strategy/decoration/SeedStrategy.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/strategy/decoration/SeedStrategy.java
new file mode 100644
index 0000000..dbf9324
--- /dev/null
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/strategy/decoration/SeedStrategy.java
@@ -0,0 +1,79 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.tinkerpop.gremlin.process.traversal.strategy.decoration;
+
+import org.apache.commons.configuration2.Configuration;
+import org.apache.commons.configuration2.MapConfiguration;
+import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
+import org.apache.tinkerpop.gremlin.process.traversal.TraversalStrategy;
+import org.apache.tinkerpop.gremlin.process.traversal.step.Seedable;
+import org.apache.tinkerpop.gremlin.process.traversal.strategy.AbstractTraversalStrategy;
+import org.apache.tinkerpop.gremlin.process.traversal.util.TraversalHelper;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A strategy that resets the specified {@code seed} value for {@link Seedable} steps, which in turn will produce
+ * deterministic results from those steps. It is important to note that when using this strategy that it only
+ * guarantees deterministic results from a step but not from an entire traversal. For example, if a graph does no
+ * guarantee iteration order for {@code g.V()} then repeated runs of {@code g.V().coin(0.5)} with this strategy
+ * will return the same number of results but not necessarily the same ones. The same problem can occur in OLAP-based
+ * traversals where iteration order is not explicitly guaranteed. The only way to ensure completely deterministic
+ * results in that sense is to apply some form of {@code order()} in these cases
+ */
+public class SeedStrategy extends AbstractTraversalStrategy<TraversalStrategy.DecorationStrategy>
+ implements TraversalStrategy.DecorationStrategy {
+
+ private final long seed;
+
+ public SeedStrategy(final long seed) {
+ this.seed = seed;
+ }
+
+ public long getSeed() {
+ return seed;
+ }
+
+ @Override
+ public void apply(final Traversal.Admin<?, ?> traversal) {
+ final List<Seedable> seedableSteps = TraversalHelper.getStepsOfAssignableClass(Seedable.class, traversal);
+ for (final Seedable seedableStepsToReset : seedableSteps) {
+ seedableStepsToReset.resetSeed(seed);
+ }
+ }
+
+ public static final String ID_SEED = "seed";
+
+ public static SeedStrategy create(final Configuration configuration) {
+ if (!configuration.containsKey(ID_SEED))
+ throw new IllegalArgumentException("SeedStrategy configuration requires a 'seed' value");
+
+ return new SeedStrategy(Long.parseLong(configuration.getProperty(ID_SEED).toString()));
+ }
+
+ @Override
+ public Configuration getConfiguration() {
+ final Map<String, Object> map = new HashMap<>();
+ map.put(STRATEGY, SeedStrategy.class.getCanonicalName());
+ map.put(ID_SEED, this.seed);
+ return new MapConfiguration(map);
+ }
+}
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/traverser/util/TraverserSet.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/traverser/util/TraverserSet.java
index 52bf4d8..e25bbb1 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/traverser/util/TraverserSet.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/traverser/util/TraverserSet.java
@@ -32,6 +32,7 @@ import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Queue;
+import java.util.Random;
import java.util.Set;
import java.util.Spliterator;
@@ -41,6 +42,7 @@ import java.util.Spliterator;
public class TraverserSet<S> extends AbstractSet<Traverser.Admin<S>> implements Set<Traverser.Admin<S>>, Queue<Traverser.Admin<S>>, Serializable {
private final Map<Traverser.Admin<S>, Traverser.Admin<S>> map = Collections.synchronizedMap(new LinkedHashMap<>());
+ private static final Random RAND = new Random();
public TraverserSet() {
@@ -153,10 +155,10 @@ public class TraverserSet<S> extends AbstractSet<Traverser.Admin<S>> implements
list.forEach(traverser -> this.map.put(traverser, traverser));
}
- public void shuffle() {
+ public void shuffle(final Random random) {
final List<Traverser.Admin<S>> list = new ArrayList<>(this.map.size());
IteratorUtils.removeOnNext(this.map.values().iterator()).forEachRemaining(list::add);
- Collections.shuffle(list);
+ Collections.shuffle(list, random);
this.map.clear();
list.forEach(traverser -> this.map.put(traverser, traverser));
}
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/GraphSONModule.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/GraphSONModule.java
index 5d3c806..f51b34c 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/GraphSONModule.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/GraphSONModule.java
@@ -41,6 +41,7 @@ import org.apache.tinkerpop.gremlin.process.traversal.strategy.decoration.EventS
import org.apache.tinkerpop.gremlin.process.traversal.strategy.decoration.HaltedTraverserStrategy;
import org.apache.tinkerpop.gremlin.process.traversal.strategy.decoration.OptionsStrategy;
import org.apache.tinkerpop.gremlin.process.traversal.strategy.decoration.PartitionStrategy;
+import org.apache.tinkerpop.gremlin.process.traversal.strategy.decoration.SeedStrategy;
import org.apache.tinkerpop.gremlin.process.traversal.strategy.decoration.SubgraphStrategy;
import org.apache.tinkerpop.gremlin.process.traversal.strategy.finalization.MatchAlgorithmStrategy;
import org.apache.tinkerpop.gremlin.process.traversal.strategy.optimization.AdjacentToIncidentStrategy;
@@ -186,6 +187,7 @@ abstract class GraphSONModule extends TinkerPopJacksonModule {
HaltedTraverserStrategy.class,
PartitionStrategy.class,
SubgraphStrategy.class,
+ SeedStrategy.class,
LazyBarrierStrategy.class,
MatchAlgorithmStrategy.class,
AdjacentToIncidentStrategy.class,
@@ -313,6 +315,7 @@ abstract class GraphSONModule extends TinkerPopJacksonModule {
HaltedTraverserStrategy.class,
PartitionStrategy.class,
SubgraphStrategy.class,
+ SeedStrategy.class,
LazyBarrierStrategy.class,
MatchAlgorithmStrategy.class,
AdjacentToIncidentStrategy.class,
@@ -420,6 +423,7 @@ abstract class GraphSONModule extends TinkerPopJacksonModule {
HaltedTraverserStrategy.class,
PartitionStrategy.class,
SubgraphStrategy.class,
+ SeedStrategy.class,
LazyBarrierStrategy.class,
MatchAlgorithmStrategy.class,
AdjacentToIncidentStrategy.class,
@@ -537,6 +541,7 @@ abstract class GraphSONModule extends TinkerPopJacksonModule {
HaltedTraverserStrategy.class,
PartitionStrategy.class,
SubgraphStrategy.class,
+ SeedStrategy.class,
LazyBarrierStrategy.class,
MatchAlgorithmStrategy.class,
AdjacentToIncidentStrategy.class,
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/gryo/GryoVersion.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/gryo/GryoVersion.java
index 41e6e27..eb9566e 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/gryo/GryoVersion.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/gryo/GryoVersion.java
@@ -50,6 +50,7 @@ import org.apache.tinkerpop.gremlin.process.traversal.strategy.decoration.Connec
import org.apache.tinkerpop.gremlin.process.traversal.strategy.decoration.HaltedTraverserStrategy;
import org.apache.tinkerpop.gremlin.process.traversal.strategy.decoration.OptionsStrategy;
import org.apache.tinkerpop.gremlin.process.traversal.strategy.decoration.PartitionStrategy;
+import org.apache.tinkerpop.gremlin.process.traversal.strategy.decoration.SeedStrategy;
import org.apache.tinkerpop.gremlin.process.traversal.strategy.decoration.SubgraphStrategy;
import org.apache.tinkerpop.gremlin.process.traversal.strategy.finalization.MatchAlgorithmStrategy;
import org.apache.tinkerpop.gremlin.process.traversal.strategy.optimization.AdjacentToIncidentStrategy;
@@ -334,11 +335,12 @@ public enum GryoVersion {
add(GryoTypeReg.of(HaltedTraverserStrategy.class, 139));
add(GryoTypeReg.of(PartitionStrategy.class, 140, new JavaSerializer()));
add(GryoTypeReg.of(SubgraphStrategy.class, 141, new JavaSerializer()));
+ add(GryoTypeReg.of(SeedStrategy.class, 192, new JavaSerializer())); // ***LAST ID***
add(GryoTypeReg.of(VertexProgramStrategy.class, 142, new JavaSerializer()));
add(GryoTypeReg.of(MatchAlgorithmStrategy.class, 143));
add(GryoTypeReg.of(MatchStep.GreedyMatchAlgorithm.class, 144));
add(GryoTypeReg.of(AdjacentToIncidentStrategy.class, 145));
- add(GryoTypeReg.of(ByModulatorOptimizationStrategy.class, 191)); // ***LAST ID***
+ add(GryoTypeReg.of(ByModulatorOptimizationStrategy.class, 191));
add(GryoTypeReg.of(CountStrategy.class, 155));
add(GryoTypeReg.of(FilterRankingStrategy.class, 146));
add(GryoTypeReg.of(IdentityRemovalStrategy.class, 147));
@@ -572,11 +574,12 @@ public enum GryoVersion {
add(GryoTypeReg.of(HaltedTraverserStrategy.class, 139));
add(GryoTypeReg.of(PartitionStrategy.class, 140, new JavaSerializer()));
add(GryoTypeReg.of(SubgraphStrategy.class, 141, new JavaSerializer()));
+ add(GryoTypeReg.of(SeedStrategy.class, 192, new JavaSerializer())); // ***LAST ID***
add(GryoTypeReg.of(VertexProgramStrategy.class, 142, new JavaSerializer()));
add(GryoTypeReg.of(MatchAlgorithmStrategy.class, 143));
add(GryoTypeReg.of(MatchStep.GreedyMatchAlgorithm.class, 144));
add(GryoTypeReg.of(AdjacentToIncidentStrategy.class, 145));
- add(GryoTypeReg.of(ByModulatorOptimizationStrategy.class, 191)); // ***LAST ID***
+ add(GryoTypeReg.of(ByModulatorOptimizationStrategy.class, 191));
add(GryoTypeReg.of(CountStrategy.class, 155));
add(GryoTypeReg.of(FilterRankingStrategy.class, 146));
add(GryoTypeReg.of(IdentityRemovalStrategy.class, 147));
diff --git a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/traverser/util/IndexedTraverserSetTest.java b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/traverser/util/IndexedTraverserSetTest.java
index 0d765f4..6f0b490 100644
--- a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/traverser/util/IndexedTraverserSetTest.java
+++ b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/traverser/util/IndexedTraverserSetTest.java
@@ -26,6 +26,7 @@ import org.junit.Test;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
+import java.util.Random;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.core.IsNull.nullValue;
@@ -111,7 +112,7 @@ public class IndexedTraverserSetTest {
assertEquals(1, nopeTraversers.size());
assertEquals(1, nopeTraversers.get(0).bulk());
- ts.shuffle();
+ ts.shuffle(new Random());
final List<Traverser.Admin<String>> testTraversersAfterShuffle = new ArrayList<>(ts.get("test"));
assertEquals(1, testTraversersAfterShuffle.size());
diff --git a/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/Strategy/Decoration/SeedStrategy.cs b/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/Strategy/Decoration/SeedStrategy.cs
new file mode 100644
index 0000000..bf531f5
--- /dev/null
+++ b/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/Strategy/Decoration/SeedStrategy.cs
@@ -0,0 +1,51 @@
+#region License
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#endregion
+
+using System.Collections.Generic;
+
+namespace Gremlin.Net.Process.Traversal.Strategy.Decoration
+{
+ /// <summary>
+ /// A strategy that resets the specified {@code seed} value for Seedable steps like coin(), sample()
+ /// and Order.shuffle, which in turn will produce deterministic results from those steps.
+ /// </summary>
+ public class SeedStrategy : AbstractTraversalStrategy
+ {
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="OptionsStrategy" /> class.
+ /// </summary>
+ public SeedStrategy()
+ {
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="SeedStrategy" /> class.
+ /// </summary>
+ /// <param name="seed">Specifies the seed the traversal will use.</param>
+ public SeedStrategy(long seed)
+ {
+ Configuration["seed"] = seed;
+ }
+ }
+}
\ No newline at end of file
diff --git a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Process/Traversal/DriverRemoteConnection/GraphTraversalTests.cs b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Process/Traversal/DriverRemoteConnection/GraphTraversalTests.cs
index 0f6bea6..5e11e33 100644
--- a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Process/Traversal/DriverRemoteConnection/GraphTraversalTests.cs
+++ b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Process/Traversal/DriverRemoteConnection/GraphTraversalTests.cs
@@ -220,6 +220,20 @@ namespace Gremlin.Net.IntegrationTest.Process.Traversal.DriverRemoteConnection
}
[Fact]
+ public void ShouldUseSeedStrategyToReturnDeterministicResults()
+ {
+ var connection = _connectionFactory.CreateRemoteConnection();
+ var g = AnonymousTraversalSource.Traversal().WithRemote(connection).WithStrategies(new SeedStrategy(664664));
+
+ var shuffledResults = g.V().Values<string>("name").Order().By(Order.Shuffle).ToList();
+ Assert.Equal(shuffledResults, g.V().Values<string>("name").Order().By(Order.Shuffle).ToList());
+ Assert.Equal(shuffledResults, g.V().Values<string>("name").Order().By(Order.Shuffle).ToList());
+ Assert.Equal(shuffledResults, g.V().Values<string>("name").Order().By(Order.Shuffle).ToList());
+ Assert.Equal(shuffledResults, g.V().Values<string>("name").Order().By(Order.Shuffle).ToList());
+ Assert.Equal(shuffledResults, g.V().Values<string>("name").Order().By(Order.Shuffle).ToList());
+ }
+
+ [Fact]
public async Task ShouldExecuteAsynchronouslyWhenPromiseIsCalled()
{
var connection = _connectionFactory.CreateRemoteConnection();
diff --git a/gremlin-python/src/main/python/gremlin_python/process/strategies.py b/gremlin-python/src/main/python/gremlin_python/process/strategies.py
index d483c05..c2ee615 100644
--- a/gremlin-python/src/main/python/gremlin_python/process/strategies.py
+++ b/gremlin-python/src/main/python/gremlin_python/process/strategies.py
@@ -63,6 +63,12 @@ class PartitionStrategy(TraversalStrategy):
self.configuration["includeMetaProperties"] = include_meta_properties
+class SeedStrategy(TraversalStrategy):
+ def __init__(self, seed):
+ TraversalStrategy.__init__(self, fqcn="org.apache.tinkerpop.gremlin.process.traversal.strategy.decoration.SeedStrategy")
+ self.configuration["seed"] = seed
+
+
class SubgraphStrategy(TraversalStrategy):
def __init__(self, vertices=None, edges=None, vertex_properties=None):
diff --git a/gremlin-python/src/main/python/tests/driver/test_driver_remote_connection.py b/gremlin-python/src/main/python/tests/driver/test_driver_remote_connection.py
index 1350b1d..663858e 100644
--- a/gremlin-python/src/main/python/tests/driver/test_driver_remote_connection.py
+++ b/gremlin-python/src/main/python/tests/driver/test_driver_remote_connection.py
@@ -28,11 +28,11 @@ from gremlin_python.driver.driver_remote_connection import (
from gremlin_python.process.traversal import Traverser
from gremlin_python.process.traversal import TraversalStrategy
from gremlin_python.process.traversal import Bindings
-from gremlin_python.process.traversal import P
+from gremlin_python.process.traversal import P, Order
from gremlin_python.process.graph_traversal import __
from gremlin_python.process.anonymous_traversal import traversal
from gremlin_python.structure.graph import Vertex
-from gremlin_python.process.strategies import SubgraphStrategy, ReservedKeysVerificationStrategy
+from gremlin_python.process.strategies import SubgraphStrategy, ReservedKeysVerificationStrategy, SeedStrategy
__author__ = 'Marko A. Rodriguez (http://markorodriguez.com)'
@@ -187,6 +187,12 @@ class TestDriverRemoteConnection(object):
assert 6 == g.V().count().next()
assert 6 == g.E().count().next()
#
+ g = traversal().withRemote(remote_connection).withStrategies(SeedStrategy(12345))
+ shuffledResult = g.V().values("name").order().by(Order.shuffle).toList()
+ assert shuffledResult == g.V().values("name").order().by(Order.shuffle).toList()
+ assert shuffledResult == g.V().values("name").order().by(Order.shuffle).toList()
+ assert shuffledResult == g.V().values("name").order().by(Order.shuffle).toList()
+ #
g = traversal().withRemote(remote_connection). \
withStrategies(ReservedKeysVerificationStrategy(throw_exception=True))
try:
diff --git a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/ProcessComputerSuite.java b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/ProcessComputerSuite.java
index b1ee6d2..8785e90 100644
--- a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/ProcessComputerSuite.java
+++ b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/ProcessComputerSuite.java
@@ -89,6 +89,7 @@ import org.apache.tinkerpop.gremlin.process.traversal.step.sideEffect.SideEffect
import org.apache.tinkerpop.gremlin.process.traversal.step.sideEffect.StoreTest;
import org.apache.tinkerpop.gremlin.process.traversal.step.sideEffect.SubgraphTest;
import org.apache.tinkerpop.gremlin.process.traversal.step.sideEffect.TreeTest;
+import org.apache.tinkerpop.gremlin.process.traversal.strategy.decoration.SeedStrategyProcessTest;
import org.apache.tinkerpop.gremlin.process.traversal.strategy.decoration.SubgraphStrategyProcessTest;
import org.apache.tinkerpop.gremlin.process.traversal.strategy.decoration.TranslationStrategyProcessTest;
import org.apache.tinkerpop.gremlin.process.traversal.strategy.optimization.EarlyLimitStrategyProcessTest;
@@ -206,6 +207,7 @@ public class ProcessComputerSuite extends AbstractGremlinSuite {
// decorations
ReadOnlyStrategyProcessTest.class,
+ SeedStrategyProcessTest.class,
SubgraphStrategyProcessTest.class,
// optimizations
diff --git a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/ProcessStandardSuite.java b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/ProcessStandardSuite.java
index f127ca0..2f31678 100644
--- a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/ProcessStandardSuite.java
+++ b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/ProcessStandardSuite.java
@@ -86,6 +86,7 @@ import org.apache.tinkerpop.gremlin.process.traversal.step.sideEffect.TreeTest;
import org.apache.tinkerpop.gremlin.process.traversal.strategy.decoration.ElementIdStrategyProcessTest;
import org.apache.tinkerpop.gremlin.process.traversal.strategy.decoration.EventStrategyProcessTest;
import org.apache.tinkerpop.gremlin.process.traversal.strategy.decoration.PartitionStrategyProcessTest;
+import org.apache.tinkerpop.gremlin.process.traversal.strategy.decoration.SeedStrategyProcessTest;
import org.apache.tinkerpop.gremlin.process.traversal.strategy.decoration.SubgraphStrategyProcessTest;
import org.apache.tinkerpop.gremlin.process.traversal.strategy.decoration.TranslationStrategyProcessTest;
import org.apache.tinkerpop.gremlin.process.traversal.strategy.optimization.EarlyLimitStrategyProcessTest;
@@ -193,6 +194,7 @@ public class ProcessStandardSuite extends AbstractGremlinSuite {
EventStrategyProcessTest.class,
ReadOnlyStrategyProcessTest.class,
PartitionStrategyProcessTest.class,
+ SeedStrategyProcessTest.class,
SubgraphStrategyProcessTest.class,
// optimizations
diff --git a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/strategy/decoration/SeedStrategyProcessTest.java b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/strategy/decoration/SeedStrategyProcessTest.java
new file mode 100644
index 0000000..77aaf0a
--- /dev/null
+++ b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/strategy/decoration/SeedStrategyProcessTest.java
@@ -0,0 +1,105 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.tinkerpop.gremlin.process.traversal.strategy.decoration;
+
+import org.apache.tinkerpop.gremlin.LoadGraphWith;
+import org.apache.tinkerpop.gremlin.process.AbstractGremlinProcessTest;
+import org.apache.tinkerpop.gremlin.process.GremlinProcessRunner;
+import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.List;
+import java.util.function.Supplier;
+
+import static org.apache.tinkerpop.gremlin.LoadGraphWith.GraphData.GRATEFUL;
+import static org.apache.tinkerpop.gremlin.LoadGraphWith.GraphData.MODERN;
+import static org.apache.tinkerpop.gremlin.process.traversal.Order.shuffle;
+import static org.apache.tinkerpop.gremlin.process.traversal.Scope.local;
+import static org.junit.Assert.assertEquals;
+
+@RunWith(GremlinProcessRunner.class)
+public class SeedStrategyProcessTest extends AbstractGremlinProcessTest {
+ private static final SeedStrategy seedStrategy = new SeedStrategy(1235L);
+
+ @Test
+ @LoadGraphWith(MODERN)
+ public void shouldSeedCoin() {
+ final GraphTraversalSource gSeeded = create();
+ final List<Object> names = gSeeded.V().order().by("name").values("name").coin(0.31).order().toList();
+ repeatAssert(() -> {
+ assertEquals(names, gSeeded.V().order().by("name").values("name").coin(0.31).order().toList());
+ return null;
+ });
+ }
+
+ @Test
+ @LoadGraphWith(MODERN)
+ public void shouldSeedGlobalOrderShuffle() {
+ final GraphTraversalSource gSeeded = create();
+ final List<Object> names = gSeeded.V().order().by("name").values("name").order().by(shuffle).toList();
+ repeatAssert(() -> {
+ assertEquals(names, gSeeded.V().order().by("name").values("name").order().by(shuffle).toList());
+ return null;
+ });
+ }
+
+ @Test
+ @LoadGraphWith(MODERN)
+ public void shouldSeedLocalOrderShuffle() {
+ final GraphTraversalSource gSeeded = create();
+ final List<Object> names = gSeeded.V().order().by("name").values("name").fold().order(local).by(shuffle).next();
+ repeatAssert(() -> {
+ assertEquals(names, gSeeded.V().order().by("name").values("name").fold().order(local).by(shuffle).next());
+ return null;
+ });
+ }
+
+ @Test
+ @LoadGraphWith(GRATEFUL)
+ public void shouldSeedGlobalSample() {
+ final GraphTraversalSource gSeeded = create();
+ final List<Object> names = gSeeded.V().order().by("name").values("name").sample(20).toList();
+ repeatAssert(() -> {
+ assertEquals(names, gSeeded.V().order().by("name").values("name").sample(20).toList());
+ return null;
+ });
+ }
+
+ @Test
+ @LoadGraphWith(GRATEFUL)
+ public void shouldSeedLocalSample() {
+ final GraphTraversalSource gSeeded = create();
+ final List<Object> names = gSeeded.V().order().by("name").values("name").fold().sample(local,20).next();
+ repeatAssert(() -> {
+ assertEquals(names, gSeeded.V().order().by("name").values("name").fold().sample(local,20).next());
+ return null;
+ });
+ }
+
+ private void repeatAssert(final Supplier<Void> assertion) {
+ for (int ix = 0; ix < 128; ix++) {
+ assertion.get();
+ }
+ }
+
+ private GraphTraversalSource create() {
+ return graphProvider.traversal(graph, seedStrategy);
+ }
+}