You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tinkerpop.apache.org by co...@apache.org on 2025/10/28 01:24:05 UTC
(tinkerpop) branch 3.8-dev updated: [TINKERPOP-3207] Add Barriers to GroupSideEffect, GroupCountSideEffect, TreeSideEffect, and Subgraph Steps (#3254)
This is an automated email from the ASF dual-hosted git repository.
colegreer pushed a commit to branch 3.8-dev
in repository https://gitbox.apache.org/repos/asf/tinkerpop.git
The following commit(s) were added to refs/heads/3.8-dev by this push:
new c778125311 [TINKERPOP-3207] Add Barriers to GroupSideEffect, GroupCountSideEffect, TreeSideEffect, and Subgraph Steps (#3254)
c778125311 is described below
commit c778125311b1b1f8ddc5393344214d89dce2486d
Author: Cole Greer <11...@users.noreply.github.com>
AuthorDate: Mon Oct 27 18:24:00 2025 -0700
[TINKERPOP-3207] Add Barriers to GroupSideEffect, GroupCountSideEffect, TreeSideEffect, and Subgraph Steps (#3254)
Introduces SideEffectBarrierStep which adds a LocalBarrier to SideEffectStep. All of GroupSideEffect, GroupCountSideEffect, TreeSideEffect, and Subgraph steps are converted over to this new base class. This implementation on the existing design of AggregateGlobalStep relative to the non-blocking AggregateLocalStep.
---
CHANGELOG.asciidoc | 1 +
docs/src/dev/provider/gremlin-semantics.asciidoc | 3 +-
docs/src/reference/the-traversal.asciidoc | 9 +-
docs/src/upgrade/release-3.8.x.asciidoc | 43 ++++++++++
.../step/sideEffect/GroupCountSideEffectStep.java | 2 +-
.../step/sideEffect/GroupSideEffectStep.java | 2 +-
.../step/sideEffect/SideEffectBarrierStep.java | 99 ++++++++++++++++++++++
.../traversal/step/sideEffect/SubgraphStep.java | 2 +-
.../step/sideEffect/TreeSideEffectStep.java | 2 +-
.../Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs | 9 +-
gremlin-go/driver/cucumber/gremlin.go | 9 +-
.../test/cucumber/feature-steps.js | 3 +
.../gremlin-javascript/test/cucumber/gremlin.js | 9 +-
gremlin-python/src/main/python/radish/gremlin.py | 9 +-
.../gremlin/driver/remote/RemoteWorld.java | 5 ++
.../traversal/step/sideEffect/GroupCountTest.java | 11 ++-
.../gremlin/test/features/sideEffect/Group.feature | 32 +++++++
.../test/features/sideEffect/GroupCount.feature | 38 ++++++++-
.../test/features/sideEffect/Subgraph.feature | 17 ++++
.../gremlin/test/features/sideEffect/Tree.feature | 35 +++++++-
.../spark/SparkGraphFeatureIntegrateTest.java | 4 +
.../optimization/SparkInterceptorStrategyTest.java | 2 +-
.../tinkerpop/gremlin/tinkergraph/TinkerWorld.java | 5 ++
23 files changed, 326 insertions(+), 25 deletions(-)
diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc
index 2f5d1ddf14..fdfb4cb6ba 100644
--- a/CHANGELOG.asciidoc
+++ b/CHANGELOG.asciidoc
@@ -99,6 +99,7 @@ This release also includes changes from <<release-3-7-XXX, 3.7.XXX>>.
* Removed `P.getOriginalValue()` in favor of `P.getValue()`.
* Simplified comparability semantics from ternary boolean logic to binary logic.
* Introduced `NotP` class for negation of `P`.
+* Add barriers to `group(String)`, `groupCount(String)`, `tree(String)` and `subgraph(String)` steps.
* Increase minimum Java version from 1.8 to 11 for building and running.
* Moved all lambda oriented Gremlin tests to `LambdaStepTest` in the Java test suite.
* Removed the `@RemoteOnly` testing tag in Gherkin as lambda tests have all been moved to the Java test suite.
diff --git a/docs/src/dev/provider/gremlin-semantics.asciidoc b/docs/src/dev/provider/gremlin-semantics.asciidoc
index a760c85059..12bcf64107 100644
--- a/docs/src/dev/provider/gremlin-semantics.asciidoc
+++ b/docs/src/dev/provider/gremlin-semantics.asciidoc
@@ -1576,7 +1576,8 @@ key.
The `groupCount()` step can be used as both a map step and a side-effect step. As a map step, it returns a `Map` with
the counted objects as keys and their counts as values. As a side-effect step, it stores the counts in a side-effect and
-passes the traverser to the next step unchanged.
+passes the traverser to the next step unchanged. Note that both the map and side-effect forms of this step are barriers
+must fully iterate the traversal before returning any results.
*Exceptions*
diff --git a/docs/src/reference/the-traversal.asciidoc b/docs/src/reference/the-traversal.asciidoc
index 6b7f1ffe7e..d563c38b3d 100644
--- a/docs/src/reference/the-traversal.asciidoc
+++ b/docs/src/reference/the-traversal.asciidoc
@@ -5015,20 +5015,19 @@ g.V().has('name','josh').out('created').values('name').
<1> When the `tree()` is created, vertex 3 and 5 are unique and thus, form unique branches in the tree structure.
<2> When the `tree()` is `by()`-modulated by `label`, then vertex 3 and 5 are both "software" and thus are merged to a single node in the tree.
-The `tree()` step can also take a side-effect key as an argument. When using this form, the `Tree` is constructed
-lazily, such that it becomes possible to assess its contents as each traverser passes through.
+The `tree()` step can also take a side-effect key as an argument. When using this form, the `Tree` is is built up in a
+side-effect as each traverser passes through. The `Tree` can later be accessed by either `select()` or `cap()`.
[gremlin-groovy,modern]
----
g.V().has('name','josh').out('created').values('name').tree('x').select('x')
----
-You can use `cap()` step to force `tree()` to consume the traversal stream eagerly and output results similar to prior
-examples.
+It is possible to force lazy construction of the tree by embedding inside a `local()` step.
[gremlin-groovy,modern]
----
-g.V().has('name','josh').out('created').values('name').tree('x').cap('x')
+g.V().has('name','josh').out('created').values('name').local(tree('x')).select('x')
----
*Additional References*
diff --git a/docs/src/upgrade/release-3.8.x.asciidoc b/docs/src/upgrade/release-3.8.x.asciidoc
index fe3dc2f0bf..e5e321522e 100644
--- a/docs/src/upgrade/release-3.8.x.asciidoc
+++ b/docs/src/upgrade/release-3.8.x.asciidoc
@@ -885,6 +885,49 @@ g.with_strategies(OptionsStrategy(options=myOptions))
g.with_strategies(OptionsStrategy(**myOptions))
----
+==== Add barrier to most SideEffect steps
+
+Prior to 3.8.0, the `group(String)`, `groupCount(String)`, `tree(String)` and `subgraph(String)` steps were non-blocking,
+in that they allowed traversers to pass through without fully iterating the traversal and fully computing the side
+effect. Consider the following example:
+
+[source, groovy]
+----
+// 3.7.4
+gremlin> g.V().groupCount("x").select("x")
+==>[v[1]:1]
+==>[v[1]:1,v[2]:1]
+==>[v[1]:1,v[2]:1,v[3]:1]
+==>[v[1]:1,v[2]:1,v[3]:1,v[4]:1]
+==>[v[1]:1,v[2]:1,v[3]:1,v[4]:1,v[5]:1]
+==>[v[1]:1,v[2]:1,v[3]:1,v[4]:1,v[5]:1,v[6]:1]
+----
+
+As of 3.8.0, all of these steps now implement `LocalBarrier`, meaning that the traversal is fully iterated before any
+results are passed. This guarantees that a traversal will produce the same results regardless of it is evaluated in a
+lazy (DFS) or eager (BFS) fashion. Any usages which are reliant on the previous "one-at-a-time" accumulation of results
+can still achieve this by embedding the side effect step inside a `local()` step.
+
+[source, groovy]
+----
+// 3.8.0
+gremlin> g.V().groupCount("x").select("x")
+==>[v[1]:1,v[2]:1,v[3]:1,v[4]:1,v[5]:1,v[6]:1]
+==>[v[1]:1,v[2]:1,v[3]:1,v[4]:1,v[5]:1,v[6]:1]
+==>[v[1]:1,v[2]:1,v[3]:1,v[4]:1,v[5]:1,v[6]:1]
+==>[v[1]:1,v[2]:1,v[3]:1,v[4]:1,v[5]:1,v[6]:1]
+==>[v[1]:1,v[2]:1,v[3]:1,v[4]:1,v[5]:1,v[6]:1]
+==>[v[1]:1,v[2]:1,v[3]:1,v[4]:1,v[5]:1,v[6]:1]
+
+gremlin> g.V().local(groupCount("x")).select("x")
+==>[v[1]:1]
+==>[v[1]:1,v[2]:1]
+==>[v[1]:1,v[2]:1,v[3]:1]
+==>[v[1]:1,v[2]:1,v[3]:1,v[4]:1]
+==>[v[1]:1,v[2]:1,v[3]:1,v[4]:1,v[5]:1]
+==>[v[1]:1,v[2]:1,v[3]:1,v[4]:1,v[5]:1,v[6]:1]
+----
+
==== choose() Semantics
Several enhancements and clarifications have been made to the `choose()` step in TinkerPop 3.8.0 to improve its behavior
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/sideEffect/GroupCountSideEffectStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/sideEffect/GroupCountSideEffectStep.java
index 186153647d..7674b1dbe6 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/sideEffect/GroupCountSideEffectStep.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/sideEffect/GroupCountSideEffectStep.java
@@ -38,7 +38,7 @@ import java.util.Set;
/**
* @author Marko A. Rodriguez (http://markorodriguez.com)
*/
-public class GroupCountSideEffectStep<S, E> extends SideEffectStep<S> implements SideEffectCapable<Map<E, Long>, Map<E, Long>>, TraversalParent, ByModulating {
+public class GroupCountSideEffectStep<S, E> extends SideEffectBarrierStep<S> implements SideEffectCapable<Map<E, Long>, Map<E, Long>>, TraversalParent, ByModulating {
private Traversal.Admin<S, E> keyTraversal = null;
private String sideEffectKey;
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/sideEffect/GroupSideEffectStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/sideEffect/GroupSideEffectStep.java
index 7fe9626495..3afb36dd28 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/sideEffect/GroupSideEffectStep.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/sideEffect/GroupSideEffectStep.java
@@ -47,7 +47,7 @@ import java.util.Set;
/**
* @author Marko A. Rodriguez (http://markorodriguez.com)
*/
-public final class GroupSideEffectStep<S, K, V> extends SideEffectStep<S>
+public final class GroupSideEffectStep<S, K, V> extends SideEffectBarrierStep<S>
implements SideEffectCapable<Map<K, ?>, Map<K, V>>, TraversalParent, ByModulating, Grouping<S, K, V> {
private char state = 'k';
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/sideEffect/SideEffectBarrierStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/sideEffect/SideEffectBarrierStep.java
new file mode 100644
index 0000000000..8511134e8c
--- /dev/null
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/sideEffect/SideEffectBarrierStep.java
@@ -0,0 +1,99 @@
+/*
+ * 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.sideEffect;
+
+import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
+import org.apache.tinkerpop.gremlin.process.traversal.Traverser;
+import org.apache.tinkerpop.gremlin.process.traversal.step.LocalBarrier;
+import org.apache.tinkerpop.gremlin.process.traversal.step.util.AbstractStep;
+import org.apache.tinkerpop.gremlin.process.traversal.traverser.util.TraverserSet;
+import org.apache.tinkerpop.gremlin.process.traversal.util.FastNoSuchElementException;
+
+import java.util.NoSuchElementException;
+
+public abstract class SideEffectBarrierStep<S> extends AbstractStep<S, S> implements LocalBarrier<S> {
+ protected TraverserSet<S> barrier;
+
+ public SideEffectBarrierStep(final Traversal.Admin traversal) {
+ super(traversal);
+ this.barrier = (TraverserSet<S>) this.traversal.getTraverserSetSupplier().get();
+ }
+
+ protected abstract void sideEffect(final Traverser.Admin<S> traverser);
+
+ @Override
+ protected Traverser.Admin<S> processNextStart() {
+ if (this.barrier.isEmpty()) {
+ this.processAllStarts();
+ }
+ return this.barrier.remove();
+ }
+
+ @Override
+ public void processAllStarts() {
+ while (this.starts.hasNext()) {
+ final Traverser.Admin<S> traverser = this.starts.next();
+ sideEffect(traverser);
+
+ traverser.setStepId(this.getNextStep().getId());
+ // when barrier is reloaded, the traversers should be at the next step
+ this.barrier.add(traverser);
+ }
+ }
+
+ @Override
+ public boolean hasNextBarrier() {
+ if (this.barrier.isEmpty()) {
+ this.processAllStarts();
+ }
+ return !this.barrier.isEmpty();
+ }
+
+ @Override
+ public TraverserSet<S> nextBarrier() throws NoSuchElementException {
+ if (this.barrier.isEmpty()) {
+ this.processAllStarts();
+ }
+ if (this.barrier.isEmpty())
+ throw FastNoSuchElementException.instance();
+ else {
+ final TraverserSet<S> temp = this.barrier;
+ this.barrier = (TraverserSet<S>) this.traversal.getTraverserSetSupplier().get();
+ return temp;
+ }
+ }
+
+ @Override
+ public void addBarrier(TraverserSet<S> barrier) {
+ this.barrier.addAll(barrier);
+ }
+
+ @Override
+ public void reset() {
+ super.reset();
+ this.barrier.clear();
+ }
+
+ @Override
+ public SideEffectBarrierStep<S> clone() {
+ final SideEffectBarrierStep<S> clone = (SideEffectBarrierStep<S>) super.clone();
+ clone.barrier = (TraverserSet<S>) this.traversal.getTraverserSetSupplier().get();
+ return clone;
+ }
+}
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/sideEffect/SubgraphStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/sideEffect/SubgraphStep.java
index 0bd81970ef..3d10239bd9 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/sideEffect/SubgraphStep.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/sideEffect/SubgraphStep.java
@@ -45,7 +45,7 @@ import java.util.Set;
* @author Stephen Mallette (http://stephen.genoprime.com)
* @author Marko A. Rodriguez (http://markorodriguez.com)
*/
-public final class SubgraphStep extends SideEffectStep<Edge> implements SideEffectCapable {
+public final class SubgraphStep extends SideEffectBarrierStep<Edge> implements SideEffectCapable {
private static final Set<TraverserRequirement> REQUIREMENTS = EnumSet.of(
TraverserRequirement.OBJECT,
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/sideEffect/TreeSideEffectStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/sideEffect/TreeSideEffectStep.java
index 1f34bf210f..2a89268920 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/sideEffect/TreeSideEffectStep.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/sideEffect/TreeSideEffectStep.java
@@ -41,7 +41,7 @@ import java.util.function.Supplier;
/**
* @author Marko A. Rodriguez (http://markorodriguez.com)
*/
-public final class TreeSideEffectStep<S> extends SideEffectStep<S> implements SideEffectCapable<Tree, Tree>, TraversalParent, ByModulating, PathProcessor {
+public final class TreeSideEffectStep<S> extends SideEffectBarrierStep<S> implements SideEffectCapable<Tree, Tree>, TraversalParent, ByModulating, PathProcessor {
private TraversalRing<Object, Object> traversalRing;
private String sideEffectKey;
diff --git a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs
index b85468c14c..05174930af 100644
--- a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs
+++ b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs
@@ -1953,6 +1953,8 @@ namespace Gremlin.Net.IntegrationTest.Gherkin
{"g_V_group_by_byXout_label_limitX0X_foldX_selectXvaluesX_unfold", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Group<object, object>().By().By(__.Out().Label().Limit<object>(0).Fold()).Select<object>(Column.Values).Unfold<object>()}},
{"g_V_group_by_byXout_label_limitX10X_foldX_selectXvaluesX_unfold_orderXlocalX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Group<object, object>().By().By(__.Out().Label().Limit<object>(10).Fold()).Select<object>(Column.Values).Unfold<object>().Order(Scope.Local)}},
{"g_V_group_by_byXout_label_tailX10X_foldX_selectXvaluesX_unfold_orderXlocalX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Group<object, object>().By().By(__.Out().Label().Tail<object>(10).Fold()).Select<object>(Column.Values).Unfold<object>().Order(Scope.Local)}},
+ {"g_V_groupXaX_byXnameX_by_selectXaX_countXlocalX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Group("a").By("name").By().Select<object>("a").Count(Scope.Local)}},
+ {"g_V_localXgroupXaX_byXnameX_by_selectXaX_countXlocalXX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Local<object>(__.Group("a").By("name").By().Select<object>("a").Count(Scope.Local))}},
{"g_V_outXcreatedX_groupCount_byXnameX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Out("created").GroupCount<object>().By("name")}},
{"g_V_groupCount_byXageX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().GroupCount<object>().By("age")}},
{"g_withStrategiesXProductiveByStrategyX_V_groupCount_byXageX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.WithStrategies(new ProductiveByStrategy()).V().GroupCount<object>().By("age")}},
@@ -1967,10 +1969,12 @@ namespace Gremlin.Net.IntegrationTest.Gherkin
{"g_V_unionXrepeatXoutX_timesX2X_groupCountXmX_byXlangXX__repeatXinX_timesX2X_groupCountXmX_byXnameXX_capXmX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Union<object>(__.Repeat(__.Out()).Times(2).GroupCount("m").By("lang"), __.Repeat(__.In()).Times(2).GroupCount("m").By("name")).Cap<object>("m")}},
{"g_V_outXcreatedX_groupCountXxX_capXxX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Out("created").GroupCount("x").Cap<object>("x")}},
{"g_V_groupCount_byXbothE_countX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().GroupCount<object>().By(__.BothE().Count())}},
- {"g_V_both_groupCountXaX_out_capXaX_selectXkeysX_unfold_both_groupCountXaX_capXaX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Both().GroupCount("a").Out().Cap<object>("a").Select<object>(Column.Keys).Unfold<object>().Both().GroupCount("a").Cap<object>("a")}},
+ {"g_V_both_localXgroupCountXaXX_out_capXaX_selectXkeysX_unfold_both_localXgroupCountXaXX_capXaX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Both().Local<object>(__.GroupCount("a")).Out().Cap<object>("a").Select<object>(Column.Keys).Unfold<object>().Both().Local<object>(__.GroupCount("a")).Cap<object>("a")}},
{"g_V_hasXperson_name_markoX_bothXknowsX_groupCount_byXvaluesXnameX_foldX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Has("person", "name", "marko").Both("knows").GroupCount<object>().By(__.Values<object>("name").Fold())}},
{"g_V_outXcreatedX_groupCount_byXnameX_byXageX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Out("created").GroupCount<object>().By("name").By("age")}},
{"g_V_outXcreatedX_groupCountXxX_byXnameX_byXageX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Out("created").GroupCount("x").By("name").By("age")}},
+ {"g_V_groupCountXaX_selectXaX_countXlocalX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().GroupCount("a").Select<object>("a").Count(Scope.Local)}},
+ {"g_V_localXgroupCountXaX_selectXaX_countXlocalXX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Local<object>(__.GroupCount("a").Select<object>("a").Count(Scope.Local))}},
{"g_VX1X_out_name_injectXdanielX_asXaX_mapXlengthX_path", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V(p["vid1"]).Out().Values<object>("name").Inject("daniel").As("a").Map<object>(__.Length()).Path()}},
{"g_injectXnull_1_3_nullX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject<object>(null, 1, 3, null)}},
{"g_injectX10_20_null_20_10_10X_groupCountXxX_dedup_asXyX_projectXa_bX_by_byXselectXxX_selectXselectXyXXX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject<object>(10, 20, null, 20, 10, 10).GroupCount("x").Dedup().As("y").Project<object>("a", "b").By().By(__.Select<object>("x").Select<object>(__.Select<object>("y")))}},
@@ -2050,6 +2054,7 @@ namespace Gremlin.Net.IntegrationTest.Gherkin
{"g_VX1X_outEXknowsX_subgraphXsgX_name_capXsgX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V(p["vid1"]).OutE("knows").Subgraph("sg").Values<object>("name").Cap<object>("sg")}},
{"g_V_repeatXbothEXcreatedX_subgraphXsgX_outVX_timesX5X_name_dedup_capXsgX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Repeat(__.BothE("created").Subgraph("sg").OutV()).Times(5).Values<object>("name").Dedup().Cap<object>("sg")}},
{"g_V_outEXnoexistX_subgraphXsgXcapXsgX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().OutE("noexist").Subgraph("sg").Cap<object>("sg")}},
+ {"g_E_hasXweight_0_5X_subgraphXaX_selectXaX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.E().Has("weight", 0.4).Subgraph("a").Select<object>("a")}},
{"g_VX1X_out_out_tree_byXnameX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V(p["vid1"]).Out().Out().Tree<object>().By("name")}},
{"g_VX1X_out_out_tree", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V(p["vid1"]).Out().Out().Tree<object>()}},
{"g_V_out_tree_byXageX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Out().Tree<object>().By("age")}},
@@ -2060,6 +2065,8 @@ namespace Gremlin.Net.IntegrationTest.Gherkin
{"g_VX1X_out_out_out_tree", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Out().Out().Out().Tree<object>()}},
{"g_VX1X_outE_inV_bothE_otherV_tree", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V(p["vid1"]).OutE().InV().BothE().OtherV().Tree<object>()}},
{"g_VX1X_outE_inV_bothE_otherV_tree_byXnameX_byXlabelX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V(p["vid1"]).OutE().InV().BothE().OtherV().Tree<object>().By("name").By(T.Label)}},
+ {"g_V_out_treeXaX_selectXaX_countXlocalX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Out().Tree("a").Select<object>("a").Count(Scope.Local)}},
+ {"g_V_out_order_byXnameX_localXtreeXaX_selectXaX_countXlocalXX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Out().Local<object>(__.Tree("a").Select<object>("a").Count(Scope.Local))}},
};
public static ITraversal UseTraversal(string scenarioName, GraphTraversalSource g, IDictionary<string, object> parameters, IDictionary<string, object> sideEffects)
diff --git a/gremlin-go/driver/cucumber/gremlin.go b/gremlin-go/driver/cucumber/gremlin.go
index 131b6b48b2..47668c7fa7 100644
--- a/gremlin-go/driver/cucumber/gremlin.go
+++ b/gremlin-go/driver/cucumber/gremlin.go
@@ -1923,6 +1923,8 @@ var translationMap = map[string][]func(g *gremlingo.GraphTraversalSource, p map[
"g_V_group_by_byXout_label_limitX0X_foldX_selectXvaluesX_unfold": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Group().By().By(gremlingo.T__.Out().Label().Limit(0).Fold()).Select(gremlingo.Column.Values).Unfold()}},
"g_V_group_by_byXout_label_limitX10X_foldX_selectXvaluesX_unfold_orderXlocalX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Group().By().By(gremlingo.T__.Out().Label().Limit(10).Fold()).Select(gremlingo.Column.Values).Unfold().Order(gremlingo.Scope.Local)}},
"g_V_group_by_byXout_label_tailX10X_foldX_selectXvaluesX_unfold_orderXlocalX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Group().By().By(gremlingo.T__.Out().Label().Tail(10).Fold()).Select(gremlingo.Column.Values).Unfold().Order(gremlingo.Scope.Local)}},
+ "g_V_groupXaX_byXnameX_by_selectXaX_countXlocalX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Group("a").By("name").By().Select("a").Count(gremlingo.Scope.Local)}},
+ "g_V_localXgroupXaX_byXnameX_by_selectXaX_countXlocalXX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Local(gremlingo.T__.Group("a").By("name").By().Select("a").Count(gremlingo.Scope.Local))}},
"g_V_outXcreatedX_groupCount_byXnameX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Out("created").GroupCount().By("name")}},
"g_V_groupCount_byXageX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().GroupCount().By("age")}},
"g_withStrategiesXProductiveByStrategyX_V_groupCount_byXageX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.WithStrategies(gremlingo.ProductiveByStrategy()).V().GroupCount().By("age")}},
@@ -1937,10 +1939,12 @@ var translationMap = map[string][]func(g *gremlingo.GraphTraversalSource, p map[
"g_V_unionXrepeatXoutX_timesX2X_groupCountXmX_byXlangXX__repeatXinX_timesX2X_groupCountXmX_byXnameXX_capXmX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Union(gremlingo.T__.Repeat(gremlingo.T__.Out()).Times(2).GroupCount("m").By("lang"), gremlingo.T__.Repeat(gremlingo.T__.In()).Times(2).GroupCount("m").By("name")).Cap("m")}},
"g_V_outXcreatedX_groupCountXxX_capXxX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Out("created").GroupCount("x").Cap("x")}},
"g_V_groupCount_byXbothE_countX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().GroupCount().By(gremlingo.T__.BothE().Count())}},
- "g_V_both_groupCountXaX_out_capXaX_selectXkeysX_unfold_both_groupCountXaX_capXaX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Both().GroupCount("a").Out().Cap("a").Select(gremlingo.Column.Keys).Unfold().Both().GroupCount("a").Cap("a")}},
+ "g_V_both_localXgroupCountXaXX_out_capXaX_selectXkeysX_unfold_both_localXgroupCountXaXX_capXaX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Both().Local(gremlingo.T__.GroupCount("a")).Out().Cap("a").Select(gremlingo.Column.Keys).Unfold().Both().Local(gremlingo.T__.GroupCount("a")).Cap("a")}},
"g_V_hasXperson_name_markoX_bothXknowsX_groupCount_byXvaluesXnameX_foldX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Has("person", "name", "marko").Both("knows").GroupCount().By(gremlingo.T__.Values("name").Fold())}},
"g_V_outXcreatedX_groupCount_byXnameX_byXageX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Out("created").GroupCount().By("name").By("age")}},
"g_V_outXcreatedX_groupCountXxX_byXnameX_byXageX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Out("created").GroupCount("x").By("name").By("age")}},
+ "g_V_groupCountXaX_selectXaX_countXlocalX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().GroupCount("a").Select("a").Count(gremlingo.Scope.Local)}},
+ "g_V_localXgroupCountXaX_selectXaX_countXlocalXX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Local(gremlingo.T__.GroupCount("a").Select("a").Count(gremlingo.Scope.Local))}},
"g_VX1X_out_name_injectXdanielX_asXaX_mapXlengthX_path": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V(p["vid1"]).Out().Values("name").Inject("daniel").As("a").Map(gremlingo.T__.Length()).Path()}},
"g_injectXnull_1_3_nullX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(nil, 1, 3, nil)}},
"g_injectX10_20_null_20_10_10X_groupCountXxX_dedup_asXyX_projectXa_bX_by_byXselectXxX_selectXselectXyXXX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(10, 20, nil, 20, 10, 10).GroupCount("x").Dedup().As("y").Project("a", "b").By().By(gremlingo.T__.Select("x").Select(gremlingo.T__.Select("y")))}},
@@ -2020,6 +2024,7 @@ var translationMap = map[string][]func(g *gremlingo.GraphTraversalSource, p map[
"g_VX1X_outEXknowsX_subgraphXsgX_name_capXsgX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V(p["vid1"]).OutE("knows").Subgraph("sg").Values("name").Cap("sg")}},
"g_V_repeatXbothEXcreatedX_subgraphXsgX_outVX_timesX5X_name_dedup_capXsgX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Repeat(gremlingo.T__.BothE("created").Subgraph("sg").OutV()).Times(5).Values("name").Dedup().Cap("sg")}},
"g_V_outEXnoexistX_subgraphXsgXcapXsgX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().OutE("noexist").Subgraph("sg").Cap("sg")}},
+ "g_E_hasXweight_0_5X_subgraphXaX_selectXaX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.E().Has("weight", 0.4).Subgraph("a").Select("a")}},
"g_VX1X_out_out_tree_byXnameX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V(p["vid1"]).Out().Out().Tree().By("name")}},
"g_VX1X_out_out_tree": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V(p["vid1"]).Out().Out().Tree()}},
"g_V_out_tree_byXageX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Out().Tree().By("age")}},
@@ -2030,6 +2035,8 @@ var translationMap = map[string][]func(g *gremlingo.GraphTraversalSource, p map[
"g_VX1X_out_out_out_tree": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Out().Out().Out().Tree()}},
"g_VX1X_outE_inV_bothE_otherV_tree": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V(p["vid1"]).OutE().InV().BothE().OtherV().Tree()}},
"g_VX1X_outE_inV_bothE_otherV_tree_byXnameX_byXlabelX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V(p["vid1"]).OutE().InV().BothE().OtherV().Tree().By("name").By(gremlingo.T.Label)}},
+ "g_V_out_treeXaX_selectXaX_countXlocalX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Out().Tree("a").Select("a").Count(gremlingo.Scope.Local)}},
+ "g_V_out_order_byXnameX_localXtreeXaX_selectXaX_countXlocalXX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Out().Local(gremlingo.T__.Tree("a").Select("a").Count(gremlingo.Scope.Local))}},
}
func GetTraversal(scenarioName string, g *gremlingo.GraphTraversalSource, parameters map[string]interface{}, sideEffects map[string]interface{}) (*gremlingo.GraphTraversal, error) {
diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/test/cucumber/feature-steps.js b/gremlin-javascript/src/main/javascript/gremlin-javascript/test/cucumber/feature-steps.js
index 10eea4d716..4bd8f2303c 100644
--- a/gremlin-javascript/src/main/javascript/gremlin-javascript/test/cucumber/feature-steps.js
+++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/test/cucumber/feature-steps.js
@@ -84,6 +84,7 @@ const ignoredScenarios = {
'g_VX1X_outEXknowsX_subgraphXsgX_name_capXsgX': new IgnoreError(ignoreReason.subgraphStepNotSupported),
'g_V_repeatXbothEXcreatedX_subgraphXsgX_outVX_timesX5X_name_dedup_capXsgX': new IgnoreError(ignoreReason.subgraphStepNotSupported),
'g_V_outEXnoexistX_subgraphXsgXcapXsgX': new IgnoreError(ignoreReason.subgraphStepNotSupported),
+ 'g_E_hasXweight_0_5X_subgraphXaX_selectXaX': new IgnoreError(ignoreReason.subgraphStepNotSupported),
// javascript doesn't have tree() step yet
'g_VX1X_out_out_tree_byXnameX': new IgnoreError(ignoreReason.treeStepNotSupported),
'g_VX1X_out_out_tree': new IgnoreError(ignoreReason.treeStepNotSupported),
@@ -95,6 +96,8 @@ const ignoredScenarios = {
'g_VX1X_out_out_out_tree': new IgnoreError(ignoreReason.treeStepNotSupported),
'g_VX1X_outE_inV_bothE_otherV_tree': new IgnoreError(ignoreReason.treeStepNotSupported),
'g_VX1X_outE_inV_bothE_otherV_tree_byXnameX_byXlabelX': new IgnoreError(ignoreReason.treeStepNotSupported),
+ 'g_V_out_treeXaX_selectXaX_countXlocalX': new IgnoreError(ignoreReason.treeStepNotSupported),
+ 'g_V_out_order_byXnameX_localXtreeXaX_selectXaX_countXlocalXX': new IgnoreError(ignoreReason.treeStepNotSupported),
// An associative array containing the scenario name as key, for example:
'g_withStrategiesXProductiveByStrategyX_V_groupCount_byXageX': new IgnoreError(ignoreReason.nullKeysInMapNotSupportedWell),
'g_V_shortestPath_edgesIncluded': new IgnoreError(ignoreReason.needsFurtherInvestigation),
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 c527e97179..f76429d77b 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
@@ -1954,6 +1954,8 @@ const gremlins = {
g_V_group_by_byXout_label_limitX0X_foldX_selectXvaluesX_unfold: [function({g}) { return g.V().group().by().by(__.out().label().limit(0).fold()).select(Column.values).unfold() }],
g_V_group_by_byXout_label_limitX10X_foldX_selectXvaluesX_unfold_orderXlocalX: [function({g}) { return g.V().group().by().by(__.out().label().limit(10).fold()).select(Column.values).unfold().order(Scope.local) }],
g_V_group_by_byXout_label_tailX10X_foldX_selectXvaluesX_unfold_orderXlocalX: [function({g}) { return g.V().group().by().by(__.out().label().tail(10).fold()).select(Column.values).unfold().order(Scope.local) }],
+ g_V_groupXaX_byXnameX_by_selectXaX_countXlocalX: [function({g}) { return g.V().group("a").by("name").by().select("a").count(Scope.local) }],
+ g_V_localXgroupXaX_byXnameX_by_selectXaX_countXlocalXX: [function({g}) { return g.V().local(__.group("a").by("name").by().select("a").count(Scope.local)) }],
g_V_outXcreatedX_groupCount_byXnameX: [function({g}) { return g.V().out("created").groupCount().by("name") }],
g_V_groupCount_byXageX: [function({g}) { return g.V().groupCount().by("age") }],
g_withStrategiesXProductiveByStrategyX_V_groupCount_byXageX: [function({g}) { return g.withStrategies(new ProductiveByStrategy()).V().groupCount().by("age") }],
@@ -1968,10 +1970,12 @@ const gremlins = {
g_V_unionXrepeatXoutX_timesX2X_groupCountXmX_byXlangXX__repeatXinX_timesX2X_groupCountXmX_byXnameXX_capXmX: [function({g}) { return g.V().union(__.repeat(__.out()).times(2).groupCount("m").by("lang"), __.repeat(__.in_()).times(2).groupCount("m").by("name")).cap("m") }],
g_V_outXcreatedX_groupCountXxX_capXxX: [function({g}) { return g.V().out("created").groupCount("x").cap("x") }],
g_V_groupCount_byXbothE_countX: [function({g}) { return g.V().groupCount().by(__.bothE().count()) }],
- g_V_both_groupCountXaX_out_capXaX_selectXkeysX_unfold_both_groupCountXaX_capXaX: [function({g}) { return g.V().both().groupCount("a").out().cap("a").select(Column.keys).unfold().both().groupCount("a").cap("a") }],
+ g_V_both_localXgroupCountXaXX_out_capXaX_selectXkeysX_unfold_both_localXgroupCountXaXX_capXaX: [function({g}) { return g.V().both().local(__.groupCount("a")).out().cap("a").select(Column.keys).unfold().both().local(__.groupCount("a")).cap("a") }],
g_V_hasXperson_name_markoX_bothXknowsX_groupCount_byXvaluesXnameX_foldX: [function({g}) { return g.V().has("person", "name", "marko").both("knows").groupCount().by(__.values("name").fold()) }],
g_V_outXcreatedX_groupCount_byXnameX_byXageX: [function({g}) { return g.V().out("created").groupCount().by("name").by("age") }],
g_V_outXcreatedX_groupCountXxX_byXnameX_byXageX: [function({g}) { return g.V().out("created").groupCount("x").by("name").by("age") }],
+ g_V_groupCountXaX_selectXaX_countXlocalX: [function({g}) { return g.V().groupCount("a").select("a").count(Scope.local) }],
+ g_V_localXgroupCountXaX_selectXaX_countXlocalXX: [function({g}) { return g.V().local(__.groupCount("a").select("a").count(Scope.local)) }],
g_VX1X_out_name_injectXdanielX_asXaX_mapXlengthX_path: [function({g, vid1}) { return g.V(vid1).out().values("name").inject("daniel").as("a").map(__.length()).path() }],
g_injectXnull_1_3_nullX: [function({g}) { return g.inject(null, 1, 3, null) }],
g_injectX10_20_null_20_10_10X_groupCountXxX_dedup_asXyX_projectXa_bX_by_byXselectXxX_selectXselectXyXXX: [function({g}) { return g.inject(10, 20, null, 20, 10, 10).groupCount("x").dedup().as("y").project("a", "b").by().by(__.select("x").select(__.select("y"))) }],
@@ -2051,6 +2055,7 @@ const gremlins = {
g_VX1X_outEXknowsX_subgraphXsgX_name_capXsgX: [function({g, vid1}) { return g.V(vid1).outE("knows").subgraph("sg").values("name").cap("sg") }],
g_V_repeatXbothEXcreatedX_subgraphXsgX_outVX_timesX5X_name_dedup_capXsgX: [function({g}) { return g.V().repeat(__.bothE("created").subgraph("sg").outV()).times(5).values("name").dedup().cap("sg") }],
g_V_outEXnoexistX_subgraphXsgXcapXsgX: [function({g}) { return g.V().outE("noexist").subgraph("sg").cap("sg") }],
+ g_E_hasXweight_0_5X_subgraphXaX_selectXaX: [function({g}) { return g.E().has("weight", 0.4).subgraph("a").select("a") }],
g_VX1X_out_out_tree_byXnameX: [function({g, vid1}) { return g.V(vid1).out().out().tree().by("name") }],
g_VX1X_out_out_tree: [function({g, vid1}) { return g.V(vid1).out().out().tree() }],
g_V_out_tree_byXageX: [function({g}) { return g.V().out().tree().by("age") }],
@@ -2061,6 +2066,8 @@ const gremlins = {
g_VX1X_out_out_out_tree: [function({g}) { return g.V().out().out().out().tree() }],
g_VX1X_outE_inV_bothE_otherV_tree: [function({g, vid1}) { return g.V(vid1).outE().inV().bothE().otherV().tree() }],
g_VX1X_outE_inV_bothE_otherV_tree_byXnameX_byXlabelX: [function({g, vid1}) { return g.V(vid1).outE().inV().bothE().otherV().tree().by("name").by(T.label) }],
+ g_V_out_treeXaX_selectXaX_countXlocalX: [function({g}) { return g.V().out().tree("a").select("a").count(Scope.local) }],
+ g_V_out_order_byXnameX_localXtreeXaX_selectXaX_countXlocalXX: [function({g}) { return g.V().out().local(__.tree("a").select("a").count(Scope.local)) }],
}
exports.gremlin = gremlins
diff --git a/gremlin-python/src/main/python/radish/gremlin.py b/gremlin-python/src/main/python/radish/gremlin.py
index 34d0ecf07d..403114e6b9 100644
--- a/gremlin-python/src/main/python/radish/gremlin.py
+++ b/gremlin-python/src/main/python/radish/gremlin.py
@@ -1926,6 +1926,8 @@ world.gremlins = {
'g_V_group_by_byXout_label_limitX0X_foldX_selectXvaluesX_unfold': [(lambda g:g.V().group().by().by(__.out().label().limit(0).fold()).select(Column.values).unfold())],
'g_V_group_by_byXout_label_limitX10X_foldX_selectXvaluesX_unfold_orderXlocalX': [(lambda g:g.V().group().by().by(__.out().label().limit(10).fold()).select(Column.values).unfold().order(Scope.local))],
'g_V_group_by_byXout_label_tailX10X_foldX_selectXvaluesX_unfold_orderXlocalX': [(lambda g:g.V().group().by().by(__.out().label().tail(10).fold()).select(Column.values).unfold().order(Scope.local))],
+ 'g_V_groupXaX_byXnameX_by_selectXaX_countXlocalX': [(lambda g:g.V().group('a').by('name').by().select('a').count(Scope.local))],
+ 'g_V_localXgroupXaX_byXnameX_by_selectXaX_countXlocalXX': [(lambda g:g.V().local(__.group('a').by('name').by().select('a').count(Scope.local)))],
'g_V_outXcreatedX_groupCount_byXnameX': [(lambda g:g.V().out('created').group_count().by('name'))],
'g_V_groupCount_byXageX': [(lambda g:g.V().group_count().by('age'))],
'g_withStrategiesXProductiveByStrategyX_V_groupCount_byXageX': [(lambda g:g.with_strategies(ProductiveByStrategy()).V().group_count().by('age'))],
@@ -1940,10 +1942,12 @@ world.gremlins = {
'g_V_unionXrepeatXoutX_timesX2X_groupCountXmX_byXlangXX__repeatXinX_timesX2X_groupCountXmX_byXnameXX_capXmX': [(lambda g:g.V().union(__.repeat(__.out()).times(2).group_count('m').by('lang'), __.repeat(__.in_()).times(2).group_count('m').by('name')).cap('m'))],
'g_V_outXcreatedX_groupCountXxX_capXxX': [(lambda g:g.V().out('created').group_count('x').cap('x'))],
'g_V_groupCount_byXbothE_countX': [(lambda g:g.V().group_count().by(__.both_e().count()))],
- 'g_V_both_groupCountXaX_out_capXaX_selectXkeysX_unfold_both_groupCountXaX_capXaX': [(lambda g:g.V().both().group_count('a').out().cap('a').select(Column.keys).unfold().both().group_count('a').cap('a'))],
+ 'g_V_both_localXgroupCountXaXX_out_capXaX_selectXkeysX_unfold_both_localXgroupCountXaXX_capXaX': [(lambda g:g.V().both().local(__.group_count('a')).out().cap('a').select(Column.keys).unfold().both().local(__.group_count('a')).cap('a'))],
'g_V_hasXperson_name_markoX_bothXknowsX_groupCount_byXvaluesXnameX_foldX': [(lambda g:g.V().has('person', 'name', 'marko').both('knows').group_count().by(__.values('name').fold()))],
'g_V_outXcreatedX_groupCount_byXnameX_byXageX': [(lambda g:g.V().out('created').group_count().by('name').by('age'))],
'g_V_outXcreatedX_groupCountXxX_byXnameX_byXageX': [(lambda g:g.V().out('created').group_count('x').by('name').by('age'))],
+ 'g_V_groupCountXaX_selectXaX_countXlocalX': [(lambda g:g.V().group_count('a').select('a').count(Scope.local))],
+ 'g_V_localXgroupCountXaX_selectXaX_countXlocalXX': [(lambda g:g.V().local(__.group_count('a').select('a').count(Scope.local)))],
'g_VX1X_out_name_injectXdanielX_asXaX_mapXlengthX_path': [(lambda g, vid1=None:g.V(vid1).out().values('name').inject('daniel').as_('a').map(__.length()).path())],
'g_injectXnull_1_3_nullX': [(lambda g:g.inject(None, 1, 3, None))],
'g_injectX10_20_null_20_10_10X_groupCountXxX_dedup_asXyX_projectXa_bX_by_byXselectXxX_selectXselectXyXXX': [(lambda g:g.inject(10, 20, None, 20, 10, 10).group_count('x').dedup().as_('y').project('a', 'b').by().by(__.select('x').select(__.select('y'))))],
@@ -2023,6 +2027,7 @@ world.gremlins = {
'g_VX1X_outEXknowsX_subgraphXsgX_name_capXsgX': [(lambda g, vid1=None:g.V(vid1).out_e('knows').subgraph('sg').values('name').cap('sg'))],
'g_V_repeatXbothEXcreatedX_subgraphXsgX_outVX_timesX5X_name_dedup_capXsgX': [(lambda g:g.V().repeat(__.both_e('created').subgraph('sg').out_v()).times(5).values('name').dedup().cap('sg'))],
'g_V_outEXnoexistX_subgraphXsgXcapXsgX': [(lambda g:g.V().out_e('noexist').subgraph('sg').cap('sg'))],
+ 'g_E_hasXweight_0_5X_subgraphXaX_selectXaX': [(lambda g:g.E().has('weight', 0.4).subgraph('a').select('a'))],
'g_VX1X_out_out_tree_byXnameX': [(lambda g, vid1=None:g.V(vid1).out().out().tree().by('name'))],
'g_VX1X_out_out_tree': [(lambda g, vid1=None:g.V(vid1).out().out().tree())],
'g_V_out_tree_byXageX': [(lambda g:g.V().out().tree().by('age'))],
@@ -2033,4 +2038,6 @@ world.gremlins = {
'g_VX1X_out_out_out_tree': [(lambda g:g.V().out().out().out().tree())],
'g_VX1X_outE_inV_bothE_otherV_tree': [(lambda g, vid1=None:g.V(vid1).out_e().in_v().both_e().other_v().tree())],
'g_VX1X_outE_inV_bothE_otherV_tree_byXnameX_byXlabelX': [(lambda g, vid1=None:g.V(vid1).out_e().in_v().both_e().other_v().tree().by('name').by(T.label))],
+ 'g_V_out_treeXaX_selectXaX_countXlocalX': [(lambda g:g.V().out().tree('a').select('a').count(Scope.local))],
+ 'g_V_out_order_byXnameX_localXtreeXaX_selectXaX_countXlocalXX': [(lambda g:g.V().out().local(__.tree('a').select('a').count(Scope.local)))],
}
diff --git a/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/driver/remote/RemoteWorld.java b/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/driver/remote/RemoteWorld.java
index de0ff4b211..f82e3e81da 100644
--- a/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/driver/remote/RemoteWorld.java
+++ b/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/driver/remote/RemoteWorld.java
@@ -147,6 +147,11 @@ public abstract class RemoteWorld implements World {
case "g_V_repeatXboth_repeatXorder_byXnameXX_timesX1XX_timesX1X":
case "g_V_order_byXname_descX_repeatXboth_simplePath_order_byXname_descXX_timesX2X_path":
throw new AssumptionViolatedException("GraphComputer doesn't order within repeat");
+ // TINKERPOP-3209
+ case "g_V_localXgroupCountXaX_selectXaX_countXlocalXX":
+ case "g_V_localXgroupXaX_byXnameX_by_selectXaX_countXlocalXX":
+ case "g_V_out_order_byXnameX_localXtreeXaX_selectXaX_countXlocalXX":
+ throw new AssumptionViolatedException("Selecting side effects inside local() produces inconsistent results in OLAP (See TINKERPOP-3209)");
default:
// Do nothing
}
diff --git a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/sideEffect/GroupCountTest.java b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/sideEffect/GroupCountTest.java
index 1051e2a4b3..081f3fa864 100644
--- a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/sideEffect/GroupCountTest.java
+++ b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/sideEffect/GroupCountTest.java
@@ -32,7 +32,6 @@ import org.junit.runner.RunWith;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
-import java.util.List;
import java.util.Map;
import static org.apache.tinkerpop.gremlin.LoadGraphWith.GraphData.MODERN;
@@ -74,7 +73,7 @@ public abstract class GroupCountTest extends AbstractGremlinProcessTest {
public abstract Traversal<Vertex, Long> get_g_V_unionXoutXknowsX__outXcreatedX_inXcreatedXX_groupCount_selectXvaluesX_unfold_sum();
- public abstract Traversal<Vertex, Map<Vertex, Long>> get_g_V_both_groupCountXaX_out_capXaX_selectXkeysX_unfold_both_groupCountXaX_capXaX();
+ public abstract Traversal<Vertex, Map<Vertex, Long>> get_g_V_both_localXgroupCountXaXX_out_capXaX_selectXkeysX_unfold_both_localXgroupCountXaXX_capXaX();
public abstract Traversal<Vertex, String> get_g_V_both_groupCountXaX_byXlabelX_asXbX_barrier_whereXselectXaX_selectXsoftwareX_isXgtX2XXX_selectXbX_name();
@@ -237,8 +236,8 @@ public abstract class GroupCountTest extends AbstractGremlinProcessTest {
@Test
@LoadGraphWith(MODERN)
- public void g_V_both_groupCountXaX_out_capXaX_selectXkeysX_unfold_both_groupCountXaX_capXaX() {
- final Traversal<Vertex, Map<Vertex, Long>> traversal = get_g_V_both_groupCountXaX_out_capXaX_selectXkeysX_unfold_both_groupCountXaX_capXaX();
+ public void g_V_both_localXgroupCountXaXX_out_capXaX_selectXkeysX_unfold_both_localXgroupCountXaXX_capXaX() {
+ final Traversal<Vertex, Map<Vertex, Long>> traversal = get_g_V_both_localXgroupCountXaXX_out_capXaX_selectXkeysX_unfold_both_localXgroupCountXaXX_capXaX();
printTraversalForm(traversal);
// [{v[1]=6, v[2]=2, v[3]=6, v[4]=6, v[5]=2, v[6]=2}]
final Map<Vertex, Long> map = traversal.next();
@@ -334,8 +333,8 @@ public abstract class GroupCountTest extends AbstractGremlinProcessTest {
}
@Override
- public Traversal<Vertex, Map<Vertex, Long>> get_g_V_both_groupCountXaX_out_capXaX_selectXkeysX_unfold_both_groupCountXaX_capXaX() {
- return g.V().both().groupCount("a").out().cap("a").select(Column.keys).unfold().both().groupCount("a").cap("a");
+ public Traversal<Vertex, Map<Vertex, Long>> get_g_V_both_localXgroupCountXaXX_out_capXaX_selectXkeysX_unfold_both_localXgroupCountXaXX_capXaX() {
+ return g.V().both().local(__.groupCount("a")).out().cap("a").select(Column.keys).unfold().both().local(__.groupCount("a")).cap("a");
}
@Override
diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/sideEffect/Group.feature b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/sideEffect/Group.feature
index f31fdd51fc..2140d41717 100644
--- a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/sideEffect/Group.feature
+++ b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/sideEffect/Group.feature
@@ -365,3 +365,35 @@ Feature: Step - group()
| l[software,software] |
| l[] |
| l[software] |
+
+ Scenario: g_V_groupXaX_byXnameX_by_selectXaX_countXlocalX
+ Given the modern graph
+ And the traversal of
+ """
+ g.V().group("a").by("name").by().select("a").count(local)
+ """
+ When iterated to list
+ Then the result should be unordered
+ | result |
+ | d[6].l |
+ | d[6].l |
+ | d[6].l |
+ | d[6].l |
+ | d[6].l |
+ | d[6].l |
+
+ Scenario: g_V_localXgroupXaX_byXnameX_by_selectXaX_countXlocalXX
+ Given the modern graph
+ And the traversal of
+ """
+ g.V().local(group("a").by("name").by().select("a").count(local))
+ """
+ When iterated to list
+ Then the result should be unordered
+ | result |
+ | d[1].l |
+ | d[2].l |
+ | d[3].l |
+ | d[4].l |
+ | d[5].l |
+ | d[6].l |
diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/sideEffect/GroupCount.feature b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/sideEffect/GroupCount.feature
index 861ce5fbc4..3dda98023f 100644
--- a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/sideEffect/GroupCount.feature
+++ b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/sideEffect/GroupCount.feature
@@ -205,11 +205,11 @@ Feature: Step - groupCount()
| result |
| m[{"d[1].l":"d[3].l","d[3].l":"d[3].l"}] |
- Scenario: g_V_both_groupCountXaX_out_capXaX_selectXkeysX_unfold_both_groupCountXaX_capXaX
+ Scenario: g_V_both_localXgroupCountXaXX_out_capXaX_selectXkeysX_unfold_both_localXgroupCountXaXX_capXaX
Given the modern graph
And the traversal of
"""
- g.V().both().groupCount("a").out().cap("a").select(Column.keys).unfold().both().groupCount("a").cap("a")
+ g.V().both().local(groupCount("a")).out().cap("a").select(Column.keys).unfold().both().local(groupCount("a")).cap("a")
"""
When iterated to list
Then the result should be unordered
@@ -243,4 +243,36 @@ Feature: Step - groupCount()
g.V().out("created").groupCount("x").by("name").by("age")
"""
When iterated to list
- Then the traversal will raise an error with message containing text of "GroupCount step can only have one by modulator"
\ No newline at end of file
+ Then the traversal will raise an error with message containing text of "GroupCount step can only have one by modulator"
+
+ Scenario: g_V_groupCountXaX_selectXaX_countXlocalX
+ Given the modern graph
+ And the traversal of
+ """
+ g.V().groupCount("a").select("a").count(local)
+ """
+ When iterated to list
+ Then the result should be unordered
+ | result |
+ | d[6].l |
+ | d[6].l |
+ | d[6].l |
+ | d[6].l |
+ | d[6].l |
+ | d[6].l |
+
+ Scenario: g_V_localXgroupCountXaX_selectXaX_countXlocalXX
+ Given the modern graph
+ And the traversal of
+ """
+ g.V().local(groupCount("a").select("a").count(local))
+ """
+ When iterated to list
+ Then the result should be unordered
+ | result |
+ | d[1].l |
+ | d[2].l |
+ | d[3].l |
+ | d[4].l |
+ | d[5].l |
+ | d[6].l |
diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/sideEffect/Subgraph.feature b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/sideEffect/Subgraph.feature
index cc27852fa1..74ec88233f 100644
--- a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/sideEffect/Subgraph.feature
+++ b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/sideEffect/Subgraph.feature
@@ -68,3 +68,20 @@ Feature: Step - subgraph()
| edges |
And the result should be a subgraph with the following
| vertices |
+
+ Scenario: g_E_hasXweight_0_5X_subgraphXaX_selectXaX
+ Given the modern graph
+ And the traversal of
+ """
+ g.E().has("weight", 0.4).subgraph("a").select("a")
+ """
+ When iterated next
+ Then the result should be a subgraph with the following
+ | edges |
+ | e[marko-created->lop] |
+ | e[josh-created->lop] |
+ And the result should be a subgraph with the following
+ | vertices |
+ | v[marko] |
+ | v[lop] |
+ | v[josh] |
diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/sideEffect/Tree.feature b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/sideEffect/Tree.feature
index 93219940f6..8f85c68226 100644
--- a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/sideEffect/Tree.feature
+++ b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/sideEffect/Tree.feature
@@ -205,4 +205,37 @@ Feature: Step - tree()
|--marko
|--josh
|--peter
- """
\ No newline at end of file
+ """
+
+ Scenario: g_V_out_treeXaX_selectXaX_countXlocalX
+ Given the modern graph
+ And the traversal of
+ """
+ g.V().out().tree("a").select("a").count(local)
+ """
+ When iterated to list
+ Then the result should be unordered
+ | result |
+ | d[3].l |
+ | d[3].l |
+ | d[3].l |
+ | d[3].l |
+ | d[3].l |
+ | d[3].l |
+
+ @InsertionOrderingRequired
+ Scenario: g_V_out_order_byXnameX_localXtreeXaX_selectXaX_countXlocalXX
+ Given the modern graph
+ And the traversal of
+ """
+ g.V().out().local(tree("a").select("a").count(local))
+ """
+ When iterated to list
+ Then the result should be unordered
+ | result |
+ | d[1].l |
+ | d[1].l |
+ | d[1].l |
+ | d[2].l |
+ | d[2].l |
+ | d[3].l |
diff --git a/spark-gremlin/src/test/java/org/apache/tinkerpop/gremlin/spark/SparkGraphFeatureIntegrateTest.java b/spark-gremlin/src/test/java/org/apache/tinkerpop/gremlin/spark/SparkGraphFeatureIntegrateTest.java
index c8ca8ed6e4..3492acb5ec 100644
--- a/spark-gremlin/src/test/java/org/apache/tinkerpop/gremlin/spark/SparkGraphFeatureIntegrateTest.java
+++ b/spark-gremlin/src/test/java/org/apache/tinkerpop/gremlin/spark/SparkGraphFeatureIntegrateTest.java
@@ -68,6 +68,7 @@ public class SparkGraphFeatureIntegrateTest {
private static final String skipReasonLength = "Spark-Gremlin is OLAP-oriented and for OLTP operations, linear-scan joins are required. This particular tests takes many minutes to execute.";
private static final String skipReasonOrdering = "GraphComputer doesn't order within repeat.";
+ private static final String skipReasonTINKERPOP3209 = "Selecting side effects inside local() produces inconsistent results in OLAP (See TINKERPOP-3209)";
private static final List<Pair<String, String>> skip = new ArrayList<Pair<String,String>>() {{
add(Pair.with("g_V_both_both_count", skipReasonLength));
@@ -86,6 +87,9 @@ public class SparkGraphFeatureIntegrateTest {
add(Pair.with("g_V_repeatXunionXoutXknowsX_order_byXnameX_inXcreatedX_order_byXnameXXX_timesX1X", skipReasonOrdering));
add(Pair.with("g_V_repeatXboth_repeatXorder_byXnameXX_timesX1XX_timesX1X", skipReasonOrdering));
add(Pair.with("g_V_order_byXname_descX_repeatXboth_simplePath_order_byXname_descXX_timesX2X_path", skipReasonOrdering));
+ add(Pair.with("g_V_localXgroupCountXaX_selectXaX_countXlocalXX", skipReasonTINKERPOP3209));
+ add(Pair.with("g_V_localXgroupXaX_byXnameX_by_selectXaX_countXlocalXX", skipReasonTINKERPOP3209));
+ add(Pair.with("g_V_out_order_byXnameX_localXtreeXaX_selectXaX_countXlocalXX", skipReasonTINKERPOP3209));
}};
public static final class ServiceModule extends AbstractModule {
diff --git a/spark-gremlin/src/test/java/org/apache/tinkerpop/gremlin/spark/process/computer/traversal/strategy/optimization/SparkInterceptorStrategyTest.java b/spark-gremlin/src/test/java/org/apache/tinkerpop/gremlin/spark/process/computer/traversal/strategy/optimization/SparkInterceptorStrategyTest.java
index 6cdaf81dfb..87fc2add7a 100644
--- a/spark-gremlin/src/test/java/org/apache/tinkerpop/gremlin/spark/process/computer/traversal/strategy/optimization/SparkInterceptorStrategyTest.java
+++ b/spark-gremlin/src/test/java/org/apache/tinkerpop/gremlin/spark/process/computer/traversal/strategy/optimization/SparkInterceptorStrategyTest.java
@@ -78,7 +78,7 @@ public class SparkInterceptorStrategyTest extends AbstractSparkTest {
assertTrue(g.getStrategies().getStrategy(SparkInterceptorStrategy.class).isPresent());
assertTrue(g.V().count().explain().toString().contains(SparkInterceptorStrategy.class.getSimpleName()));
/// groupCount(m)-test
- Traversal.Admin<Vertex, Long> traversal = g.V().groupCount("m").by(T.label).count().asAdmin();
+ Traversal.Admin<Vertex, Long> traversal = g.V().local(__.groupCount("m").by(T.label)).count().asAdmin();
test(SparkStarBarrierInterceptor.class, 6l, traversal);
assertEquals(1, traversal.getSideEffects().keys().size());
assertTrue(traversal.getSideEffects().exists("m"));
diff --git a/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/TinkerWorld.java b/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/TinkerWorld.java
index db32d7618b..e3c89e0e6e 100644
--- a/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/TinkerWorld.java
+++ b/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/TinkerWorld.java
@@ -314,6 +314,11 @@ public abstract class TinkerWorld implements World {
case "g_V_repeatXboth_repeatXorder_byXnameXX_timesX1XX_timesX1X":
case "g_V_order_byXname_descX_repeatXboth_simplePath_order_byXname_descXX_timesX2X_path":
throw new AssumptionViolatedException("GraphComputer doesn't order within repeat");
+ // TINKERPOP-3209
+ case "g_V_localXgroupCountXaX_selectXaX_countXlocalXX":
+ case "g_V_localXgroupXaX_byXnameX_by_selectXaX_countXlocalXX":
+ case "g_V_out_order_byXnameX_localXtreeXaX_selectXaX_countXlocalXX":
+ throw new AssumptionViolatedException("Selecting side effects inside local() produces inconsistent results in OLAP (See TINKERPOP-3209)");
default:
// Do nothing
}