TINKERPOP-2873 Added union() as a start step
diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc
index 8cea5be..43e1464 100644
--- a/CHANGELOG.asciidoc
+++ b/CHANGELOG.asciidoc
@@ -28,6 +28,7 @@
* 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 99cbf29..5ab4972 100644
--- a/docs/src/reference/the-traversal.asciidoc
+++ b/docs/src/reference/the-traversal.asciidoc
@@ -4277,6 +4277,8 @@
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 5ba1594..270afe9 100644
--- a/docs/src/upgrade/release-3.7.x.asciidoc
+++ b/docs/src/upgrade/release-3.7.x.asciidoc
@@ -29,6 +29,37 @@
=== 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]
+----
+gremlin> g.union(V().has('name','vadas'),
+......1> V().has('software','name','lop').in('created')).
+......2> values('name')
+==>vadas
+==>marko
+==>josh
+==>peter
+----
+
+See: link:https://issues.apache.org/jira/browse/TINKERPOP-2873[TINKERPOP-2873]
+
==== Properties on Elements
===== Introduction
@@ -43,7 +74,7 @@
[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 +83,24 @@
[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 +127,6 @@
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 4531861..798578d 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 @@
* {@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 3d3ca4f..0e52b60 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 @@
(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 5d1ebaa..de890a0 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 @@
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 @@
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 c12bf41..9dfd749 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.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.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;
@@ -555,6 +557,21 @@
}
/**
+ * 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
* and must be accompanied by a {@link GraphTraversal#read()} or {@link GraphTraversal#write()} modulator step
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 1ce6137..9606082 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)
@@ -46,6 +69,12 @@
}
@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-dotnet/src/Gremlin.Net/Process/Traversal/GraphTraversalSource.cs b/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/GraphTraversalSource.cs
index 218fb7b..675c1ad 100644
--- a/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/GraphTraversalSource.cs
+++ b/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/GraphTraversalSource.cs
@@ -523,6 +523,19 @@
return traversal;
}
+ /// <summary>
+ /// Spawns a <see cref="GraphTraversal{SType, EType}" /> off this graph traversal source and adds the union step to that
+ /// traversal.
+ /// </summary>
+ public GraphTraversal<TStart, TStart> Union<TStart>(params ITraversal[] unionTraversals)
+ {
+ var traversal = new GraphTraversal<TStart, TStart>(TraversalStrategies, new Bytecode(Bytecode));
+ var args = new List<object>(unionTraversals.Length);
+ args.AddRange(unionTraversals);
+ traversal.Bytecode.AddStep("union", args.ToArray());
+ return traversal;
+ }
+
}
#pragma warning restore 1591
diff --git a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs
index 290b2fe..8d1b3c5 100644
--- a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs
+++ b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs
@@ -116,6 +116,12 @@
{"g_VX1X_repeatXrepeatXunionXout_uses_out_traversesXX_whereXloops_isX0X_timesX1X_timeX2X_name", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V(p["vid1"]).Repeat(__.Repeat(__.Union<object>(__.Out("uses"),__.Out("traverses")).Where(__.Loops().Is(0))).Times(1)).Times(2).Values<object>("name")}},
{"g_V_repeatXa_outXknows_repeatXb_outXcreatedX_filterXloops_isX0XX_emit_lang", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Repeat("a",__.Out("knows").Repeat("b",__.Out("created").Filter(__.Loops("a").Is(0))).Emit()).Emit().Values<object>("lang")}},
{"g_VX6X_repeatXa_bothXcreatedX_simplePathX_emitXrepeatXb_bothXknowsXX_untilXloopsXbX_asXb_whereXloopsXaX_asXbX_hasXname_vadasXX_dedup_name", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V(p["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<object>("name")}},
+ {"g_unionXX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Union<object>()}},
+ {"g_unionXconstantX1X_constantX2X_constantX3XX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Union<object>(__.Constant<object>(p["xx1"]),__.Constant<object>(p["xx2"]),__.Constant<object>(p["xx3"]))}},
+ {"g_unionXV_name", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Union<object>(__.V().Values<object>("name"))}},
+ {"g_unionXVXv1X_VX4XX_name", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Union<object>(__.V((Vertex) p["v1"]),__.V((Vertex) p["v4"])).Values<object>("name")}},
+ {"g_unionXV_hasLabelXsoftwareX_V_hasLabelXpersonXX_name", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Union<object>(__.V().HasLabel("software"),__.V().HasLabel("person")).Values<object>("name")}},
+ {"g_V_unionXconstantX1X_constantX2X_constantX3XX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V((Vertex) p["v2"]).Union<object>(__.Constant<object>(p["xx1"]),__.Constant<object>(p["xx2"]),__.Constant<object>(p["xx3"]))}},
{"g_V_unionXout__inX_name", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Union<object>(__.Out(),__.In()).Values<object>("name")}},
{"g_VX1X_unionXrepeatXoutX_timesX2X__outX_name", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V(p["vid1"]).Union<object>(__.Repeat(__.Out()).Times(2),__.Out()).Values<object>("name")}},
{"g_V_chooseXlabel_is_person__unionX__out_lang__out_nameX__in_labelX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Choose<object>(__.Label().Is("person"),__.Union<object>(__.Out().Values<object>("lang"),__.Out().Values<object>("name")),__.In().Label())}},
diff --git a/gremlin-go/driver/cucumber/gremlin.go b/gremlin-go/driver/cucumber/gremlin.go
index bfa60c2..c1a0844 100644
--- a/gremlin-go/driver/cucumber/gremlin.go
+++ b/gremlin-go/driver/cucumber/gremlin.go
@@ -86,6 +86,12 @@
"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().Values("name")}},
+ "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_unionXconstantX1X_constantX2X_constantX3XX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V(p["v2"]).Union(gremlingo.T__.Constant(p["xx1"]), gremlingo.T__.Constant(p["xx2"]), gremlingo.T__.Constant(p["xx3"]))}},
"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 b8f8660..31ee2d4 100644
--- a/gremlin-go/driver/graphTraversalSource.go
+++ b/gremlin-go/driver/graphTraversalSource.go
@@ -215,6 +215,13 @@
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 60b03cc..328c7f3 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 @@
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 0f05259..402fd99 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,12 @@
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_unionXconstantX1X_constantX2X_constantX3XX: [function({g, xx1, xx3, xx2, v2}) { return g.V(v2).union(__.constant(xx1),__.constant(xx2),__.constant(xx3)) }],
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 bc41b62..6f2f941 100644
--- a/gremlin-language/src/main/antlr4/Gremlin.g4
+++ b/gremlin-language/src/main/antlr4/Gremlin.g4
@@ -102,6 +102,7 @@
| traversalSourceSpawnMethod_inject
| traversalSourceSpawnMethod_io
| traversalSourceSpawnMethod_call
+ | traversalSourceSpawnMethod_union
;
traversalSourceSpawnMethod_addE
@@ -149,6 +150,10 @@
| '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 3decfb2..ed4e0ca 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 @@
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 0f60240..c5a99a1 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,99 @@
@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
+
+ # this use of union() is a bit like inject() so just gonna use that tag to ignore
+ @GraphComputerVerificationInjectionNotSupported
+ 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 |
+
+ @GraphComputerVerificationMidVNotSupported
+ 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 |
+
+ @GraphComputerVerificationMidVNotSupported
+ 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 |
+
+ @GraphComputerVerificationMidVNotSupported
+ 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_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 using the parameter v2 defined as "v[vadas]"
+ And the traversal of
+ """
+ g.V(v2).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_V_unionXout__inX_name
Given the modern graph
And the traversal of
diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/sideEffect/Inject.feature b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/sideEffect/Inject.feature
index 75edc15..668ba57 100644
--- a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/sideEffect/Inject.feature
+++ b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/sideEffect/Inject.feature
@@ -18,6 +18,7 @@
@StepClassSideEffect @StepInject
Feature: Step - inject()
+ @GraphComputerVerificationInjectionNotSupported
Scenario: g_VX1X_out_injectXv2X_name
Given the modern graph
And using the parameter vid1 defined as "v[marko].id"
@@ -34,7 +35,7 @@
| vadas |
| josh |
-
+ @GraphComputerVerificationInjectionNotSupported
Scenario: g_VX1X_out_name_injectXdanielX_asXaX_mapXlengthX_path
Given the modern graph
And using the parameter vid1 defined as "v[marko].id"
@@ -69,8 +70,9 @@
| vadas |
| josh |
+ @GraphComputerVerificationInjectionNotSupported
Scenario: g_injectXnull_1_3_nullX
- Given the empty graph
+ Given the modern graph
And the traversal of
"""
g.inject(null, 1, 3, null)
@@ -83,8 +85,9 @@
| d[3].i |
| null |
+ @GraphComputerVerificationInjectionNotSupported
Scenario: g_injectX10_20_null_20_10_10X_groupCountXxX_dedup_asXyX_projectXa_bX_by_byXselectXxX_selectXselectXyXXX
- Given the empty graph
+ Given the modern graph
And the traversal of
"""
g.inject(10,20,null,20,10,10).groupCount("x").dedup().as("y").project("a","b").by().by(__.select("x").select(__.select("y")))
@@ -96,8 +99,9 @@
| m[{"a":"d[20].i", "b":"d[2].l"}] |
| m[{"a":null, "b":"d[1].l"}] |
+ @GraphComputerVerificationInjectionNotSupported
Scenario: g_injectXname_marko_age_nullX_selectXname_ageX
- Given the empty graph
+ Given the modern graph
And using the parameter xx1 defined as "m[{\"name\":\"marko\", \"age\":null}]"
And the traversal of
"""
@@ -108,8 +112,9 @@
| result |
| m[{"name":"marko", "age":null}] |
+ @GraphComputerVerificationInjectionNotSupported
Scenario: g_injectXnull_nullX
- Given the empty graph
+ Given the modern graph
And the traversal of
"""
g.inject(null, null)
@@ -120,8 +125,9 @@
| null |
| null |
+ @GraphComputerVerificationInjectionNotSupported
Scenario: g_injectXnullX
- Given the empty graph
+ Given the modern graph
And the traversal of
"""
g.inject(null)
@@ -131,8 +137,9 @@
| result |
| null |
+ @GraphComputerVerificationInjectionNotSupported
Scenario: g_inject
- Given the empty graph
+ Given the modern graph
And the traversal of
"""
g.inject()
@@ -140,6 +147,7 @@
When iterated to list
Then the result should be empty
+ @GraphComputerVerificationInjectionNotSupported
Scenario: g_VX1X_valuesXageX_injectXnull_nullX
Given the modern graph
And using the parameter xx1 defined as "v[marko].id"
@@ -181,8 +189,9 @@
| result |
| d[29].i |
+ @GraphComputerVerificationInjectionNotSupported
Scenario: g_injectXnull_1_3_nullX_asXaX_selectXaX
- Given the empty graph
+ Given the modern graph
And the traversal of
"""
g.inject(null, 1, 3, null).as("a").select("a")