You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tinkerpop.apache.org by sp...@apache.org on 2023/05/18 18:39:16 UTC
[tinkerpop] 01/01: TINKERPOP-2873 Added union() as a start step
This is an automated email from the ASF dual-hosted git repository.
spmallette pushed a commit to branch TINKERPOP-2873
in repository https://gitbox.apache.org/repos/asf/tinkerpop.git
commit 143d187b5a5a700465071f4dfdd4f82620b17496
Author: Stephen Mallette <st...@amazon.com>
AuthorDate: Thu May 18 14:38:40 2023 -0400
TINKERPOP-2873 Added union() as a start step
---
CHANGELOG.asciidoc | 1 +
docs/src/reference/the-traversal.asciidoc | 2 +
docs/src/upgrade/release-3.7.x.asciidoc | 44 +++++++++++---
.../grammar/DefaultGremlinBaseVisitor.java | 5 +-
.../language/grammar/TraversalRootVisitor.java | 2 +-
.../grammar/TraversalSourceSpawnMethodVisitor.java | 13 +++-
.../traversal/dsl/graph/GraphTraversalSource.java | 17 ++++++
.../process/traversal/step/branch/UnionStep.java | 33 +++++++++-
gremlin-go/driver/cucumber/gremlin.go | 5 ++
gremlin-go/driver/graphTraversalSource.go | 7 +++
.../lib/process/graph-traversal.js | 10 +++
.../gremlin-javascript/test/cucumber/gremlin.js | 5 ++
gremlin-language/src/main/antlr4/Gremlin.g4 | 5 ++
.../gremlin_python/process/graph_traversal.py | 5 ++
.../gremlin/test/features/branch/Union.feature | 71 ++++++++++++++++++++++
15 files changed, 211 insertions(+), 14 deletions(-)
diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc
index 3399827f9f..2778c756ad 100644
--- a/CHANGELOG.asciidoc
+++ b/CHANGELOG.asciidoc
@@ -28,6 +28,7 @@ This release also includes changes from <<release-3-6-XXX, 3.6.XXX>>.
* Removed `connectOnStartup` configuration option from gremlin-javascript.
* Changed `Gremlin.version()` to read from the more specificly named `tinkerpop-version` attribute.
* Added warning on vertex property cardinality mismatch when reading GraphML.
+* Added a `union()` start step.
* Bumped to `ws` 8.x for `gremlin-javascript`.
* Added support for mid-traversal `E()`-steps to Gremlin core and GLV's.
* Added nullable annotations to Gremlin.NET.
diff --git a/docs/src/reference/the-traversal.asciidoc b/docs/src/reference/the-traversal.asciidoc
index 99cbf29bda..5ab49721ac 100644
--- a/docs/src/reference/the-traversal.asciidoc
+++ b/docs/src/reference/the-traversal.asciidoc
@@ -4277,6 +4277,8 @@ g.V(4).union(
g.V(4).union(
__.in().values('age'),
out().values('lang')).path()
+g.union(V().has('person','name','vadas'),
+ V().has('software','name','lop').in('created'))
----
*Additional References*
diff --git a/docs/src/upgrade/release-3.7.x.asciidoc b/docs/src/upgrade/release-3.7.x.asciidoc
index cf4c80b128..9450706119 100644
--- a/docs/src/upgrade/release-3.7.x.asciidoc
+++ b/docs/src/upgrade/release-3.7.x.asciidoc
@@ -29,6 +29,33 @@ Please see the link:https://github.com/apache/tinkerpop/blob/3.7.0/CHANGELOG.asc
=== Upgrading for Users
+==== union() Start Step
+
+The `union()`-step could only be used mid-traversal after a start step. The typical workaround for this issue was to
+use `inject()` with a dummy value to start the traversal and then utilize `union()`:
+
+[source,text]
+----
+gremlin> g.inject(0).union(V().has('name','vadas'),
+......1> V().has('software','name','lop').in('created')).
+......2> values('name')
+==>vadas
+==>marko
+==>josh
+==>peter
+----
+
+As of this version, `union()` can be used more directly to avoid the workaround:
+
+[source,text]
+----
+g.union(V().has('name','vadas'),
+ V().has('software','name','lop').in('created')).
+ values('name')
+----
+
+See: link:https://issues.apache.org/jira/browse/TINKERPOP-2873[TINKERPOP-2873]
+
==== Properties on Elements
===== Introduction
@@ -43,7 +70,7 @@ If you need to get a property, then this can be explicitly configured with `Halt
[source,java]
----
- g.withComputer().withStrategies(HaltedTraverserFactoryStrategy.detached())
+g.withComputer().withStrategies(HaltedTraverserFactoryStrategy.detached())
----
===== Output comparison for Gremlin Server 3.5/3.6 and 3.7
@@ -52,24 +79,24 @@ Let's take a closer look at a Javascript GLV code example in 3.6 and 3.7:
[source,javascript]
----
- const client = new Client('ws://localhost:8182/gremlin',{traversalSource: 'gmodern'});
- await client.open();
- const result = await client.submit('g.V(1)');
- console.log(JSON.stringify(result.first()));
- await client.close();
+const client = new Client('ws://localhost:8182/gremlin',{traversalSource: 'gmodern'});
+await client.open();
+const result = await client.submit('g.V(1)');
+console.log(JSON.stringify(result.first()));
+await client.close();
----
The result will be different depending on the version of Gremlin Server.
For 3.5/3.6:
[source,json]
----
- {"id":1,"label":"person"}
+{"id":1,"label":"person"}
----
For 3.7:
[source,json]
----
- {"id":1,"label":"person","properties":{"name":[{"id":0,"label":"name","value":"marko","key":"name"}],"age":[{"id":1,"label":"age","value":29,"key":"age"}]}}
+{"id":1,"label":"person","properties":{"name":[{"id":0,"label":"name","value":"marko","key":"name"}],"age":[{"id":1,"label":"age","value":29,"key":"age"}]}}
----
===== Enabling the previous behavior
@@ -96,7 +123,6 @@ ReferenceElement-type objects are no longer returned - you get a DetachedElement
See: link:https://issues.apache.org/jira/browse/TINKERPOP-2824[TINKERPOP-2824]
-
==== Gremlin.NET: Nullable Annotations
Gremlin.NET now uses link:https://learn.microsoft.com/en-us/dotnet/csharp/nullable-references#nullable-variable-annotations[nullable annotations]
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/DefaultGremlinBaseVisitor.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/DefaultGremlinBaseVisitor.java
index 4531861838..798578dfac 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/DefaultGremlinBaseVisitor.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/DefaultGremlinBaseVisitor.java
@@ -1432,8 +1432,11 @@ public class DefaultGremlinBaseVisitor<T> extends AbstractParseTreeVisitor<T> im
* {@inheritDoc}
*/
@Override public T visitTraversalMethod_mergeV_empty(final GremlinParser.TraversalMethod_mergeV_emptyContext ctx) { notImplemented(ctx); return null; }
+
+ @Override public T visitTraversalMethod_mergeE_empty(final GremlinParser.TraversalMethod_mergeE_emptyContext ctx) { notImplemented(ctx); return null; }
/**
* {@inheritDoc}
*/
- @Override public T visitTraversalMethod_mergeE_empty(final GremlinParser.TraversalMethod_mergeE_emptyContext ctx) { notImplemented(ctx); return null; }
+ @Override
+ public T visitTraversalSourceSpawnMethod_union(final GremlinParser.TraversalSourceSpawnMethod_unionContext ctx) { notImplemented(ctx); return null; }
}
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/TraversalRootVisitor.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/TraversalRootVisitor.java
index 3d3ca4f695..0e52b606d5 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/TraversalRootVisitor.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/TraversalRootVisitor.java
@@ -75,7 +75,7 @@ public class TraversalRootVisitor<G extends Traversal> extends DefaultGremlinBas
(GremlinParser.TraversalSourceContext) ctx.getChild(childIndexOfTraversalSource));
// call traversal source spawn method
final int childIndexOfTraversalSourceSpawnMethod = 2;
- final GraphTraversal traversal = new TraversalSourceSpawnMethodVisitor(source, this).visitTraversalSourceSpawnMethod(
+ final GraphTraversal traversal = new TraversalSourceSpawnMethodVisitor(source, this, antlr).visitTraversalSourceSpawnMethod(
(GremlinParser.TraversalSourceSpawnMethodContext) ctx.getChild(childIndexOfTraversalSourceSpawnMethod));
if (ctx.getChildCount() == 5) {
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/TraversalSourceSpawnMethodVisitor.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/TraversalSourceSpawnMethodVisitor.java
index 5d1ebaa8c9..de890a0c5f 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/TraversalSourceSpawnMethodVisitor.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/TraversalSourceSpawnMethodVisitor.java
@@ -33,10 +33,14 @@ public class TraversalSourceSpawnMethodVisitor extends DefaultGremlinBaseVisitor
protected GraphTraversal graphTraversal;
protected final DefaultGremlinBaseVisitor<Traversal> anonymousVisitor;
+ protected final GremlinAntlrToJava antlr;
+
public TraversalSourceSpawnMethodVisitor(final GraphTraversalSource traversalSource,
- final DefaultGremlinBaseVisitor<Traversal> anonymousVisitor) {
+ final DefaultGremlinBaseVisitor<Traversal> anonymousVisitor,
+ final GremlinAntlrToJava antlr) {
this.traversalSource = traversalSource;
this.anonymousVisitor = anonymousVisitor;
+ this.antlr = antlr;
}
/**
@@ -200,4 +204,11 @@ public class TraversalSourceSpawnMethodVisitor extends DefaultGremlinBaseVisitor
anonymousVisitor.visitNestedTraversal(ctx.nestedTraversal()));
}
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public GraphTraversal visitTraversalSourceSpawnMethod_union(final GremlinParser.TraversalSourceSpawnMethod_unionContext ctx) {
+ return this.traversalSource.union(antlr.tListVisitor.visitNestedTraversalList(ctx.nestedTraversalList()));
+ }
}
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversalSource.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversalSource.java
index c12bf41e46..9dfd7499cc 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversalSource.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversalSource.java
@@ -27,6 +27,7 @@ import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
import org.apache.tinkerpop.gremlin.process.traversal.TraversalSource;
import org.apache.tinkerpop.gremlin.process.traversal.TraversalStrategies;
import org.apache.tinkerpop.gremlin.process.traversal.TraversalStrategy;
+import org.apache.tinkerpop.gremlin.process.traversal.step.branch.UnionStep;
import org.apache.tinkerpop.gremlin.process.traversal.step.map.AddEdgeStartStep;
import org.apache.tinkerpop.gremlin.process.traversal.step.map.AddVertexStartStep;
import org.apache.tinkerpop.gremlin.process.traversal.step.map.CallStep;
@@ -46,6 +47,7 @@ import org.apache.tinkerpop.gremlin.structure.Vertex;
import org.apache.tinkerpop.gremlin.structure.util.StringFactory;
import org.apache.tinkerpop.gremlin.structure.util.empty.EmptyGraph;
+import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Optional;
@@ -554,6 +556,21 @@ public class GraphTraversalSource implements TraversalSource {
return traversal.addStep(step);
}
+ /**
+ * Merges the results of an arbitrary number of traversals.
+ *
+ * @param unionTraversals the traversals to merge
+ * @see <a href="http://tinkerpop.apache.org/docs/${project.version}/reference/#union-step" target="_blank">Reference Documentation - Union Step</a>
+ * @since 3.7.0
+ */
+ public <S> GraphTraversal<S, S> union(final Traversal<?, S>... unionTraversals) {
+ final GraphTraversalSource clone = this.clone();
+ clone.bytecode.addStep(GraphTraversal.Symbols.union, unionTraversals);
+ final GraphTraversal.Admin traversal = new DefaultGraphTraversal(clone);
+ final UnionStep<?, S> step = new UnionStep<>(traversal, true, Arrays.copyOf(unionTraversals, unionTraversals.length, Traversal.Admin[].class));
+ return traversal.addStep(step);
+ }
+
/**
* Performs a read or write based operation on the {@link Graph} backing this {@code GraphTraversalSource}. This
* step can be accompanied by the {@link GraphTraversal#with(String, Object)} modulator for further configuration
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/branch/UnionStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/branch/UnionStep.java
index 1ce613733e..96060821c8 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/branch/UnionStep.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/branch/UnionStep.java
@@ -19,25 +19,48 @@
package org.apache.tinkerpop.gremlin.process.traversal.step.branch;
import org.apache.tinkerpop.gremlin.process.traversal.Pick;
+import org.apache.tinkerpop.gremlin.process.traversal.Step;
import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
+import org.apache.tinkerpop.gremlin.process.traversal.Traverser;
+import org.apache.tinkerpop.gremlin.process.traversal.TraverserGenerator;
import org.apache.tinkerpop.gremlin.process.traversal.lambda.ConstantTraversal;
import org.apache.tinkerpop.gremlin.structure.util.StringFactory;
import java.util.Collections;
+import java.util.Iterator;
/**
* @author Marko A. Rodriguez (http://markorodriguez.com)
*/
-public final class UnionStep<S, E> extends BranchStep<S, E, Pick> {
+public class UnionStep<S, E> extends BranchStep<S, E, Pick> {
- public UnionStep(final Traversal.Admin traversal, final Traversal.Admin<?, E>... unionTraversals) {
+ private final boolean isStart;
+ protected boolean first = true;
+
+ public UnionStep(final Traversal.Admin traversal, final boolean isStart, final Traversal.Admin<?, E>... unionTraversals) {
super(traversal);
+ this.isStart = isStart;
this.setBranchTraversal(new ConstantTraversal<>(Pick.any));
for (final Traversal.Admin<?, E> union : unionTraversals) {
this.addChildOption(Pick.any, (Traversal.Admin) union);
}
}
+ public UnionStep(final Traversal.Admin traversal, final Traversal.Admin<?, E>... unionTraversals) {
+ this(traversal, false, unionTraversals);
+ }
+
+ @Override
+ protected Traverser.Admin<E> processNextStart() {
+ // when it's a start step a traverser needs to be created to kick off the traversal.
+ if (isStart && first) {
+ first = false;
+ final TraverserGenerator generator = this.getTraversal().getTraverserGenerator();
+ this.addStart(generator.generate(false, (Step) this, 1L));
+ }
+ return super.processNextStart();
+ }
+
@Override
public void addChildOption(final Pick pickToken, final Traversal.Admin<S, E> traversalOption) {
if (Pick.any != pickToken)
@@ -45,6 +68,12 @@ public final class UnionStep<S, E> extends BranchStep<S, E, Pick> {
super.addChildOption(pickToken, traversalOption);
}
+ @Override
+ public void reset() {
+ super.reset();
+ first = true;
+ }
+
@Override
public String toString() {
return StringFactory.stepString(this, this.traversalPickOptions.getOrDefault(Pick.any, Collections.emptyList()));
diff --git a/gremlin-go/driver/cucumber/gremlin.go b/gremlin-go/driver/cucumber/gremlin.go
index bfa60c2bb5..1133945263 100644
--- a/gremlin-go/driver/cucumber/gremlin.go
+++ b/gremlin-go/driver/cucumber/gremlin.go
@@ -86,6 +86,11 @@ var translationMap = map[string][]func(g *gremlingo.GraphTraversalSource, p map[
"g_VX1X_repeatXrepeatXunionXout_uses_out_traversesXX_whereXloops_isX0X_timesX1X_timeX2X_name": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V(p["vid1"]).Repeat(gremlingo.T__.Repeat(gremlingo.T__.Union(gremlingo.T__.Out("uses"), gremlingo.T__.Out("traverses")).Where(gremlingo.T__.Loops().Is(0))).Times(1)).Times(2).Values("name")}},
"g_V_repeatXa_outXknows_repeatXb_outXcreatedX_filterXloops_isX0XX_emit_lang": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Repeat("a", gremlingo.T__.Out("knows").Repeat("b", gremlingo.T__.Out("created").Filter(gremlingo.T__.Loops("a").Is(0))).Emit()).Emit().Values("lang")}},
"g_VX6X_repeatXa_bothXcreatedX_simplePathX_emitXrepeatXb_bothXknowsXX_untilXloopsXbX_asXb_whereXloopsXaX_asXbX_hasXname_vadasXX_dedup_name": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V(p["vid6"]).Repeat("a", gremlingo.T__.Both("created").SimplePath()).Emit(gremlingo.T__.Repeat("b", gremlingo.T__.Both("knows")).Until(gremlingo.T__.Loops("b").As("b").Where(gremlingo.T__.Loops("a").As("b"))).Has("name", "vadas")).Dedup().Value [...]
+ "g_unionXX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Union()}},
+ "g_unionXconstantX1X_constantX2X_constantX3XX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Union(gremlingo.T__.Constant(p["xx1"]), gremlingo.T__.Constant(p["xx2"]), gremlingo.T__.Constant(p["xx3"]))}},
+ "g_unionXV_name": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Union(gremlingo.T__.V().Values("name"))}},
+ "g_unionXVXv1X_VX4XX_name": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Union(gremlingo.T__.V(p["v1"]), gremlingo.T__.V(p["v4"])).Values("name")}},
+ "g_unionXV_hasLabelXsoftwareX_V_hasLabelXpersonXX_name": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Union(gremlingo.T__.V().HasLabel("software"), gremlingo.T__.V().HasLabel("person")).Values("name")}},
"g_V_unionXout__inX_name": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Union(gremlingo.T__.Out(), gremlingo.T__.In()).Values("name")}},
"g_VX1X_unionXrepeatXoutX_timesX2X__outX_name": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V(p["vid1"]).Union(gremlingo.T__.Repeat(gremlingo.T__.Out()).Times(2), gremlingo.T__.Out()).Values("name")}},
"g_V_chooseXlabel_is_person__unionX__out_lang__out_nameX__in_labelX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Choose(gremlingo.T__.Label().Is("person"), gremlingo.T__.Union(gremlingo.T__.Out().Values("lang"), gremlingo.T__.Out().Values("name")), gremlingo.T__.In().Label())}},
diff --git a/gremlin-go/driver/graphTraversalSource.go b/gremlin-go/driver/graphTraversalSource.go
index b8f866048d..31ee2d4be7 100644
--- a/gremlin-go/driver/graphTraversalSource.go
+++ b/gremlin-go/driver/graphTraversalSource.go
@@ -215,6 +215,13 @@ func (gts *GraphTraversalSource) MergeV(args ...interface{}) *GraphTraversal {
return traversal
}
+// Union allows for a multi-branched start to a traversal.
+func (gts *GraphTraversalSource) Union(args ...interface{}) *GraphTraversal {
+ traversal := gts.GetGraphTraversal()
+ traversal.Bytecode.AddStep("union", args...)
+ return traversal
+}
+
func (gts *GraphTraversalSource) Tx() *Transaction {
return &Transaction{g: gts, remoteConnection: gts.remoteConnection}
}
diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/process/graph-traversal.js b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/process/graph-traversal.js
index 60b03ccf80..328c7f3ed9 100644
--- a/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/process/graph-traversal.js
+++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/process/graph-traversal.js
@@ -337,6 +337,16 @@ class GraphTraversalSource {
const b = new Bytecode(this.bytecode).addStep('call', args);
return new this.graphTraversalClass(this.graph, new TraversalStrategies(this.traversalStrategies), b);
}
+
+ /**
+ * union GraphTraversalSource method.
+ * @param {...Object} args
+ * @returns {GraphTraversal}
+ */
+ union(...args) {
+ const b = new Bytecode(this.bytecode).addStep('union', args);
+ return new this.graphTraversalClass(this.graph, new TraversalStrategies(this.traversalStrategies), b);
+ }
}
/**
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 0f052598d8..1e63756572 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
@@ -105,6 +105,11 @@ const gremlins = {
g_VX1X_repeatXrepeatXunionXout_uses_out_traversesXX_whereXloops_isX0X_timesX1X_timeX2X_name: [function({g, vid1}) { return g.V(vid1).repeat(__.repeat(__.union(__.out("uses"),__.out("traverses")).where(__.loops().is(0))).times(1)).times(2).values("name") }],
g_V_repeatXa_outXknows_repeatXb_outXcreatedX_filterXloops_isX0XX_emit_lang: [function({g}) { return g.V().repeat("a",__.out("knows").repeat("b",__.out("created").filter(__.loops("a").is(0))).emit()).emit().values("lang") }],
g_VX6X_repeatXa_bothXcreatedX_simplePathX_emitXrepeatXb_bothXknowsXX_untilXloopsXbX_asXb_whereXloopsXaX_asXbX_hasXname_vadasXX_dedup_name: [function({g, vid6}) { return g.V(vid6).repeat("a",__.both("created").simplePath()).emit(__.repeat("b",__.both("knows")).until(__.loops("b").as("b").where(__.loops("a").as("b"))).has("name","vadas")).dedup().values("name") }],
+ g_unionXX: [function({g}) { return g.union() }],
+ g_unionXconstantX1X_constantX2X_constantX3XX: [function({g, xx1, xx3, xx2}) { return g.union(__.constant(xx1),__.constant(xx2),__.constant(xx3)) }],
+ g_unionXV_name: [function({g}) { return g.union(__.V().values("name")) }],
+ g_unionXVXv1X_VX4XX_name: [function({g, v4, v1}) { return g.union(__.V(v1),__.V(v4)).values("name") }],
+ g_unionXV_hasLabelXsoftwareX_V_hasLabelXpersonXX_name: [function({g}) { return g.union(__.V().hasLabel("software"),__.V().hasLabel("person")).values("name") }],
g_V_unionXout__inX_name: [function({g}) { return g.V().union(__.out(),__.in_()).values("name") }],
g_VX1X_unionXrepeatXoutX_timesX2X__outX_name: [function({g, vid1}) { return g.V(vid1).union(__.repeat(__.out()).times(2),__.out()).values("name") }],
g_V_chooseXlabel_is_person__unionX__out_lang__out_nameX__in_labelX: [function({g}) { return g.V().choose(__.label().is("person"),__.union(__.out().values("lang"),__.out().values("name")),__.in_().label()) }],
diff --git a/gremlin-language/src/main/antlr4/Gremlin.g4 b/gremlin-language/src/main/antlr4/Gremlin.g4
index bc41b628dd..6f2f9414dc 100644
--- a/gremlin-language/src/main/antlr4/Gremlin.g4
+++ b/gremlin-language/src/main/antlr4/Gremlin.g4
@@ -102,6 +102,7 @@ traversalSourceSpawnMethod
| traversalSourceSpawnMethod_inject
| traversalSourceSpawnMethod_io
| traversalSourceSpawnMethod_call
+ | traversalSourceSpawnMethod_union
;
traversalSourceSpawnMethod_addE
@@ -149,6 +150,10 @@ traversalSourceSpawnMethod_call
| 'call' LPAREN stringBasedLiteral COMMA genericLiteralMap COMMA nestedTraversal RPAREN #traversalSourceSpawnMethod_call_string_map_traversal
;
+traversalSourceSpawnMethod_union
+ : 'union' LPAREN nestedTraversalList RPAREN
+ ;
+
chainedTraversal
: traversalMethod
| chainedTraversal DOT traversalMethod
diff --git a/gremlin-python/src/main/python/gremlin_python/process/graph_traversal.py b/gremlin-python/src/main/python/gremlin_python/process/graph_traversal.py
index 3decfb2f73..ed4e0ca4c2 100644
--- a/gremlin-python/src/main/python/gremlin_python/process/graph_traversal.py
+++ b/gremlin-python/src/main/python/gremlin_python/process/graph_traversal.py
@@ -243,6 +243,11 @@ class GraphTraversalSource(object):
traversal.bytecode.add_step("call", *args)
return traversal
+ def union(self, *args):
+ traversal = self.get_graph_traversal()
+ traversal.bytecode.add_step("union", *args)
+ return traversal
+
class GraphTraversal(Traversal):
def __init__(self, graph, traversal_strategies, bytecode):
diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/branch/Union.feature b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/branch/Union.feature
index 0f60240371..5d08599568 100644
--- a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/branch/Union.feature
+++ b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/branch/Union.feature
@@ -18,6 +18,77 @@
@StepClassBranch @StepUnion
Feature: Step - union()
+ Scenario: g_unionXX
+ Given the modern graph
+ And the traversal of
+ """
+ g.union()
+ """
+ When iterated to list
+ Then the result should be empty
+
+ Scenario: g_unionXconstantX1X_constantX2X_constantX3XX
+ Given the modern graph
+ And using the parameter xx1 defined as "d[1].i"
+ And using the parameter xx2 defined as "d[2].i"
+ And using the parameter xx3 defined as "d[3].i"
+ And the traversal of
+ """
+ g.union(constant(xx1), constant(xx2), constant(xx3))
+ """
+ When iterated to list
+ Then the result should be unordered
+ | result |
+ | d[1].i |
+ | d[2].i |
+ | d[3].i |
+
+ Scenario: g_unionXV_name
+ Given the modern graph
+ And the traversal of
+ """
+ g.union(__.V().values("name"))
+ """
+ When iterated to list
+ Then the result should be unordered
+ | result |
+ | marko |
+ | vadas |
+ | lop |
+ | josh |
+ | ripple |
+ | peter |
+
+ Scenario: g_unionXVXv1X_VX4XX_name
+ Given the modern graph
+ And using the parameter v1 defined as "v[vadas]"
+ And using the parameter v4 defined as "v[josh]"
+ And the traversal of
+ """
+ g.union(__.V(v1), __.V(v4)).values("name")
+ """
+ When iterated to list
+ Then the result should be unordered
+ | result |
+ | vadas |
+ | josh |
+
+ Scenario: g_unionXV_hasLabelXsoftwareX_V_hasLabelXpersonXX_name
+ Given the modern graph
+ And the traversal of
+ """
+ g.union(__.V().hasLabel("software"), __.V().hasLabel("person")).values("name")
+ """
+ When iterated to list
+ Then the result should be unordered
+ | result |
+ | marko |
+ | vadas |
+ | lop |
+ | josh |
+ | ripple |
+ | peter |
+
Scenario: g_V_unionXout__inX_name
Given the modern graph
And the traversal of