Merge branch 'pr-2666' into 3.7-dev
diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
new file mode 100644
index 0000000..f151584
--- /dev/null
+++ b/.github/pull_request_template.md
@@ -0,0 +1,14 @@
+<!--
+Thanks for contributing! Reminders:
++ TARGET the earliest branch where you want the change
+ 3.6-dev -> 3.6.8 (bugs only)
+ 3.7-dev -> 3.7.3 (non-breaking)
+ master -> 4.0.0
++ Committers will MERGE the PR forward to newer versions
++ ADD entry to the CHANGELOG.asciidoc for the targeted version
+ Do not reference a JIRA number there
++ ADD JIRA number to title and link in description
++ PRs requires 3 +1s from committers OR
+ 1 +1 and 7 day wait to merge.
++ MORE details: https://s.apache.org/rtnal
+-->
\ No newline at end of file
diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc
index 1930bdf..eada449 100644
--- a/CHANGELOG.asciidoc
+++ b/CHANGELOG.asciidoc
@@ -31,6 +31,7 @@
* Allow specifying a customized Spark app name
* CoinStep has a getter method for its probability field
* Fixed so that TrimGlobalStep and TrimLocalStep have the same character control handling as Ltrim and Rtrim
+* Fix a bug in MaxLocalStep, MinLocalStep, MeanLocalStep and SumLocalStep that it throws NoSuchElementException when encounters an empty iterator as input.
[[release-3-7-2]]
=== TinkerPop 3.7.2 (April 8, 2024)
diff --git a/docs/src/dev/developer/release.asciidoc b/docs/src/dev/developer/release.asciidoc
index 6a5e525..9812c79 100644
--- a/docs/src/dev/developer/release.asciidoc
+++ b/docs/src/dev/developer/release.asciidoc
@@ -324,6 +324,7 @@
.. `bin/process-docs.sh` and validate the generated `SNAPSHOT` documentation locally and then `bin/publish-docs.sh <username>`
.. Commit and push the `SNAPSHOT` changes to git
. Examine the `future.asciidoc` and update the "Roadmap" as needed.
+. Update the version numbers in `pull_request_template.md`.
. Send email to advise that code freeze is lifted.
. Consider the changes made to Gremlin and determine if the community needs to organize a PR to [DefinitelyTyped](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/gremlin) to keep TypeScript up to date.
. Ensure that the GLV examples compile and run with the latest image and dependencies: `bin/run-examples.sh`.
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MaxLocalStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MaxLocalStep.java
index e4ad8b4..93b95e4 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MaxLocalStep.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MaxLocalStep.java
@@ -21,11 +21,11 @@
import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
import org.apache.tinkerpop.gremlin.process.traversal.Traverser;
import org.apache.tinkerpop.gremlin.process.traversal.traverser.TraverserRequirement;
-import org.apache.tinkerpop.gremlin.process.traversal.util.FastNoSuchElementException;
import org.apache.tinkerpop.gremlin.util.iterator.IteratorUtils;
import java.util.Collections;
import java.util.Iterator;
+import java.util.NoSuchElementException;
import java.util.Set;
import static org.apache.tinkerpop.gremlin.util.NumberHelper.max;
@@ -34,23 +34,26 @@
* @author Marko A. Rodriguez (http://markorodriguez.com)
* @author Daniel Kuppitz (http://gremlin.guru)
*/
-public final class MaxLocalStep<E extends Comparable, S extends Iterable<E>> extends ScalarMapStep<S, E> {
+public final class MaxLocalStep<E extends Comparable, S extends Iterable<E>> extends MapStep<S, E> {
public MaxLocalStep(final Traversal.Admin traversal) {
super(traversal);
}
@Override
- protected E map(final Traverser.Admin<S> traverser) {
- final Iterator<E> iterator = IteratorUtils.asIterator(traverser.get());
- if (iterator.hasNext()) {
- Comparable result = iterator.next();
- while (iterator.hasNext()) {
- result = max(iterator.next(), result);
+ protected Traverser.Admin<E> processNextStart() throws NoSuchElementException {
+ while (true) {
+ final Traverser.Admin<S> traverser = this.starts.next();
+ final Iterator<E> iterator = IteratorUtils.asIterator(traverser.get());
+
+ if (iterator.hasNext()) {
+ Comparable result = iterator.next();
+ while (iterator.hasNext()) {
+ result = max(iterator.next(), result);
+ }
+ return traverser.split((E) result, this);
}
- return (E) result;
}
- throw FastNoSuchElementException.instance();
}
@Override
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MeanLocalStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MeanLocalStep.java
index 7a5abe7..d6e30e3 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MeanLocalStep.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MeanLocalStep.java
@@ -18,44 +18,49 @@
*/
package org.apache.tinkerpop.gremlin.process.traversal.step.map;
-import org.apache.tinkerpop.gremlin.util.NumberHelper;
import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
import org.apache.tinkerpop.gremlin.process.traversal.Traverser;
import org.apache.tinkerpop.gremlin.process.traversal.traverser.TraverserRequirement;
-import org.apache.tinkerpop.gremlin.process.traversal.util.FastNoSuchElementException;
import org.apache.tinkerpop.gremlin.util.iterator.IteratorUtils;
import java.util.Collections;
import java.util.Iterator;
+import java.util.NoSuchElementException;
import java.util.Set;
+import static org.apache.tinkerpop.gremlin.util.NumberHelper.add;
+import static org.apache.tinkerpop.gremlin.util.NumberHelper.div;
+
/**
* @author Marko A. Rodriguez (http://markorodriguez.com)
* @author Daniel Kuppitz (http://gremlin.guru)
*/
-public final class MeanLocalStep<E extends Number, S extends Iterable<E>> extends ScalarMapStep<S, Number> {
+public final class MeanLocalStep<E extends Number, S extends Iterable<E>> extends MapStep<S, E> {
public MeanLocalStep(final Traversal.Admin traversal) {
super(traversal);
}
@Override
- protected Number map(final Traverser.Admin<S> traverser) {
- final Iterator<E> iterator = IteratorUtils.asIterator(traverser.get());
- if (iterator.hasNext()) {
- // forward the iterator to the first non-null or return null
- E result = untilNonNull(iterator);
- Long counter = 1L;
- while (iterator.hasNext()) {
- final Number n = iterator.next();
- if (n != null) {
- result = (E) NumberHelper.add(result, n);
- counter++;
+ protected Traverser.Admin<E> processNextStart() throws NoSuchElementException {
+ while (true) {
+ final Traverser.Admin<S> traverser = this.starts.next();
+ final Iterator<E> iterator = IteratorUtils.asIterator(traverser.get());
+
+ if (iterator.hasNext()) {
+ // forward the iterator to the first non-null or return null
+ E result = untilNonNull(iterator);
+ Long counter = 1L;
+ while (iterator.hasNext()) {
+ final Number n = iterator.next();
+ if (n != null) {
+ result = (E) add(result, n);
+ counter++;
+ }
}
+ return traverser.split((E) div(result, counter, true), this);
}
- return NumberHelper.div(result, counter, true);
}
- throw FastNoSuchElementException.instance();
}
private E untilNonNull(final Iterator<E> itty) {
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MinLocalStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MinLocalStep.java
index 0781d4a..cf29a73 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MinLocalStep.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MinLocalStep.java
@@ -21,11 +21,11 @@
import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
import org.apache.tinkerpop.gremlin.process.traversal.Traverser;
import org.apache.tinkerpop.gremlin.process.traversal.traverser.TraverserRequirement;
-import org.apache.tinkerpop.gremlin.process.traversal.util.FastNoSuchElementException;
import org.apache.tinkerpop.gremlin.util.iterator.IteratorUtils;
import java.util.Collections;
import java.util.Iterator;
+import java.util.NoSuchElementException;
import java.util.Set;
import static org.apache.tinkerpop.gremlin.util.NumberHelper.min;
@@ -34,23 +34,26 @@
* @author Marko A. Rodriguez (http://markorodriguez.com)
* @author Daniel Kuppitz (http://gremlin.guru)
*/
-public final class MinLocalStep<E extends Comparable, S extends Iterable<E>> extends ScalarMapStep<S, E> {
+public final class MinLocalStep<E extends Comparable, S extends Iterable<E>> extends MapStep<S, E> {
public MinLocalStep(final Traversal.Admin traversal) {
super(traversal);
}
@Override
- protected E map(final Traverser.Admin<S> traverser) {
- final Iterator<E> iterator = IteratorUtils.asIterator(traverser.get());
- if (iterator.hasNext()) {
- Comparable result = iterator.next();
- while (iterator.hasNext()) {
- result = min(iterator.next(), result);
+ protected Traverser.Admin<E> processNextStart() throws NoSuchElementException {
+ while (true) {
+ final Traverser.Admin<S> traverser = this.starts.next();
+ final Iterator<E> iterator = IteratorUtils.asIterator(traverser.get());
+
+ if (iterator.hasNext()) {
+ Comparable result = iterator.next();
+ while (iterator.hasNext()) {
+ result = min(iterator.next(), result);
+ }
+ return traverser.split((E) result, this);
}
- return (E) result;
}
- throw FastNoSuchElementException.instance();
}
@Override
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/SumLocalStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/SumLocalStep.java
index 92b9796..69fd944 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/SumLocalStep.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/SumLocalStep.java
@@ -18,39 +18,47 @@
*/
package org.apache.tinkerpop.gremlin.process.traversal.step.map;
-import org.apache.tinkerpop.gremlin.util.NumberHelper;
import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
import org.apache.tinkerpop.gremlin.process.traversal.Traverser;
import org.apache.tinkerpop.gremlin.process.traversal.traverser.TraverserRequirement;
-import org.apache.tinkerpop.gremlin.process.traversal.util.FastNoSuchElementException;
import org.apache.tinkerpop.gremlin.util.iterator.IteratorUtils;
import java.util.Collections;
import java.util.Iterator;
+import java.util.NoSuchElementException;
import java.util.Set;
+import static org.apache.tinkerpop.gremlin.util.NumberHelper.add;
+
/**
* @author Marko A. Rodriguez (http://markorodriguez.com)
* @author Daniel Kuppitz (http://gremlin.guru)
*/
-public final class SumLocalStep<E extends Number, S extends Iterable<E>> extends ScalarMapStep<S, E> {
+public final class SumLocalStep<E extends Number, S extends Iterable<E>> extends MapStep<S, E> {
public SumLocalStep(final Traversal.Admin traversal) {
super(traversal);
}
@Override
- protected E map(final Traverser.Admin<S> traverser) {
- final Iterator<E> iterator = IteratorUtils.asIterator(traverser.get());
- if (iterator.hasNext()) {
- // forward the iterator to the first non-null or return null
- Number result = untilNonNull(iterator);
- while (iterator.hasNext()) {
- result = NumberHelper.add(result, iterator.next());
+ protected Traverser.Admin<E> processNextStart() throws NoSuchElementException {
+ while (true) {
+ final Traverser.Admin<S> traverser = this.starts.next();
+ final Iterator<E> iterator = IteratorUtils.asIterator(traverser.get());
+
+ if (iterator.hasNext()) {
+ // forward the iterator to the first non-null or return null
+ E result = untilNonNull(iterator);
+ while (iterator.hasNext()) {
+ final Number n = iterator.next();
+ if (n != null) {
+ result = (E) add(result, n);
+ }
+ }
+ return traverser.split(result, this);
}
- return (E) result;
+
}
- throw FastNoSuchElementException.instance();
}
private E untilNonNull(final Iterator<E> itty) {
diff --git a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs
index eba8217..4be4287 100644
--- a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs
+++ b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs
@@ -835,6 +835,7 @@
{"g_V_repeatXbothX_timesX5X_age_max", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Repeat(__.Both()).Times(5).Values<object>("age").Max<object>()}},
{"g_V_hasLabelXsoftwareX_group_byXnameX_byXbothE_weight_maxX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().HasLabel("software").Group<object,object>().By("name").By(__.BothE().Values<object>("weight").Max<object>())}},
{"g_VX1X_valuesXageX_maxXlocalX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V(p["vid1"]).Values<object>("age").Max<object>(Scope.Local)}},
+ {"g_V_localXunionXvaluesXageX_outE_valuesXweightXX_foldX_maxXlocalX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Local<object>(__.Union<object>(__.Values<object>("age"),__.OutE().Values<object>("weight")).Fold()).Max<object>(Scope.Local)}},
{"g_V_age_mean", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Values<object>("age").Mean<object>()}},
{"g_V_foo_mean", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Values<object>("foo").Mean<object>()}},
{"g_V_age_fold_meanXlocalX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Values<object>("age").Fold().Mean<object>(Scope.Local)}},
@@ -851,6 +852,7 @@
{"g_injectXnull_10_20_nullX_mean", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject<object>(null,p["xx1"],p["xx2"],null).Mean<object>()}},
{"g_injectXlistXnull_10_20_nullXX_meanXlocalX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject(p["xx1"]).Mean<object>(Scope.Local)}},
{"g_VX1X_valuesXageX_meanXlocalX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V(p["vid1"]).Values<object>("age").Mean<object>(Scope.Local)}},
+ {"g_V_localXunionXvaluesXageX_outE_valuesXweightXX_foldX_meanXlocalX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Local<object>(__.Union<object>(__.Values<object>("age"),__.OutE().Values<object>("weight")).Fold()).Mean<object>(Scope.Local)}},
{"g_injectXnullX_mergeXinjectX1XX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject<object>(null).Merge(__.Inject(1))}},
{"g_V_valuesXnameX_mergeXV_foldX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Values<object>("name").Merge(__.V().Fold())}},
{"g_V_fold_mergeXconstantXnullXX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Fold().Merge(__.Constant<object>(null))}},
@@ -996,6 +998,7 @@
{"g_V_hasLabelXsoftwareX_group_byXnameX_byXbothE_weight_minX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().HasLabel("software").Group<object,object>().By("name").By(__.BothE().Values<object>("weight").Min<object>())}},
{"g_V_foo_injectX9999999999X_min", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Values<object>("foo").Inject(p["xx1"]).Min<object>()}},
{"g_VX1X_valuesXageX_minXlocalX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V(p["vid1"]).Values<object>("age").Min<object>(Scope.Local)}},
+ {"g_V_localXunionXvaluesXageX_outE_valuesXweightXX_foldX_minXlocalX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Local<object>(__.Union<object>(__.Values<object>("age"),__.OutE().Values<object>("weight")).Fold()).Min<object>(Scope.Local)}},
{"g_V_name_order", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Values<object>("name").Order()}},
{"g_V_name_order_byXa1_b1X_byXb2_a2X", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Values<object>("name").Order().By((IComparator) p["c1"]).By((IComparator) p["c2"])}},
{"g_V_order_byXname_ascX_name", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Order().By("name",Order.Asc).Values<object>("name")}},
@@ -1214,6 +1217,7 @@
{"g_injectXnull_10_5_nullX_sum", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject<object>(null,p["xx1"],p["xx2"],null).Sum<object>()}},
{"g_injectXlistXnull_10_5_nullXX_sumXlocalX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject(p["xx1"]).Sum<object>(Scope.Local)}},
{"g_VX1X_valuesXageX_sumXlocalX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V(p["vid1"]).Values<object>("age").Sum<object>(Scope.Local)}},
+ {"g_V_localXunionXvaluesXageX_outE_valuesXweightXX_foldX_sumXlocalX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Local<object>(__.Union<object>(__.Values<object>("age"),__.OutE().Values<object>("weight")).Fold()).Sum<object>(Scope.Local)}},
{"g_injectXfeature_test_nullX_toLower", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject<object>("FEATURE","tESt",null).ToLower()}},
{"g_injectXfeature_test_nullX_toLowerXlocalX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject(p["xx1"]).ToLower<object>(Scope.Local)}},
{"g_injectXListXa_bXX_toLower", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject(p["xx1"]).ToLower()}},
diff --git a/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/util/ProfilingApplication.java b/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/util/ProfilingApplication.java
index 4259e25..898f3b1 100644
--- a/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/util/ProfilingApplication.java
+++ b/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/util/ProfilingApplication.java
@@ -30,7 +30,9 @@
import java.io.File;
import java.io.FileWriter;
import java.io.PrintWriter;
+import java.util.Iterator;
import java.util.Map;
+import java.util.NoSuchElementException;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
@@ -48,7 +50,7 @@
*/
public class ProfilingApplication {
- private static final Random random = new Random();
+ private static final Random random = new Random(0); // Same seed to ensure consistent test runs.
private static final String[] scripts = new String[]{
"g.V()",
"g.V(1).out('knows')",
@@ -87,7 +89,7 @@
this.exercise = exercise;
}
- public long execute() throws Exception {
+ public long executeThroughput() throws Exception {
final AtomicInteger tooSlow = new AtomicInteger(0);
final Client client = cluster.connect();
@@ -130,16 +132,51 @@
}
}
+ public double executeLatency() throws Exception {
+ final Client client = cluster.connect();
+ final String executionId = "[" + executionName + "]";
+ try {
+ client.init();
+
+ final long start = System.nanoTime();
+ int size = 0;
+ final Iterator itr = client.submitAsync(script).get().iterator();
+ try {
+ while (true) {
+ itr.next();
+ size++;
+ }
+ } catch (NoSuchElementException nsee) {
+ ; // Expected as hasNext() not called to increase performance.
+ }
+ final long end = System.nanoTime();
+ final long total = (end - start);
+
+
+ final double totalSeconds = total / 1000000000d;
+ System.out.println(String.format(StringUtils.rightPad(executionId, 10) + "time: %s, result count: %s", StringUtils.rightPad(String.valueOf(totalSeconds), 7), StringUtils.rightPad(String.valueOf(size), 10)));
+ return totalSeconds;
+ } catch (Exception ex) {
+ ex.printStackTrace();
+ throw new RuntimeException(ex);
+ } finally {
+ client.close();
+ }
+ }
+
private String chooseScript() {
return scripts[random.nextInt(scripts.length - 1)];
}
+ public enum TestType { LATENCY, THROUGHPUT };
+
public static void main(final String[] args) {
final Map<String,Object> options = ElementHelper.asMap(args);
final boolean noExit = Boolean.parseBoolean(options.getOrDefault("noExit", "false").toString());
final int parallelism = Integer.parseInt(options.getOrDefault("parallelism", "16").toString());
final BasicThreadFactory threadFactory = new BasicThreadFactory.Builder().namingPattern("profiler-%d").build();
final ExecutorService executor = Executors.newFixedThreadPool(parallelism, threadFactory);
+ final TestType testType = TestType.values()[(Integer.parseInt(options.getOrDefault("testType", "1").toString()) % TestType.values().length)];
final String host = options.getOrDefault("host", "localhost").toString();
final int minExpectedRps = Integer.parseInt(options.getOrDefault("minExpectedRps", "1000").toString());
@@ -155,7 +192,7 @@
final int maxInProcessPerConnection = Integer.parseInt(options.getOrDefault("maxInProcessPerConnection", "64").toString());
final int minInProcessPerConnection = Integer.parseInt(options.getOrDefault("minInProcessPerConnection", "16").toString());
final int maxWaitForConnection = Integer.parseInt(options.getOrDefault("maxWaitForConnection", "3000").toString());
- final int workerPoolSize = Integer.parseInt(options.getOrDefault("workerPoolSize", "2").toString());
+ final int workerPoolSize = Integer.parseInt(options.getOrDefault("workerPoolSize", Runtime.getRuntime().availableProcessors() * 2).toString());
final int tooSlowThreshold = Integer.parseInt(options.getOrDefault("tooSlowThreshold", "125").toString());
final String channelizer = options.getOrDefault("channelizer", Channelizer.WebSocketChannelizer.class.getName()).toString();
final String serializer = options.getOrDefault("serializer", Serializers.GRAPHBINARY_V1.name()).toString();
@@ -177,6 +214,12 @@
.workerPoolSize(workerPoolSize).create();
try {
+ if (TestType.LATENCY == testType) {
+ System.out.println("-----------------------LATENCY TEST SELECTED----------------------");
+ } else {
+ System.out.println("---------------------THROUGHPUT TEST SELECTED---------------------");
+ }
+
if (exercise) {
System.out.println("--------------------------INITIALIZATION--------------------------");
final Client client = cluster.connect();
@@ -189,43 +232,72 @@
System.out.println("Modern graph loaded");
}
- final Object fileName = options.get("store");
- final File f = null == fileName ? null : new File(fileName.toString());
- if (f != null && f.length() == 0) {
- try (final PrintWriter writer = new PrintWriter(new BufferedWriter(new FileWriter(f, true)))) {
- writer.println("parallelism\tnioPoolSize\tminConnectionPoolSize\tmaxConnectionPoolSize\tminSimultaneousUsagePerConnection\tmaxSimultaneousUsagePerConnection\tminInProcessPerConnection\tmaxInProcessPerConnection\tworkerPoolSize\trequestPerSecond");
+ if (TestType.THROUGHPUT == testType) {
+ final Object fileName = options.get("store");
+ final File f = null == fileName ? null : new File(fileName.toString());
+ if (f != null && f.length() == 0) {
+ try (final PrintWriter writer = new PrintWriter(new BufferedWriter(new FileWriter(f, true)))) {
+ writer.println("parallelism\tnioPoolSize\tminConnectionPoolSize\tmaxConnectionPoolSize\tminSimultaneousUsagePerConnection\tmaxSimultaneousUsagePerConnection\tminInProcessPerConnection\tmaxInProcessPerConnection\tworkerPoolSize\trequestPerSecond");
+ }
}
- }
- // not much point to continuing with a line of tests if we can't get at least minExpectedRps.
- final AtomicBoolean meetsRpsExpectation = new AtomicBoolean(true);
- System.out.println("---------------------------WARMUP CYCLE---------------------------");
- for (int ix = 0; ix < warmups && meetsRpsExpectation.get(); ix++) {
- final long averageRequestsPerSecond = new ProfilingApplication("warmup-" + (ix + 1), cluster, 1000, executor, script, tooSlowThreshold, exercise).execute();
- meetsRpsExpectation.set(averageRequestsPerSecond > minExpectedRps);
- TimeUnit.SECONDS.sleep(1); // pause between executions
- }
-
- final AtomicBoolean exceededTimeout = new AtomicBoolean(false);
- long totalRequestsPerSecond = 0;
-
- // no need to execute this if we didn't pass the basic expectation in the warmups
- if (exercise || meetsRpsExpectation.get()) {
- final long start = System.nanoTime();
- System.out.println("----------------------------TEST CYCLE----------------------------");
- for (int ix = 0; ix < executions && !exceededTimeout.get(); ix++) {
- totalRequestsPerSecond += new ProfilingApplication("test-" + (ix + 1), cluster, requests, executor, script, tooSlowThreshold, exercise).execute();
- exceededTimeout.set((System.nanoTime() - start) > TimeUnit.NANOSECONDS.convert(timeout, TimeUnit.MILLISECONDS));
+ // not much point to continuing with a line of tests if we can't get at least minExpectedRps.
+ final AtomicBoolean meetsRpsExpectation = new AtomicBoolean(true);
+ System.out.println("---------------------------WARMUP CYCLE---------------------------");
+ for (int ix = 0; ix < warmups && meetsRpsExpectation.get(); ix++) {
+ final long averageRequestsPerSecond = new ProfilingApplication("warmup-" + (ix + 1), cluster, 1000, executor, script, tooSlowThreshold, exercise).executeThroughput();
+ meetsRpsExpectation.set(averageRequestsPerSecond > minExpectedRps);
TimeUnit.SECONDS.sleep(1); // pause between executions
}
- }
- final int averageRequestPerSecond = !meetsRpsExpectation.get() || exceededTimeout.get() ? 0 : Math.round(totalRequestsPerSecond / executions);
- System.out.println(String.format("avg req/sec: %s", averageRequestPerSecond));
- if (f != null) {
- try (final PrintWriter writer = new PrintWriter(new BufferedWriter(new FileWriter(f, true)))) {
- writer.println(String.join("\t", String.valueOf(parallelism), String.valueOf(nioPoolSize), String.valueOf(minConnectionPoolSize), String.valueOf(maxConnectionPoolSize), String.valueOf(minSimultaneousUsagePerConnection), String.valueOf(maxSimultaneousUsagePerConnection), String.valueOf(minInProcessPerConnection), String.valueOf(maxInProcessPerConnection), String.valueOf(workerPoolSize), String.valueOf(averageRequestPerSecond)));
+ final AtomicBoolean exceededTimeout = new AtomicBoolean(false);
+ long totalRequestsPerSecond = 0;
+
+ // no need to execute this if we didn't pass the basic expectation in the warmups
+ if (exercise || meetsRpsExpectation.get()) {
+ final long start = System.nanoTime();
+ System.out.println("----------------------------TEST CYCLE----------------------------");
+ for (int ix = 0; ix < executions && !exceededTimeout.get(); ix++) {
+ totalRequestsPerSecond += new ProfilingApplication("test-" + (ix + 1), cluster, requests, executor, script, tooSlowThreshold, exercise).executeThroughput();
+ exceededTimeout.set((System.nanoTime() - start) > TimeUnit.NANOSECONDS.convert(timeout, TimeUnit.MILLISECONDS));
+ TimeUnit.SECONDS.sleep(1); // pause between executions
+ }
}
+
+ final int averageRequestPerSecond = !meetsRpsExpectation.get() || exceededTimeout.get() ? 0 : Math.round(totalRequestsPerSecond / executions);
+ System.out.println(String.format("avg req/sec: %s", averageRequestPerSecond));
+ if (f != null) {
+ try (final PrintWriter writer = new PrintWriter(new BufferedWriter(new FileWriter(f, true)))) {
+ writer.println(String.join("\t", String.valueOf(parallelism), String.valueOf(nioPoolSize), String.valueOf(minConnectionPoolSize), String.valueOf(maxConnectionPoolSize), String.valueOf(minSimultaneousUsagePerConnection), String.valueOf(maxSimultaneousUsagePerConnection), String.valueOf(minInProcessPerConnection), String.valueOf(maxInProcessPerConnection), String.valueOf(workerPoolSize), String.valueOf(averageRequestPerSecond)));
+ }
+ }
+ } else if (TestType.LATENCY == testType) {
+ final AtomicBoolean meetsTimeoutExpectation = new AtomicBoolean(true);
+ System.out.println("---------------------------WARMUP CYCLE---------------------------");
+ for (int ix = 0; ix < warmups && meetsTimeoutExpectation.get(); ix++) {
+ final double latency = new ProfilingApplication("warmup-" + (ix + 1), cluster, 1000, executor, script, tooSlowThreshold, exercise).executeLatency();
+ meetsTimeoutExpectation.set(latency < timeout);
+ TimeUnit.SECONDS.sleep(1); // pause between executions
+ }
+
+ final AtomicBoolean exceededTimeout = new AtomicBoolean(false);
+ double totalTime = 0;
+
+ // no need to execute this if we didn't pass the basic expectation in the warmups
+ if (exercise || meetsTimeoutExpectation.get()) {
+ final long start = System.nanoTime();
+ System.out.println("----------------------------TEST CYCLE----------------------------");
+ for (int ix = 0; ix < executions && !exceededTimeout.get(); ix++) {
+ totalTime += new ProfilingApplication("test-" + (ix + 1), cluster, requests, executor, script, tooSlowThreshold, exercise).executeLatency();
+ exceededTimeout.set((System.nanoTime() - start) > TimeUnit.NANOSECONDS.convert(timeout, TimeUnit.MILLISECONDS));
+ TimeUnit.SECONDS.sleep(1); // pause between executions
+ }
+ }
+
+ final double averageLatency = !meetsTimeoutExpectation.get() || exceededTimeout.get() ? 0 : (totalTime / executions);
+ System.out.println(String.format("avg latency (sec/req): %s", averageLatency));
+ } else {
+ System.out.println("Encountered unknown testType. Please enter a valid value and try again.");
}
if (!noExit) System.exit(0);
diff --git a/gremlin-go/driver/cucumber/gremlin.go b/gremlin-go/driver/cucumber/gremlin.go
index 0922e72..0bd7ba6 100644
--- a/gremlin-go/driver/cucumber/gremlin.go
+++ b/gremlin-go/driver/cucumber/gremlin.go
@@ -806,6 +806,7 @@
"g_V_repeatXbothX_timesX5X_age_max": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Repeat(gremlingo.T__.Both()).Times(5).Values("age").Max()}},
"g_V_hasLabelXsoftwareX_group_byXnameX_byXbothE_weight_maxX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().HasLabel("software").Group().By("name").By(gremlingo.T__.BothE().Values("weight").Max())}},
"g_VX1X_valuesXageX_maxXlocalX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V(p["vid1"]).Values("age").Max(gremlingo.Scope.Local)}},
+ "g_V_localXunionXvaluesXageX_outE_valuesXweightXX_foldX_maxXlocalX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Local(gremlingo.T__.Union(gremlingo.T__.Values("age"), gremlingo.T__.OutE().Values("weight")).Fold()).Max(gremlingo.Scope.Local)}},
"g_V_age_mean": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Values("age").Mean()}},
"g_V_foo_mean": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Values("foo").Mean()}},
"g_V_age_fold_meanXlocalX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Values("age").Fold().Mean(gremlingo.Scope.Local)}},
@@ -822,6 +823,7 @@
"g_injectXnull_10_20_nullX_mean": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(nil, p["xx1"], p["xx2"], nil).Mean()}},
"g_injectXlistXnull_10_20_nullXX_meanXlocalX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(p["xx1"]).Mean(gremlingo.Scope.Local)}},
"g_VX1X_valuesXageX_meanXlocalX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V(p["vid1"]).Values("age").Mean(gremlingo.Scope.Local)}},
+ "g_V_localXunionXvaluesXageX_outE_valuesXweightXX_foldX_meanXlocalX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Local(gremlingo.T__.Union(gremlingo.T__.Values("age"), gremlingo.T__.OutE().Values("weight")).Fold()).Mean(gremlingo.Scope.Local)}},
"g_injectXnullX_mergeXinjectX1XX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(nil).Merge(gremlingo.T__.Inject(1))}},
"g_V_valuesXnameX_mergeXV_foldX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Values("name").Merge(gremlingo.T__.V().Fold())}},
"g_V_fold_mergeXconstantXnullXX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Fold().Merge(gremlingo.T__.Constant(nil))}},
@@ -967,6 +969,7 @@
"g_V_hasLabelXsoftwareX_group_byXnameX_byXbothE_weight_minX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().HasLabel("software").Group().By("name").By(gremlingo.T__.BothE().Values("weight").Min())}},
"g_V_foo_injectX9999999999X_min": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Values("foo").Inject(p["xx1"]).Min()}},
"g_VX1X_valuesXageX_minXlocalX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V(p["vid1"]).Values("age").Min(gremlingo.Scope.Local)}},
+ "g_V_localXunionXvaluesXageX_outE_valuesXweightXX_foldX_minXlocalX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Local(gremlingo.T__.Union(gremlingo.T__.Values("age"), gremlingo.T__.OutE().Values("weight")).Fold()).Min(gremlingo.Scope.Local)}},
"g_V_name_order": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Values("name").Order()}},
"g_V_name_order_byXa1_b1X_byXb2_a2X": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Values("name").Order().By(p["c1"]).By(p["c2"])}},
"g_V_order_byXname_ascX_name": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Order().By("name", gremlingo.Order.Asc).Values("name")}},
@@ -1185,6 +1188,7 @@
"g_injectXnull_10_5_nullX_sum": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(nil, p["xx1"], p["xx2"], nil).Sum()}},
"g_injectXlistXnull_10_5_nullXX_sumXlocalX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(p["xx1"]).Sum(gremlingo.Scope.Local)}},
"g_VX1X_valuesXageX_sumXlocalX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V(p["vid1"]).Values("age").Sum(gremlingo.Scope.Local)}},
+ "g_V_localXunionXvaluesXageX_outE_valuesXweightXX_foldX_sumXlocalX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Local(gremlingo.T__.Union(gremlingo.T__.Values("age"), gremlingo.T__.OutE().Values("weight")).Fold()).Sum(gremlingo.Scope.Local)}},
"g_injectXfeature_test_nullX_toLower": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject("FEATURE", "tESt", nil).ToLower()}},
"g_injectXfeature_test_nullX_toLowerXlocalX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(p["xx1"]).ToLower(gremlingo.Scope.Local)}},
"g_injectXListXa_bXX_toLower": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(p["xx1"]).ToLower()}},
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 8eaca3b..c666394 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
@@ -826,6 +826,7 @@
g_V_repeatXbothX_timesX5X_age_max: [function({g}) { return g.V().repeat(__.both()).times(5).values("age").max() }],
g_V_hasLabelXsoftwareX_group_byXnameX_byXbothE_weight_maxX: [function({g}) { return g.V().hasLabel("software").group().by("name").by(__.bothE().values("weight").max()) }],
g_VX1X_valuesXageX_maxXlocalX: [function({g, vid1}) { return g.V(vid1).values("age").max(Scope.local) }],
+ g_V_localXunionXvaluesXageX_outE_valuesXweightXX_foldX_maxXlocalX: [function({g}) { return g.V().local(__.union(__.values("age"),__.outE().values("weight")).fold()).max(Scope.local) }],
g_V_age_mean: [function({g}) { return g.V().values("age").mean() }],
g_V_foo_mean: [function({g}) { return g.V().values("foo").mean() }],
g_V_age_fold_meanXlocalX: [function({g}) { return g.V().values("age").fold().mean(Scope.local) }],
@@ -842,6 +843,7 @@
g_injectXnull_10_20_nullX_mean: [function({g, xx1, xx2}) { return g.inject(null,xx1,xx2,null).mean() }],
g_injectXlistXnull_10_20_nullXX_meanXlocalX: [function({g, xx1}) { return g.inject(xx1).mean(Scope.local) }],
g_VX1X_valuesXageX_meanXlocalX: [function({g, vid1}) { return g.V(vid1).values("age").mean(Scope.local) }],
+ g_V_localXunionXvaluesXageX_outE_valuesXweightXX_foldX_meanXlocalX: [function({g}) { return g.V().local(__.union(__.values("age"),__.outE().values("weight")).fold()).mean(Scope.local) }],
g_injectXnullX_mergeXinjectX1XX: [function({g}) { return g.inject(null).merge(__.inject(1)) }],
g_V_valuesXnameX_mergeXV_foldX: [function({g}) { return g.V().values("name").merge(__.V().fold()) }],
g_V_fold_mergeXconstantXnullXX: [function({g}) { return g.V().fold().merge(__.constant(null)) }],
@@ -987,6 +989,7 @@
g_V_hasLabelXsoftwareX_group_byXnameX_byXbothE_weight_minX: [function({g}) { return g.V().hasLabel("software").group().by("name").by(__.bothE().values("weight").min()) }],
g_V_foo_injectX9999999999X_min: [function({g, xx1}) { return g.V().values("foo").inject(xx1).min() }],
g_VX1X_valuesXageX_minXlocalX: [function({g, vid1}) { return g.V(vid1).values("age").min(Scope.local) }],
+ g_V_localXunionXvaluesXageX_outE_valuesXweightXX_foldX_minXlocalX: [function({g}) { return g.V().local(__.union(__.values("age"),__.outE().values("weight")).fold()).min(Scope.local) }],
g_V_name_order: [function({g}) { return g.V().values("name").order() }],
g_V_name_order_byXa1_b1X_byXb2_a2X: [function({g, c1, c2}) { return g.V().values("name").order().by(c1).by(c2) }],
g_V_order_byXname_ascX_name: [function({g}) { return g.V().order().by("name",Order.asc).values("name") }],
@@ -1205,6 +1208,7 @@
g_injectXnull_10_5_nullX_sum: [function({g, xx1, xx2}) { return g.inject(null,xx1,xx2,null).sum() }],
g_injectXlistXnull_10_5_nullXX_sumXlocalX: [function({g, xx1}) { return g.inject(xx1).sum(Scope.local) }],
g_VX1X_valuesXageX_sumXlocalX: [function({g, vid1}) { return g.V(vid1).values("age").sum(Scope.local) }],
+ g_V_localXunionXvaluesXageX_outE_valuesXweightXX_foldX_sumXlocalX: [function({g}) { return g.V().local(__.union(__.values("age"),__.outE().values("weight")).fold()).sum(Scope.local) }],
g_injectXfeature_test_nullX_toLower: [function({g}) { return g.inject("FEATURE","tESt",null).toLower() }],
g_injectXfeature_test_nullX_toLowerXlocalX: [function({g, xx1}) { return g.inject(xx1).toLower(Scope.local) }],
g_injectXListXa_bXX_toLower: [function({g, xx1}) { return g.inject(xx1).toLower() }],
diff --git a/gremlin-python/src/main/python/radish/gremlin.py b/gremlin-python/src/main/python/radish/gremlin.py
index 9847e87..8696202 100644
--- a/gremlin-python/src/main/python/radish/gremlin.py
+++ b/gremlin-python/src/main/python/radish/gremlin.py
@@ -808,6 +808,7 @@
'g_V_repeatXbothX_timesX5X_age_max': [(lambda g:g.V().repeat(__.both()).times(5).age.max_())],
'g_V_hasLabelXsoftwareX_group_byXnameX_byXbothE_weight_maxX': [(lambda g:g.V().hasLabel('software').group().by('name').by(__.bothE().weight.max_()))],
'g_VX1X_valuesXageX_maxXlocalX': [(lambda g, vid1=None:g.V(vid1).age.max_(Scope.local))],
+ 'g_V_localXunionXvaluesXageX_outE_valuesXweightXX_foldX_maxXlocalX': [(lambda g:g.V().local(__.union(__.age,__.outE().weight).fold()).max_(Scope.local))],
'g_V_age_mean': [(lambda g:g.V().age.mean())],
'g_V_foo_mean': [(lambda g:g.V().foo.mean())],
'g_V_age_fold_meanXlocalX': [(lambda g:g.V().age.fold().mean(Scope.local))],
@@ -824,6 +825,7 @@
'g_injectXnull_10_20_nullX_mean': [(lambda g, xx1=None,xx2=None:g.inject(None,xx1,xx2,None).mean())],
'g_injectXlistXnull_10_20_nullXX_meanXlocalX': [(lambda g, xx1=None:g.inject(xx1).mean(Scope.local))],
'g_VX1X_valuesXageX_meanXlocalX': [(lambda g, vid1=None:g.V(vid1).age.mean(Scope.local))],
+ 'g_V_localXunionXvaluesXageX_outE_valuesXweightXX_foldX_meanXlocalX': [(lambda g:g.V().local(__.union(__.age,__.outE().weight).fold()).mean(Scope.local))],
'g_injectXnullX_mergeXinjectX1XX': [(lambda g:g.inject(None).merge(__.inject(1)))],
'g_V_valuesXnameX_mergeXV_foldX': [(lambda g:g.V().name.merge(__.V().fold()))],
'g_V_fold_mergeXconstantXnullXX': [(lambda g:g.V().fold().merge(__.constant(None)))],
@@ -969,6 +971,7 @@
'g_V_hasLabelXsoftwareX_group_byXnameX_byXbothE_weight_minX': [(lambda g:g.V().hasLabel('software').group().by('name').by(__.bothE().weight.min_()))],
'g_V_foo_injectX9999999999X_min': [(lambda g, xx1=None:g.V().foo.inject(xx1).min_())],
'g_VX1X_valuesXageX_minXlocalX': [(lambda g, vid1=None:g.V(vid1).age.min_(Scope.local))],
+ 'g_V_localXunionXvaluesXageX_outE_valuesXweightXX_foldX_minXlocalX': [(lambda g:g.V().local(__.union(__.age,__.outE().weight).fold()).min_(Scope.local))],
'g_V_name_order': [(lambda g:g.V().name.order())],
'g_V_name_order_byXa1_b1X_byXb2_a2X': [(lambda g, c1=None,c2=None:g.V().name.order().by(c1).by(c2))],
'g_V_order_byXname_ascX_name': [(lambda g:g.V().order().by('name',Order.asc).name)],
@@ -1187,6 +1190,7 @@
'g_injectXnull_10_5_nullX_sum': [(lambda g, xx1=None,xx2=None:g.inject(None,xx1,xx2,None).sum_())],
'g_injectXlistXnull_10_5_nullXX_sumXlocalX': [(lambda g, xx1=None:g.inject(xx1).sum_(Scope.local))],
'g_VX1X_valuesXageX_sumXlocalX': [(lambda g, vid1=None:g.V(vid1).age.sum_(Scope.local))],
+ 'g_V_localXunionXvaluesXageX_outE_valuesXweightXX_foldX_sumXlocalX': [(lambda g:g.V().local(__.union(__.age,__.outE().weight).fold()).sum_(Scope.local))],
'g_injectXfeature_test_nullX_toLower': [(lambda g:g.inject('FEATURE','tESt',None).to_lower())],
'g_injectXfeature_test_nullX_toLowerXlocalX': [(lambda g, xx1=None:g.inject(xx1).to_lower(Scope.local))],
'g_injectXListXa_bXX_toLower': [(lambda g, xx1=None:g.inject(xx1).to_lower())],
diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/Max.feature b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/Max.feature
index 1603370..75caa6a 100644
--- a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/Max.feature
+++ b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/Max.feature
@@ -201,3 +201,18 @@
Then the result should be unordered
| result |
| d[29].i |
+
+ # It verifies if empty lists are filtered out as expected
+ Scenario: g_V_localXunionXvaluesXageX_outE_valuesXweightXX_foldX_maxXlocalX
+ Given the modern graph
+ And the traversal of
+ """
+ g.V().local(union(values("age"), outE().values("weight")).fold()).max(local)
+ """
+ When iterated to list
+ Then the result should be unordered
+ | result |
+ | d[29].d |
+ | d[27].i |
+ | d[32].d |
+ | d[35].d |
diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/Mean.feature b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/Mean.feature
index 75fc132..fed1a0c 100644
--- a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/Mean.feature
+++ b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/Mean.feature
@@ -202,4 +202,19 @@
When iterated to list
Then the result should be unordered
| result |
- | d[29.0].d |
\ No newline at end of file
+ | d[29.0].d |
+
+ # It verifies if empty lists are filtered out as expected
+ Scenario: g_V_localXunionXvaluesXageX_outE_valuesXweightXX_foldX_meanXlocalX
+ Given the modern graph
+ And the traversal of
+ """
+ g.V().local(union(values("age"), outE().values("weight")).fold()).mean(local)
+ """
+ When iterated to list
+ Then the result should be unordered
+ | result |
+ | d[7.725].d |
+ | d[27.0].d |
+ | d[11.133333333333333].d |
+ | d[17.6].d |
\ No newline at end of file
diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/Min.feature b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/Min.feature
index 0b8c4af..cff82d8 100644
--- a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/Min.feature
+++ b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/Min.feature
@@ -214,3 +214,18 @@
Then the result should be unordered
| result |
| d[29].i |
+
+ # It verifies if empty lists are filtered out as expected
+ Scenario: g_V_localXunionXvaluesXageX_outE_valuesXweightXX_foldX_minXlocalX
+ Given the modern graph
+ And the traversal of
+ """
+ g.V().local(union(values("age"), outE().values("weight")).fold()).min(local)
+ """
+ When iterated to list
+ Then the result should be unordered
+ | result |
+ | d[0.4].d |
+ | d[27].i |
+ | d[0.4].d |
+ | d[0.2].d |
\ No newline at end of file
diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/Sum.feature b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/Sum.feature
index e0e7345..c5a475f 100644
--- a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/Sum.feature
+++ b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/Sum.feature
@@ -201,4 +201,19 @@
When iterated to list
Then the result should be unordered
| result |
- | d[29].i |
\ No newline at end of file
+ | d[29].i |
+
+ # It verifies if empty lists are filtered out as expected
+ Scenario: g_V_localXunionXvaluesXageX_outE_valuesXweightXX_foldX_sumXlocalX
+ Given the modern graph
+ And the traversal of
+ """
+ g.V().local(union(values("age"), outE().values("weight")).fold()).sum(local)
+ """
+ When iterated to list
+ Then the result should be unordered
+ | result |
+ | d[30.9].d |
+ | d[27].i |
+ | d[33.4].d |
+ | d[35.2].d |
\ No newline at end of file