This is an automated email from the ASF dual-hosted git repository.
Cole-Greer pushed a commit to branch 3.7-dev
in repository https://gitbox.apache.org/repos/asf/tinkerpop.git
The following commit(s) were added to refs/heads/3.7-dev by this push:
new e6f5181a93 TINKERPOP-3059: Prevent repeated option() calls to merge
steps (#3714)
e6f5181a93 is described below
commit e6f5181a9316fe542dbf57fc6e83c386116dc600
Author: Iryna-Kahamlyk <[email protected]>
AuthorDate: Wed Oct 21 22:43:44 2026 -0700
TINKERPOP-3059: Prevent repeated option() calls to merge steps (#3714)
Prevented repeated option(onCreate), option(onMatch), option(outV) and
option(inV) calls on mergeV() and mergeE(), which now throw an
IllegalArgumentException instead of silently keeping only the last one.
---
CHANGELOG.asciidoc | 2 +
docs/src/dev/provider/gremlin-semantics.asciidoc | 4 ++
.../tinkerpop/gremlin/jsr223/JavaTranslator.java | 14 +++++--
.../process/traversal/step/map/MergeEdgeStep.java | 4 ++
.../process/traversal/step/map/MergeStep.java | 4 ++
.../traversal/step/map/MergeEdgeStepTest.java | 30 ++++++++++++++
.../traversal/step/map/MergeVertexStepTest.java | 14 +++++++
gremlin-dotnet/build/generate.groovy | 9 +++-
.../Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs | 6 +++
gremlin-go/build/generate.groovy | 8 +++-
gremlin-go/driver/cucumber/gremlin.go | 6 +++
gremlin-go/driver/traversal.go | 4 ++
gremlin-javascript/build/generate.groovy | 9 +++-
.../gremlin-javascript/test/cucumber/gremlin.js | 6 +++
gremlin-python/build/generate.groovy | 9 +++-
.../src/main/python/tests/feature/gremlin.py | 6 +++
.../tinkerpop/gremlin/features/StepDefinition.java | 8 ++++
.../gremlin/test/features/map/MergeEdge.feature | 48 +++++++++++++++++++++-
.../gremlin/test/features/map/MergeVertex.feature | 26 +++++++++++-
19 files changed, 208 insertions(+), 9 deletions(-)
diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc
index 1e78f3ae03..1cd3893a08 100644
--- a/CHANGELOG.asciidoc
+++ b/CHANGELOG.asciidoc
@@ -29,6 +29,8 @@
image::https://raw.githubusercontent.com/apache/tinkerpop/master/docs/static/ima
* Fixed `gremlin-python` GraphBinary serialization of `int` values outside the
Java `int` range, which raised a `struct.error` rather than writing a `Long`.
* Fixed bug in `TinkerTransactionGraph` where an indexed lookup missed a
vertex or edge that had been modified in the same transaction, such as by
adding an edge to a vertex or changing another property.
* Fixed `TinkerTransactionGraph` rollback, which threw a
`NullPointerException` when only a vertex index existed and left rolled-back
entries visible to an indexed edge lookup when only an edge index existed.
+* Prevented repeated `option(onCreate)`, `option(onMatch)`, `option(outV)` and
`option(inV)` calls on `mergeV()` and `mergeE()`, which now throw an
`IllegalArgumentException` instead of silently keeping only the last one.
+* Fixed the Gherkin feature test harness to assert errors raised during
traversal construction.
[[release-3-7-7]]
=== TinkerPop 3.7.7 (Release Date: September 1, 2026)
diff --git a/docs/src/dev/provider/gremlin-semantics.asciidoc
b/docs/src/dev/provider/gremlin-semantics.asciidoc
index 0ce7e138b0..6143ef221c 100644
--- a/docs/src/dev/provider/gremlin-semantics.asciidoc
+++ b/docs/src/dev/provider/gremlin-semantics.asciidoc
@@ -1302,6 +1302,8 @@ with `mergeV` via a `select` operation.
* `Map` arguments are validated for their keys resulting in exception if they
do not meet requirements defined above.
* Use of `T.label` should always have a value that is a `String`.
* If `T.id`, `T.label`, and/or `Direction.IN/OUT` are specified in
`searchCreate`, they cannot be overridden in `onCreate`.
+* Each of `onCreate`, `onMatch`, `outV` and `inV` may be specified with
`option()` at most once. A repeated option
+results in exception at traversal construction rather than the later value
silently replacing the earlier one.
* For late binding of the from and to vertices, `Direction.OUT` must be set to
`Merge.outV` and `Direction.IN` must
be set to `Merge.inV`. Other combinations are not allowed and will result in
exception.
@@ -1372,6 +1374,8 @@ resolve to a `Map`.
* `Map` arguments are validated for their keys resulting in exception if they
do not meet requirements defined above.
* Use of `T.label` should always have a value that is a `String`.
* If `T.id` and/or `T.label` are specified in `searchCreate`, they cannot be
overridden in `onCreate`.
+* Each of `onCreate` and `onMatch` may be specified with `option()` at most
once. A repeated option results in
+exception at traversal construction rather than the later value silently
replacing the earlier one.
*Considerations:*
diff --git
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/JavaTranslator.java
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/JavaTranslator.java
index caf820c171..a275fa1c93 100644
---
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/JavaTranslator.java
+++
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/JavaTranslator.java
@@ -242,6 +242,7 @@ public final class JavaTranslator<S extends
TraversalSource, T extends Traversal
"Could not locate method", delegate, methodName,
argumentsCopy));
}
+ boolean found = false;
try {
for (final ReflectedMethod methodx : methodCache.get(methodName)) {
final Method method = methodx.method;
@@ -249,7 +250,7 @@ public final class JavaTranslator<S extends
TraversalSource, T extends Traversal
final Parameter[] parameters = methodx.parameters;
if (parameters.length == argumentsCopy.length ||
methodx.hasVarArgs) {
final Object[] newArguments = new
Object[parameters.length];
- boolean found = true;
+ found = true;
for (int i = 0; i < parameters.length; i++) {
if (parameters[i].isVarArgs()) {
final Class<?> parameterClass =
parameters[i].getType().getComponentType();
@@ -324,8 +325,15 @@ public final class JavaTranslator<S extends
TraversalSource, T extends Traversal
}
}
} catch (final Throwable e) {
- throw new IllegalStateException(generateMethodNotFoundMessage(
- e.getMessage(), null, methodName, argumentsCopy), e);
+ if (found && (e instanceof InvocationTargetException)) {
+ // get the target exception out for a better message,
otherwise, it will be null.
+ final InvocationTargetException ite =
(InvocationTargetException) e;
+ throw new IllegalStateException(generateMethodNotFoundMessage(
+ ite.getTargetException().getMessage(), null,
methodName, argumentsCopy), ite.getTargetException());
+ } else {
+ throw new IllegalStateException(generateMethodNotFoundMessage(
+ e.getMessage(), null, methodName, argumentsCopy), e);
+ }
}
// if it got down here then the method was in the cache but it was
never called as it could not be found
diff --git
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MergeEdgeStep.java
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MergeEdgeStep.java
index 36fa768974..8c20b53575 100644
---
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MergeEdgeStep.java
+++
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MergeEdgeStep.java
@@ -94,8 +94,12 @@ public class MergeEdgeStep<S> extends MergeStep<S, Edge,
Object> {
@Override
public void addChildOption(final Merge token, final Traversal.Admin<S,
Object> traversalOption) {
if (token == Merge.outV) {
+ if (outVTraversal != null)
+ throw new IllegalArgumentException("option(outV) can only be
specified once per mergeE step");
this.outVTraversal = this.integrateChild(traversalOption);
} else if (token == Merge.inV) {
+ if (inVTraversal != null)
+ throw new IllegalArgumentException("option(inV) can only be
specified once per mergeE step");
this.inVTraversal = this.integrateChild(traversalOption);
} else {
super.addChildOption(token, traversalOption);
diff --git
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MergeStep.java
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MergeStep.java
index bd8f34caea..5e5b10f8e4 100644
---
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MergeStep.java
+++
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MergeStep.java
@@ -141,10 +141,14 @@ public abstract class MergeStep<S, E, C> extends
FlatMapStep<S, E>
@Override
public void addChildOption(final Merge token, final Traversal.Admin<S, C>
traversalOption) {
if (token == Merge.onCreate) {
+ if (onCreateTraversal != null)
+ throw new IllegalArgumentException("option(onCreate) can only
be specified once per merge step");
// early swipe at validation if both search and create arguments
are static
validateStaticNoOverrides(mergeTraversal, traversalOption);
this.onCreateTraversal = this.integrateChild(traversalOption);
} else if (token == Merge.onMatch) {
+ if (onMatchTraversal != null)
+ throw new IllegalArgumentException("option(onMatch) can only
be specified once per merge step");
// add a guard rail to ensure that the incoming object is not an
Element. this will prevent
// a possibly inadvertent mutation of the graph if you did
something like g.V().mergeE(). for
// 3.x we won't allow this behavior at all but in 4.x we will make
it consistent like it will
diff --git
a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MergeEdgeStepTest.java
b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MergeEdgeStepTest.java
index 7ac2c01597..14c2b5fc85 100644
---
a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MergeEdgeStepTest.java
+++
b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MergeEdgeStepTest.java
@@ -23,7 +23,9 @@ import
org.apache.tinkerpop.gremlin.process.traversal.Traversal;
import org.apache.tinkerpop.gremlin.process.traversal.TraversalSideEffects;
import org.apache.tinkerpop.gremlin.process.traversal.TraversalStrategies;
import org.apache.tinkerpop.gremlin.process.traversal.Traverser;
+import org.apache.tinkerpop.gremlin.process.traversal.lambda.ConstantTraversal;
import org.apache.tinkerpop.gremlin.process.traversal.step.util.EmptyStep;
+import org.apache.tinkerpop.gremlin.process.traversal.util.EmptyTraversal;
import
org.apache.tinkerpop.gremlin.process.traversal.util.DefaultTraversalStrategies;
import org.apache.tinkerpop.gremlin.structure.Direction;
import org.apache.tinkerpop.gremlin.structure.T;
@@ -135,4 +137,32 @@ public class MergeEdgeStepTest {
assertEquals(CollectionUtil.asMap("key1", "value1", "key2", "value2"),
onCreateMap);
}
+
+ @Test(expected = IllegalArgumentException.class)
+ public void shouldNotAllowRepeatedOnCreate() {
+ final MergeEdgeStep<Object> step = new
MergeEdgeStep<>(EmptyTraversal.instance(), true);
+ step.addChildOption(Merge.onCreate, new
ConstantTraversal<>(Collections.emptyMap()));
+ step.addChildOption(Merge.onCreate, new
ConstantTraversal<>(Collections.emptyMap()));
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void shouldNotAllowRepeatedOnMatch() {
+ final MergeEdgeStep<Object> step = new
MergeEdgeStep<>(EmptyTraversal.instance(), true);
+ step.addChildOption(Merge.onMatch, new
ConstantTraversal<>(Collections.emptyMap()));
+ step.addChildOption(Merge.onMatch, new
ConstantTraversal<>(Collections.emptyMap()));
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void shouldNotAllowRepeatedOutV() {
+ final MergeEdgeStep<Object> step = new
MergeEdgeStep<>(EmptyTraversal.instance(), true);
+ step.addChildOption(Merge.outV, new
ConstantTraversal<>(Collections.emptyMap()));
+ step.addChildOption(Merge.outV, new
ConstantTraversal<>(Collections.emptyMap()));
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void shouldNotAllowRepeatedInV() {
+ final MergeEdgeStep<Object> step = new
MergeEdgeStep<>(EmptyTraversal.instance(), true);
+ step.addChildOption(Merge.inV, new
ConstantTraversal<>(Collections.emptyMap()));
+ step.addChildOption(Merge.inV, new
ConstantTraversal<>(Collections.emptyMap()));
+ }
}
diff --git
a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MergeVertexStepTest.java
b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MergeVertexStepTest.java
index 58da0688e2..4b0c93b88e 100644
---
a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MergeVertexStepTest.java
+++
b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MergeVertexStepTest.java
@@ -171,4 +171,18 @@ public class MergeVertexStepTest {
T.label, "~person");
MergeVertexStep.validateMapInput(m, false);
}
+
+ @Test(expected = IllegalArgumentException.class)
+ public void shouldNotAllowRepeatedOnCreate() {
+ final MergeVertexStep<Object> step = new
MergeVertexStep<>(org.apache.tinkerpop.gremlin.process.traversal.util.EmptyTraversal.instance(),
true);
+ step.addChildOption(Merge.onCreate, new
org.apache.tinkerpop.gremlin.process.traversal.lambda.ConstantTraversal<>(Collections.emptyMap()));
+ step.addChildOption(Merge.onCreate, new
org.apache.tinkerpop.gremlin.process.traversal.lambda.ConstantTraversal<>(Collections.emptyMap()));
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void shouldNotAllowRepeatedOnMatch() {
+ final MergeVertexStep<Object> step = new
MergeVertexStep<>(org.apache.tinkerpop.gremlin.process.traversal.util.EmptyTraversal.instance(),
true);
+ step.addChildOption(Merge.onMatch, new
org.apache.tinkerpop.gremlin.process.traversal.lambda.ConstantTraversal<>(Collections.emptyMap()));
+ step.addChildOption(Merge.onMatch, new
org.apache.tinkerpop.gremlin.process.traversal.lambda.ConstantTraversal<>(Collections.emptyMap()));
+ }
}
diff --git a/gremlin-dotnet/build/generate.groovy
b/gremlin-dotnet/build/generate.groovy
index b01dfeb5a0..f920a483c2 100644
--- a/gremlin-dotnet/build/generate.groovy
+++ b/gremlin-dotnet/build/generate.groovy
@@ -127,7 +127,14 @@ radishGremlinFile.withWriter('UTF-8') { Writer writer ->
// Groovy can't process certain null oriented calls because it gets
confused with the right overload to call
// at runtime. using this approach for now as these are the only such
situations encountered so far. a better
// solution may become necessary as testing of nulls expands.
- def staticTranslate = [:]
+ def staticTranslate = [
+ g_mergeVXxx1X_optionXonCreate_xx2X_optionXonCreate_xx2X_repeated:
"
{\"g_mergeVXxx1X_optionXonCreate_xx2X_optionXonCreate_xx2X_repeated\", new
List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>>
{(g,p) =>g.MergeV((IDictionary<object,object>)
p[\"xx1\"]).Option(Merge.OnCreate, (IDictionary<object,object>)
p[\"xx2\"]).Option(Merge.OnCreate, (IDictionary<object,object>) p[\"xx2\"])}},
",
+ g_mergeVXxx1X_optionXonMatch_xx2X_optionXonMatch_xx2X_repeated: "
{\"g_mergeVXxx1X_optionXonMatch_xx2X_optionXonMatch_xx2X_repeated\", new
List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>>
{(g,p) =>g.MergeV((IDictionary<object,object>)
p[\"xx1\"]).Option(Merge.OnMatch, (IDictionary<object,object>)
p[\"xx2\"]).Option(Merge.OnMatch, (IDictionary<object,object>) p[\"xx2\"])}}, ",
+ g_mergeEXxx1X_optionXonCreate_xx2X_optionXonCreate_xx2X_repeated:
"
{\"g_mergeEXxx1X_optionXonCreate_xx2X_optionXonCreate_xx2X_repeated\", new
List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>>
{(g,p) =>g.MergeE((IDictionary<object,object>)
p[\"xx1\"]).Option(Merge.OnCreate, (IDictionary<object,object>)
p[\"xx2\"]).Option(Merge.OnCreate, (IDictionary<object,object>) p[\"xx2\"])}},
",
+ g_mergeEXxx1X_optionXonMatch_xx2X_optionXonMatch_xx2X_repeated: "
{\"g_mergeEXxx1X_optionXonMatch_xx2X_optionXonMatch_xx2X_repeated\", new
List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>>
{(g,p) =>g.MergeE((IDictionary<object,object>)
p[\"xx1\"]).Option(Merge.OnMatch, (IDictionary<object,object>)
p[\"xx2\"]).Option(Merge.OnMatch, (IDictionary<object,object>) p[\"xx2\"])}}, ",
+
g_mergeEXxx1X_optionXoutV_selectXvXX_optionXoutV_selectXvXX_repeated: "
{\"g_mergeEXxx1X_optionXoutV_selectXvXX_optionXoutV_selectXvXX_repeated\", new
List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>>
{(g,p) =>g.MergeE((IDictionary<object,object>) p[\"xx1\"]).Option(Merge.OutV,
(ITraversal) __.Select<object>(\"v\")).Option(Merge.OutV, (ITraversal)
__.Select<object>(\"v\"))}}, ",
+
g_mergeEXxx1X_optionXinV_selectXvXX_optionXinV_selectXvXX_repeated: "
{\"g_mergeEXxx1X_optionXinV_selectXvXX_optionXinV_selectXvXX_repeated\",
new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>>
{(g,p) =>g.MergeE((IDictionary<object,object>) p[\"xx1\"]).Option(Merge.InV,
(ITraversal) __.Select<object>(\"v\")).Option(Merge.InV, (ITraversal)
__.Select<object>(\"v\"))}}, "
+ ]
// SAMPLE: g_injectXnull_nullX: " {\"g_injectXnull_nullX\",
new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>>
{(g,p) =>g.Inject<object>(null,null)}},
",1\"]).Values<object>(\"age\").Inject(null,null)}}, "
gremlins.each { k,v ->
diff --git a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs
b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs
index 6a85f9ebb7..4c664f5698 100644
--- a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs
+++ b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs
@@ -937,6 +937,10 @@ namespace Gremlin.Net.IntegrationTest.Gherkin
{"g_injectXlist1_list2X_mergeEXlimitXlocal_1XX_optionXonCreate_rangeXlocal_1_2XX_optionXonMatch_tailXlocalXX_to_create",
new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>>
{(g,p)
=>g.AddV("person").Property("name","marko").Property("age",29).As("marko").AddV("person").Property("name","vadas").Property("age",27).As("vadas").AddV("software").Property("name","lop").Property("lang","java").As("lop").AddV("person").Property("name","josh").Property("ag
[...]
{"g_mergeEXlabel_knows_out_marko_in_vadasX_optionXonMatch_weight_nullX_allowed",
new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>>
{(g,p)
=>g.AddV("person").Property("name","marko").As("a").AddV("person").Property("name","vadas").As("b").AddE("knows").From("a").To("b").Property("weight",1.0),
(g,p) =>g.MergeE((IDictionary<object,object>) p["xx1"]).Option(Merge.OnMatch,
(IDictionary<object,object>) p["xx2"]), (g,p) =>g.V(), (g,p) =>g.E().HasLabel
[...]
{"g_mergeEXlabel_knows_out_marko_in_vadasX_optionXonMatch_weight_nullX", new
List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>>
{(g,p)
=>g.AddV("person").Property("name","marko").As("a").AddV("person").Property("name","vadas").As("b").AddE("knows").From("a").To("b").Property("weight",1.0),
(g,p) =>g.MergeE((IDictionary<object,object>) p["xx1"]).Option(Merge.OnMatch,
(IDictionary<object,object>) p["xx2"]), (g,p) =>g.V(), (g,p)
=>g.E().HasLabel("knows" [...]
+
{"g_mergeEXxx1X_optionXonCreate_xx2X_optionXonCreate_xx2X_repeated", new
List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>>
{(g,p) =>g.MergeE((IDictionary<object,object>) p["xx1"]).Option(Merge.OnCreate,
(IDictionary<object,object>) p["xx2"]).Option(Merge.OnCreate,
(IDictionary<object,object>) p["xx2"])}},
+
{"g_mergeEXxx1X_optionXonMatch_xx2X_optionXonMatch_xx2X_repeated", new
List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>>
{(g,p) =>g.MergeE((IDictionary<object,object>) p["xx1"]).Option(Merge.OnMatch,
(IDictionary<object,object>) p["xx2"]).Option(Merge.OnMatch,
(IDictionary<object,object>) p["xx2"])}},
+
{"g_mergeEXxx1X_optionXoutV_selectXvXX_optionXoutV_selectXvXX_repeated", new
List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>>
{(g,p) =>g.MergeE((IDictionary<object,object>) p["xx1"]).Option(Merge.OutV,
(ITraversal) __.Select<object>("v")).Option(Merge.OutV, (ITraversal)
__.Select<object>("v"))}},
+
{"g_mergeEXxx1X_optionXinV_selectXvXX_optionXinV_selectXvXX_repeated", new
List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>>
{(g,p) =>g.MergeE((IDictionary<object,object>) p["xx1"]).Option(Merge.InV,
(ITraversal) __.Select<object>("v")).Option(Merge.InV, (ITraversal)
__.Select<object>("v"))}},
{"g_mergeVXemptyX_optionXonMatch_nullX", new
List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>>
{(g,p) =>g.AddV("person").Property("name","marko").Property("age",29), (g,p)
=>g.MergeV((IDictionary<object,object>) new Dictionary<object,object>
{}).Option(Merge.OnMatch, (IDictionary<object,object>) null), (g,p)
=>g.V().Has("person","name","marko").Has("age",29)}},
{"g_V_mergeVXemptyX_optionXonMatch_nullX", new
List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>>
{(g,p) =>g.AddV("person").Property("name","marko").Property("age",29), (g,p)
=>g.V().MergeV((IDictionary<object,object>) new Dictionary<object,object>
{}).Option(Merge.OnMatch, (IDictionary<object,object>) null), (g,p)
=>g.V().Has("person","name","marko").Has("age",29)}},
{"g_mergeVXnullX_optionXonCreate_label_null_name_markoX", new
List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>>
{(g,p) =>g.AddV("person").Property("name","marko").Property("age",29), (g,p)
=>g.MergeV((IDictionary<object,object>) p["xx1"])}},
@@ -999,6 +1003,8 @@ namespace Gremlin.Net.IntegrationTest.Gherkin
{"g_injectXlist1_list2X_mergeVXlimitXlocal_1XX_optionXonCreate_rangeXlocal_1_2X_optionXonMatch_tailXlocalXX_to_create",
new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>>
{(g,p) =>g.AddV("person").Property("name","marko").Property("age",29), (g,p)
=>g.Inject(p["xx1"],p["xx1"],p["xx2"]).Fold().MergeV((ITraversal)
__.Limit<object>(Scope.Local,1)).Option(Merge.OnCreate, (ITraversal)
__.Range<object>(Scope.Local,1,2)).Option(Merge.OnMatch, (ITraversa [...]
{"g_mergeVXlabel_person_name_marko_age_29X_optionXonMatch_age_nullX_allowed",
new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>>
{(g,p) =>g.AddV("person").Property("name","marko").Property("age",29), (g,p)
=>g.MergeV((IDictionary<object,object>) p["xx1"]).Option(Merge.OnMatch,
(IDictionary<object,object>) p["xx2"]), (g,p) =>g.V(), (g,p)
=>g.V().Has("person","name","marko").Has("age",(object) null)}},
{"g_mergeVXlabel_person_name_marko_age_29X_optionXonMatch_age_nullX", new
List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>>
{(g,p) =>g.AddV("person").Property("name","marko").Property("age",29), (g,p)
=>g.MergeV((IDictionary<object,object>) p["xx1"]).Option(Merge.OnMatch,
(IDictionary<object,object>) p["xx2"]), (g,p) =>g.V(), (g,p)
=>g.V().Has("person","name","marko").Has("age")}},
+
{"g_mergeVXxx1X_optionXonCreate_xx2X_optionXonCreate_xx2X_repeated", new
List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>>
{(g,p) =>g.MergeV((IDictionary<object,object>) p["xx1"]).Option(Merge.OnCreate,
(IDictionary<object,object>) p["xx2"]).Option(Merge.OnCreate,
(IDictionary<object,object>) p["xx2"])}},
+
{"g_mergeVXxx1X_optionXonMatch_xx2X_optionXonMatch_xx2X_repeated", new
List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>>
{(g,p) =>g.MergeV((IDictionary<object,object>) p["xx1"]).Option(Merge.OnMatch,
(IDictionary<object,object>) p["xx2"]).Option(Merge.OnMatch,
(IDictionary<object,object>) p["xx2"])}},
{"g_V_age_min", new List<Func<GraphTraversalSource,
IDictionary<string, object>, ITraversal>> {(g,p)
=>g.V().Values<object>("age").Min<object>()}},
{"g_V_foo_min", new List<Func<GraphTraversalSource,
IDictionary<string, object>, ITraversal>> {(g,p)
=>g.V().Values<object>("foo").Min<object>()}},
{"g_V_name_min", new List<Func<GraphTraversalSource,
IDictionary<string, object>, ITraversal>> {(g,p)
=>g.V().Values<object>("name").Min<object>()}},
diff --git a/gremlin-go/build/generate.groovy b/gremlin-go/build/generate.groovy
index 32fb626089..27d7b5f124 100644
--- a/gremlin-go/build/generate.groovy
+++ b/gremlin-go/build/generate.groovy
@@ -109,7 +109,13 @@ radishGremlinFile.withWriter('UTF-8') { Writer writer ->
// solution may become necessary as testing of nulls expands.
def staticTranslate = [
g_injectXnull_nullX: " \"g_injectXnull_nullX\": {func(g
*gremlingo.GraphTraversalSource, p map[string]interface{})
*gremlingo.GraphTraversal {return g.Inject(nil, nil)}}, ",
- g_VX1X_valuesXageX_injectXnull_nullX: "
\"g_VX1X_valuesXageX_injectXnull_nullX\": {func(g
*gremlingo.GraphTraversalSource, p map[string]interface{})
*gremlingo.GraphTraversal {return g.V(p[\"xx1\"]).Values(\"age\").Inject(nil,
nil)}}, "
+ g_VX1X_valuesXageX_injectXnull_nullX: "
\"g_VX1X_valuesXageX_injectXnull_nullX\": {func(g
*gremlingo.GraphTraversalSource, p map[string]interface{})
*gremlingo.GraphTraversal {return g.V(p[\"xx1\"]).Values(\"age\").Inject(nil,
nil)}}, ",
+ g_mergeVXxx1X_optionXonCreate_xx2X_optionXonCreate_xx2X_repeated:
" \"g_mergeVXxx1X_optionXonCreate_xx2X_optionXonCreate_xx2X_repeated\":
{func(g *gremlingo.GraphTraversalSource, p map[string]interface{})
*gremlingo.GraphTraversal {return
g.MergeV(p[\"xx1\"]).Option(gremlingo.Merge.OnCreate,
p[\"xx2\"]).Option(gremlingo.Merge.OnCreate, p[\"xx2\"])}}, ",
+ g_mergeVXxx1X_optionXonMatch_xx2X_optionXonMatch_xx2X_repeated: "
\"g_mergeVXxx1X_optionXonMatch_xx2X_optionXonMatch_xx2X_repeated\": {func(g
*gremlingo.GraphTraversalSource, p map[string]interface{})
*gremlingo.GraphTraversal {return
g.MergeV(p[\"xx1\"]).Option(gremlingo.Merge.OnMatch,
p[\"xx2\"]).Option(gremlingo.Merge.OnMatch, p[\"xx2\"])}}, ",
+ g_mergeEXxx1X_optionXonCreate_xx2X_optionXonCreate_xx2X_repeated:
" \"g_mergeEXxx1X_optionXonCreate_xx2X_optionXonCreate_xx2X_repeated\":
{func(g *gremlingo.GraphTraversalSource, p map[string]interface{})
*gremlingo.GraphTraversal {return
g.MergeE(p[\"xx1\"]).Option(gremlingo.Merge.OnCreate,
p[\"xx2\"]).Option(gremlingo.Merge.OnCreate, p[\"xx2\"])}}, ",
+ g_mergeEXxx1X_optionXonMatch_xx2X_optionXonMatch_xx2X_repeated: "
\"g_mergeEXxx1X_optionXonMatch_xx2X_optionXonMatch_xx2X_repeated\": {func(g
*gremlingo.GraphTraversalSource, p map[string]interface{})
*gremlingo.GraphTraversal {return
g.MergeE(p[\"xx1\"]).Option(gremlingo.Merge.OnMatch,
p[\"xx2\"]).Option(gremlingo.Merge.OnMatch, p[\"xx2\"])}}, ",
+
g_mergeEXxx1X_optionXoutV_selectXvXX_optionXoutV_selectXvXX_repeated: "
\"g_mergeEXxx1X_optionXoutV_selectXvXX_optionXoutV_selectXvXX_repeated\":
{func(g *gremlingo.GraphTraversalSource, p map[string]interface{})
*gremlingo.GraphTraversal {return
g.MergeE(p[\"xx1\"]).Option(gremlingo.Merge.OutV,
gremlingo.T__.Select(\"v\")).Option(gremlingo.Merge.OutV,
gremlingo.T__.Select(\"v\"))}}, ",
+
g_mergeEXxx1X_optionXinV_selectXvXX_optionXinV_selectXvXX_repeated: "
\"g_mergeEXxx1X_optionXinV_selectXvXX_optionXinV_selectXvXX_repeated\": {func(g
*gremlingo.GraphTraversalSource, p map[string]interface{})
*gremlingo.GraphTraversal {return
g.MergeE(p[\"xx1\"]).Option(gremlingo.Merge.InV,
gremlingo.T__.Select(\"v\")).Option(gremlingo.Merge.InV,
gremlingo.T__.Select(\"v\"))}}, "
]
gremlins.each { k,v ->
diff --git a/gremlin-go/driver/cucumber/gremlin.go
b/gremlin-go/driver/cucumber/gremlin.go
index 321b7fc946..4b2a87bb77 100644
--- a/gremlin-go/driver/cucumber/gremlin.go
+++ b/gremlin-go/driver/cucumber/gremlin.go
@@ -908,6 +908,10 @@ var translationMap = map[string][]func(g
*gremlingo.GraphTraversalSource, p map[
"g_injectXlist1_list2X_mergeEXlimitXlocal_1XX_optionXonCreate_rangeXlocal_1_2XX_optionXonMatch_tailXlocalXX_to_create":
{func(g *gremlingo.GraphTraversalSource, p map[string]interface{})
*gremlingo.GraphTraversal {return g.AddV("person").Property("name",
"marko").Property("age", 29).As("marko").AddV("person").Property("name",
"vadas").Property("age", 27).As("vadas").AddV("software").Property("name",
"lop").Property("lang", "java").As("lop").AddV("person").Property("name",
"josh").Pro [...]
"g_mergeEXlabel_knows_out_marko_in_vadasX_optionXonMatch_weight_nullX_allowed":
{func(g *gremlingo.GraphTraversalSource, p map[string]interface{})
*gremlingo.GraphTraversal {return g.AddV("person").Property("name",
"marko").As("a").AddV("person").Property("name",
"vadas").As("b").AddE("knows").From("a").To("b").Property("weight", 1.0)},
func(g *gremlingo.GraphTraversalSource, p map[string]interface{})
*gremlingo.GraphTraversal {return
g.MergeE(p["xx1"]).Option(gremlingo.Merge.OnMatch [...]
"g_mergeEXlabel_knows_out_marko_in_vadasX_optionXonMatch_weight_nullX":
{func(g *gremlingo.GraphTraversalSource, p map[string]interface{})
*gremlingo.GraphTraversal {return g.AddV("person").Property("name",
"marko").As("a").AddV("person").Property("name",
"vadas").As("b").AddE("knows").From("a").To("b").Property("weight", 1.0)},
func(g *gremlingo.GraphTraversalSource, p map[string]interface{})
*gremlingo.GraphTraversal {return
g.MergeE(p["xx1"]).Option(gremlingo.Merge.OnMatch, p["xx2 [...]
+ "g_mergeEXxx1X_optionXonCreate_xx2X_optionXonCreate_xx2X_repeated":
{func(g *gremlingo.GraphTraversalSource, p map[string]interface{})
*gremlingo.GraphTraversal {return
g.MergeE(p["xx1"]).Option(gremlingo.Merge.OnCreate,
p["xx2"]).Option(gremlingo.Merge.OnCreate, p["xx2"])}},
+ "g_mergeEXxx1X_optionXonMatch_xx2X_optionXonMatch_xx2X_repeated": {func(g
*gremlingo.GraphTraversalSource, p map[string]interface{})
*gremlingo.GraphTraversal {return
g.MergeE(p["xx1"]).Option(gremlingo.Merge.OnMatch,
p["xx2"]).Option(gremlingo.Merge.OnMatch, p["xx2"])}},
+ "g_mergeEXxx1X_optionXoutV_selectXvXX_optionXoutV_selectXvXX_repeated":
{func(g *gremlingo.GraphTraversalSource, p map[string]interface{})
*gremlingo.GraphTraversal {return
g.MergeE(p["xx1"]).Option(gremlingo.Merge.OutV,
gremlingo.T__.Select("v")).Option(gremlingo.Merge.OutV,
gremlingo.T__.Select("v"))}},
+ "g_mergeEXxx1X_optionXinV_selectXvXX_optionXinV_selectXvXX_repeated":
{func(g *gremlingo.GraphTraversalSource, p map[string]interface{})
*gremlingo.GraphTraversal {return
g.MergeE(p["xx1"]).Option(gremlingo.Merge.InV,
gremlingo.T__.Select("v")).Option(gremlingo.Merge.InV,
gremlingo.T__.Select("v"))}},
"g_mergeVXemptyX_optionXonMatch_nullX": {func(g
*gremlingo.GraphTraversalSource, p map[string]interface{})
*gremlingo.GraphTraversal {return g.AddV("person").Property("name",
"marko").Property("age", 29)}, func(g *gremlingo.GraphTraversalSource, p
map[string]interface{}) *gremlingo.GraphTraversal {return
g.MergeV(map[interface{}]interface{}{}).Option(gremlingo.Merge.OnMatch, nil)},
func(g *gremlingo.GraphTraversalSource, p map[string]interface{})
*gremlingo.GraphTraversal {return g.V [...]
"g_V_mergeVXemptyX_optionXonMatch_nullX": {func(g
*gremlingo.GraphTraversalSource, p map[string]interface{})
*gremlingo.GraphTraversal {return g.AddV("person").Property("name",
"marko").Property("age", 29)}, func(g *gremlingo.GraphTraversalSource, p
map[string]interface{}) *gremlingo.GraphTraversal {return
g.V().MergeV(map[interface{}]interface{}{}).Option(gremlingo.Merge.OnMatch,
nil)}, func(g *gremlingo.GraphTraversalSource, p map[string]interface{})
*gremlingo.GraphTraversal {retu [...]
"g_mergeVXnullX_optionXonCreate_label_null_name_markoX": {func(g
*gremlingo.GraphTraversalSource, p map[string]interface{})
*gremlingo.GraphTraversal {return g.AddV("person").Property("name",
"marko").Property("age", 29)}, func(g *gremlingo.GraphTraversalSource, p
map[string]interface{}) *gremlingo.GraphTraversal {return g.MergeV(p["xx1"])}},
@@ -970,6 +974,8 @@ var translationMap = map[string][]func(g
*gremlingo.GraphTraversalSource, p map[
"g_injectXlist1_list2X_mergeVXlimitXlocal_1XX_optionXonCreate_rangeXlocal_1_2X_optionXonMatch_tailXlocalXX_to_create":
{func(g *gremlingo.GraphTraversalSource, p map[string]interface{})
*gremlingo.GraphTraversal {return g.AddV("person").Property("name",
"marko").Property("age", 29)}, func(g *gremlingo.GraphTraversalSource, p
map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(p["xx1"],
p["xx1"], p["xx2"]).Fold().MergeV(gremlingo.T__.Limit(gremlingo.Scope.Local,
1)).Opt [...]
"g_mergeVXlabel_person_name_marko_age_29X_optionXonMatch_age_nullX_allowed":
{func(g *gremlingo.GraphTraversalSource, p map[string]interface{})
*gremlingo.GraphTraversal {return g.AddV("person").Property("name",
"marko").Property("age", 29)}, func(g *gremlingo.GraphTraversalSource, p
map[string]interface{}) *gremlingo.GraphTraversal {return
g.MergeV(p["xx1"]).Option(gremlingo.Merge.OnMatch, p["xx2"])}, func(g
*gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.Graph
[...]
"g_mergeVXlabel_person_name_marko_age_29X_optionXonMatch_age_nullX":
{func(g *gremlingo.GraphTraversalSource, p map[string]interface{})
*gremlingo.GraphTraversal {return g.AddV("person").Property("name",
"marko").Property("age", 29)}, func(g *gremlingo.GraphTraversalSource, p
map[string]interface{}) *gremlingo.GraphTraversal {return
g.MergeV(p["xx1"]).Option(gremlingo.Merge.OnMatch, p["xx2"])}, func(g
*gremlingo.GraphTraversalSource, p map[string]interface{})
*gremlingo.GraphTraversa [...]
+ "g_mergeVXxx1X_optionXonCreate_xx2X_optionXonCreate_xx2X_repeated":
{func(g *gremlingo.GraphTraversalSource, p map[string]interface{})
*gremlingo.GraphTraversal {return
g.MergeV(p["xx1"]).Option(gremlingo.Merge.OnCreate,
p["xx2"]).Option(gremlingo.Merge.OnCreate, p["xx2"])}},
+ "g_mergeVXxx1X_optionXonMatch_xx2X_optionXonMatch_xx2X_repeated": {func(g
*gremlingo.GraphTraversalSource, p map[string]interface{})
*gremlingo.GraphTraversal {return
g.MergeV(p["xx1"]).Option(gremlingo.Merge.OnMatch,
p["xx2"]).Option(gremlingo.Merge.OnMatch, p["xx2"])}},
"g_V_age_min": {func(g *gremlingo.GraphTraversalSource, p
map[string]interface{}) *gremlingo.GraphTraversal {return
g.V().Values("age").Min()}},
"g_V_foo_min": {func(g *gremlingo.GraphTraversalSource, p
map[string]interface{}) *gremlingo.GraphTraversal {return
g.V().Values("foo").Min()}},
"g_V_name_min": {func(g *gremlingo.GraphTraversalSource, p
map[string]interface{}) *gremlingo.GraphTraversal {return
g.V().Values("name").Min()}},
diff --git a/gremlin-go/driver/traversal.go b/gremlin-go/driver/traversal.go
index 1a62c057de..c09a95ef70 100644
--- a/gremlin-go/driver/traversal.go
+++ b/gremlin-go/driver/traversal.go
@@ -111,6 +111,10 @@ func (t *Traversal) Next() (*Result, error) {
return nil, err
}
if results.IsEmpty() {
+ err = results.GetError()
+ if err != nil {
+ return nil, err
+ }
return nil, newError(err0903NextNoResultsLeftError)
}
result, _, err := results.One()
diff --git a/gremlin-javascript/build/generate.groovy
b/gremlin-javascript/build/generate.groovy
index 7281b941af..fb79f00f72 100644
--- a/gremlin-javascript/build/generate.groovy
+++ b/gremlin-javascript/build/generate.groovy
@@ -120,7 +120,14 @@ radishGremlinFile.withWriter('UTF-8') { Writer writer ->
// Groovy can't process certain null oriented calls because it gets
confused with the right overload to call
// at runtime. using this approach for now as these are the only such
situations encountered so far. a better
// solution may become necessary as testing of nulls expands.
- def staticTranslate = [:]
+ def staticTranslate = [
+ g_mergeVXxx1X_optionXonCreate_xx2X_optionXonCreate_xx2X_repeated:
" g_mergeVXxx1X_optionXonCreate_xx2X_optionXonCreate_xx2X_repeated:
[function({g, xx1, xx2}) { return
g.mergeV(xx1).option(Merge.onCreate,xx2).option(Merge.onCreate,xx2) }], ",
+ g_mergeVXxx1X_optionXonMatch_xx2X_optionXonMatch_xx2X_repeated: "
g_mergeVXxx1X_optionXonMatch_xx2X_optionXonMatch_xx2X_repeated: [function({g,
xx1, xx2}) { return
g.mergeV(xx1).option(Merge.onMatch,xx2).option(Merge.onMatch,xx2) }], ",
+ g_mergeEXxx1X_optionXonCreate_xx2X_optionXonCreate_xx2X_repeated:
" g_mergeEXxx1X_optionXonCreate_xx2X_optionXonCreate_xx2X_repeated:
[function({g, xx1, xx2}) { return
g.mergeE(xx1).option(Merge.onCreate,xx2).option(Merge.onCreate,xx2) }], ",
+ g_mergeEXxx1X_optionXonMatch_xx2X_optionXonMatch_xx2X_repeated: "
g_mergeEXxx1X_optionXonMatch_xx2X_optionXonMatch_xx2X_repeated: [function({g,
xx1, xx2}) { return
g.mergeE(xx1).option(Merge.onMatch,xx2).option(Merge.onMatch,xx2) }], ",
+
g_mergeEXxx1X_optionXoutV_selectXvXX_optionXoutV_selectXvXX_repeated: "
g_mergeEXxx1X_optionXoutV_selectXvXX_optionXoutV_selectXvXX_repeated:
[function({g, xx1}) { return
g.mergeE(xx1).option(Merge.outV,__.select(\"v\")).option(Merge.outV,__.select(\"v\"))
}], ",
+
g_mergeEXxx1X_optionXinV_selectXvXX_optionXinV_selectXvXX_repeated: "
g_mergeEXxx1X_optionXinV_selectXvXX_optionXinV_selectXvXX_repeated:
[function({g, xx1}) { return
g.mergeE(xx1).option(Merge.inV,__.select(\"v\")).option(Merge.inV,__.select(\"v\"))
}], "
+ ]
// SAMPLE: g_injectXnull_nullX: " g_injectXnull_nullX: [function({g}) {
return g.inject(null,null) }], ",
writer.writeLine('const gremlins = {')
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 4749161753..c270e8c7f9 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
@@ -928,6 +928,10 @@ const gremlins = {
g_injectXlist1_list2X_mergeEXlimitXlocal_1XX_optionXonCreate_rangeXlocal_1_2XX_optionXonMatch_tailXlocalXX_to_create:
[function({g, xx1, xx2}) { return
g.addV("person").property("name","marko").property("age",29).as("marko").addV("person").property("name","vadas").property("age",27).as("vadas").addV("software").property("name","lop").property("lang","java").as("lop").addV("person").property("name","josh").property("age",32).as("josh").addV("software").property("name","ripple").proper
[...]
g_mergeEXlabel_knows_out_marko_in_vadasX_optionXonMatch_weight_nullX_allowed:
[function({g, xx1, xx2}) { return
g.addV("person").property("name","marko").as("a").addV("person").property("name","vadas").as("b").addE("knows").from_("a").to("b").property("weight",1.0)
}, function({g, xx1, xx2}) { return g.mergeE(xx1).option(Merge.onMatch,xx2) },
function({g, xx1, xx2}) { return g.V() }, function({g, xx1, xx2}) { return
g.E().hasLabel("knows") }, function({g, xx1, xx2}) { return g.E().ha [...]
g_mergeEXlabel_knows_out_marko_in_vadasX_optionXonMatch_weight_nullX:
[function({g, xx1, xx2}) { return
g.addV("person").property("name","marko").as("a").addV("person").property("name","vadas").as("b").addE("knows").from_("a").to("b").property("weight",1.0)
}, function({g, xx1, xx2}) { return g.mergeE(xx1).option(Merge.onMatch,xx2) },
function({g, xx1, xx2}) { return g.V() }, function({g, xx1, xx2}) { return
g.E().hasLabel("knows") }, function({g, xx1, xx2}) { return g.E().hasLabel("
[...]
+ g_mergeEXxx1X_optionXonCreate_xx2X_optionXonCreate_xx2X_repeated:
[function({g, xx1, xx2}) { return
g.mergeE(xx1).option(Merge.onCreate,xx2).option(Merge.onCreate,xx2) }],
+ g_mergeEXxx1X_optionXonMatch_xx2X_optionXonMatch_xx2X_repeated:
[function({g, xx1, xx2}) { return
g.mergeE(xx1).option(Merge.onMatch,xx2).option(Merge.onMatch,xx2) }],
+ g_mergeEXxx1X_optionXoutV_selectXvXX_optionXoutV_selectXvXX_repeated:
[function({g, xx1}) { return
g.mergeE(xx1).option(Merge.outV,__.select("v")).option(Merge.outV,__.select("v"))
}],
+ g_mergeEXxx1X_optionXinV_selectXvXX_optionXinV_selectXvXX_repeated:
[function({g, xx1}) { return
g.mergeE(xx1).option(Merge.inV,__.select("v")).option(Merge.inV,__.select("v"))
}],
g_mergeVXemptyX_optionXonMatch_nullX: [function({g}) { return
g.addV("person").property("name","marko").property("age",29) }, function({g}) {
return g.mergeV(new Map([])).option(Merge.onMatch,null) }, function({g}) {
return g.V().has("person","name","marko").has("age",29) }],
g_V_mergeVXemptyX_optionXonMatch_nullX: [function({g}) { return
g.addV("person").property("name","marko").property("age",29) }, function({g}) {
return g.V().mergeV(new Map([])).option(Merge.onMatch,null) }, function({g}) {
return g.V().has("person","name","marko").has("age",29) }],
g_mergeVXnullX_optionXonCreate_label_null_name_markoX: [function({g, xx1})
{ return g.addV("person").property("name","marko").property("age",29) },
function({g, xx1}) { return g.mergeV(xx1) }],
@@ -990,6 +994,8 @@ const gremlins = {
g_injectXlist1_list2X_mergeVXlimitXlocal_1XX_optionXonCreate_rangeXlocal_1_2X_optionXonMatch_tailXlocalXX_to_create:
[function({g, xx1, xx2}) { return
g.addV("person").property("name","marko").property("age",29) }, function({g,
xx1, xx2}) { return
g.inject(xx1,xx1,xx2).fold().mergeV(__.limit(Scope.local,1)).option(Merge.onCreate,__.range(Scope.local,1,2)).option(Merge.onMatch,__.tail(Scope.local))
}, function({g, xx1, xx2}) { return
g.V().has("person","name","stephen").hasNot("create [...]
g_mergeVXlabel_person_name_marko_age_29X_optionXonMatch_age_nullX_allowed:
[function({g, xx1, xx2}) { return
g.addV("person").property("name","marko").property("age",29) }, function({g,
xx1, xx2}) { return g.mergeV(xx1).option(Merge.onMatch,xx2) }, function({g,
xx1, xx2}) { return g.V() }, function({g, xx1, xx2}) { return
g.V().has("person","name","marko").has("age",null) }],
g_mergeVXlabel_person_name_marko_age_29X_optionXonMatch_age_nullX:
[function({g, xx1, xx2}) { return
g.addV("person").property("name","marko").property("age",29) }, function({g,
xx1, xx2}) { return g.mergeV(xx1).option(Merge.onMatch,xx2) }, function({g,
xx1, xx2}) { return g.V() }, function({g, xx1, xx2}) { return
g.V().has("person","name","marko").has("age") }],
+ g_mergeVXxx1X_optionXonCreate_xx2X_optionXonCreate_xx2X_repeated:
[function({g, xx1, xx2}) { return
g.mergeV(xx1).option(Merge.onCreate,xx2).option(Merge.onCreate,xx2) }],
+ g_mergeVXxx1X_optionXonMatch_xx2X_optionXonMatch_xx2X_repeated:
[function({g, xx1, xx2}) { return
g.mergeV(xx1).option(Merge.onMatch,xx2).option(Merge.onMatch,xx2) }],
g_V_age_min: [function({g}) { return g.V().values("age").min() }],
g_V_foo_min: [function({g}) { return g.V().values("foo").min() }],
g_V_name_min: [function({g}) { return g.V().values("name").min() }],
diff --git a/gremlin-python/build/generate.groovy
b/gremlin-python/build/generate.groovy
index cad3e86169..10c92d1407 100644
--- a/gremlin-python/build/generate.groovy
+++ b/gremlin-python/build/generate.groovy
@@ -100,7 +100,14 @@ radishGremlinFile.withWriter('UTF-8') { Writer writer ->
// Groovy can't process certain null oriented calls because it gets
confused with the right overload to call
// at runtime. using this approach for now as these are the only such
situations encountered so far. a better
// solution may become necessary as testing of nulls expands.
- def staticTranslate = [:]
+ def staticTranslate = [
+ g_mergeVXxx1X_optionXonCreate_xx2X_optionXonCreate_xx2X_repeated:
" 'g_mergeVXxx1X_optionXonCreate_xx2X_optionXonCreate_xx2X_repeated':
[(lambda g,
xx1=None,xx2=None:g.merge_v(xx1).option(Merge.on_create,xx2).option(Merge.on_create,xx2))],
",
+ g_mergeVXxx1X_optionXonMatch_xx2X_optionXonMatch_xx2X_repeated: "
'g_mergeVXxx1X_optionXonMatch_xx2X_optionXonMatch_xx2X_repeated': [(lambda g,
xx1=None,xx2=None:g.merge_v(xx1).option(Merge.on_match,xx2).option(Merge.on_match,xx2))],
",
+ g_mergeEXxx1X_optionXonCreate_xx2X_optionXonCreate_xx2X_repeated:
" 'g_mergeEXxx1X_optionXonCreate_xx2X_optionXonCreate_xx2X_repeated':
[(lambda g,
xx1=None,xx2=None:g.merge_e(xx1).option(Merge.on_create,xx2).option(Merge.on_create,xx2))],
",
+ g_mergeEXxx1X_optionXonMatch_xx2X_optionXonMatch_xx2X_repeated: "
'g_mergeEXxx1X_optionXonMatch_xx2X_optionXonMatch_xx2X_repeated': [(lambda g,
xx1=None,xx2=None:g.merge_e(xx1).option(Merge.on_match,xx2).option(Merge.on_match,xx2))],
",
+
g_mergeEXxx1X_optionXoutV_selectXvXX_optionXoutV_selectXvXX_repeated: "
'g_mergeEXxx1X_optionXoutV_selectXvXX_optionXoutV_selectXvXX_repeated':
[(lambda g,
xx1=None:g.merge_e(xx1).option(Merge.out_v,__.select('v')).option(Merge.out_v,__.select('v')))],
",
+
g_mergeEXxx1X_optionXinV_selectXvXX_optionXinV_selectXvXX_repeated: "
'g_mergeEXxx1X_optionXinV_selectXvXX_optionXinV_selectXvXX_repeated': [(lambda
g,
xx1=None:g.merge_e(xx1).option(Merge.in_v,__.select('v')).option(Merge.in_v,__.select('v')))],
"
+ ]
// SAMPLE: g_injectXnull_nullX: " 'g_injectXnull_nullX': [(lambda g:
g.inject(None,None))], ",
writer.writeLine('world.gremlins = {')
diff --git a/gremlin-python/src/main/python/tests/feature/gremlin.py
b/gremlin-python/src/main/python/tests/feature/gremlin.py
index 530b3915b7..9170777050 100644
--- a/gremlin-python/src/main/python/tests/feature/gremlin.py
+++ b/gremlin-python/src/main/python/tests/feature/gremlin.py
@@ -910,6 +910,10 @@ world.gremlins = {
'g_injectXlist1_list2X_mergeEXlimitXlocal_1XX_optionXonCreate_rangeXlocal_1_2XX_optionXonMatch_tailXlocalXX_to_create':
[(lambda g,
xx1=None,xx2=None:g.add_v('person').property('name','marko').property('age',29).as_('marko').add_v('person').property('name','vadas').property('age',27).as_('vadas').add_v('software').property('name','lop').property('lang','java').as_('lop').add_v('person').property('name','josh').property('age',32).as_('josh').add_v('software').property('name','ripple')
[...]
'g_mergeEXlabel_knows_out_marko_in_vadasX_optionXonMatch_weight_nullX_allowed':
[(lambda g,
xx1=None,xx2=None:g.add_v('person').property('name','marko').as_('a').add_v('person').property('name','vadas').as_('b').add_e('knows').from_('a').to('b').property('weight',float(1.0))),
(lambda g, xx1=None,xx2=None:g.merge_e(xx1).option(Merge.on_match,xx2)),
(lambda g, xx1=None,xx2=None:g.V()), (lambda g,
xx1=None,xx2=None:g.E().has_label('knows')), (lambda g,
xx1=None,xx2=None:g.E().has_label [...]
'g_mergeEXlabel_knows_out_marko_in_vadasX_optionXonMatch_weight_nullX':
[(lambda g,
xx1=None,xx2=None:g.add_v('person').property('name','marko').as_('a').add_v('person').property('name','vadas').as_('b').add_e('knows').from_('a').to('b').property('weight',float(1.0))),
(lambda g, xx1=None,xx2=None:g.merge_e(xx1).option(Merge.on_match,xx2)),
(lambda g, xx1=None,xx2=None:g.V()), (lambda g,
xx1=None,xx2=None:g.E().has_label('knows')), (lambda g,
xx1=None,xx2=None:g.E().has_label('knows' [...]
+ 'g_mergeEXxx1X_optionXonCreate_xx2X_optionXonCreate_xx2X_repeated':
[(lambda g,
xx1=None,xx2=None:g.merge_e(xx1).option(Merge.on_create,xx2).option(Merge.on_create,xx2))],
+ 'g_mergeEXxx1X_optionXonMatch_xx2X_optionXonMatch_xx2X_repeated': [(lambda
g,
xx1=None,xx2=None:g.merge_e(xx1).option(Merge.on_match,xx2).option(Merge.on_match,xx2))],
+ 'g_mergeEXxx1X_optionXoutV_selectXvXX_optionXoutV_selectXvXX_repeated':
[(lambda g,
xx1=None:g.merge_e(xx1).option(Merge.out_v,__.select('v')).option(Merge.out_v,__.select('v')))],
+ 'g_mergeEXxx1X_optionXinV_selectXvXX_optionXinV_selectXvXX_repeated':
[(lambda g,
xx1=None:g.merge_e(xx1).option(Merge.in_v,__.select('v')).option(Merge.in_v,__.select('v')))],
'g_mergeVXemptyX_optionXonMatch_nullX': [(lambda
g:g.add_v('person').property('name','marko').property('age',29)), (lambda
g:g.merge_v({}).option(Merge.on_match,None)), (lambda
g:g.V().has('person','name','marko').has('age',29))],
'g_V_mergeVXemptyX_optionXonMatch_nullX': [(lambda
g:g.add_v('person').property('name','marko').property('age',29)), (lambda
g:g.V().merge_v({}).option(Merge.on_match,None)), (lambda
g:g.V().has('person','name','marko').has('age',29))],
'g_mergeVXnullX_optionXonCreate_label_null_name_markoX': [(lambda g,
xx1=None:g.add_v('person').property('name','marko').property('age',29)),
(lambda g, xx1=None:g.merge_v(xx1))],
@@ -972,6 +976,8 @@ world.gremlins = {
'g_injectXlist1_list2X_mergeVXlimitXlocal_1XX_optionXonCreate_rangeXlocal_1_2X_optionXonMatch_tailXlocalXX_to_create':
[(lambda g,
xx1=None,xx2=None:g.add_v('person').property('name','marko').property('age',29)),
(lambda g,
xx1=None,xx2=None:g.inject(xx1,xx1,xx2).fold().merge_v(__.limit(Scope.local,1)).option(Merge.on_create,__.range_(Scope.local,1,2)).option(Merge.on_match,__.tail(Scope.local))),
(lambda g,
xx1=None,xx2=None:g.V().has('person','name','stephen').has_not('created')),
[...]
'g_mergeVXlabel_person_name_marko_age_29X_optionXonMatch_age_nullX_allowed':
[(lambda g,
xx1=None,xx2=None:g.add_v('person').property('name','marko').property('age',29)),
(lambda g, xx1=None,xx2=None:g.merge_v(xx1).option(Merge.on_match,xx2)),
(lambda g, xx1=None,xx2=None:g.V()), (lambda g,
xx1=None,xx2=None:g.V().has('person','name','marko').has('age',None))],
'g_mergeVXlabel_person_name_marko_age_29X_optionXonMatch_age_nullX':
[(lambda g,
xx1=None,xx2=None:g.add_v('person').property('name','marko').property('age',29)),
(lambda g, xx1=None,xx2=None:g.merge_v(xx1).option(Merge.on_match,xx2)),
(lambda g, xx1=None,xx2=None:g.V()), (lambda g,
xx1=None,xx2=None:g.V().has('person','name','marko').has('age'))],
+ 'g_mergeVXxx1X_optionXonCreate_xx2X_optionXonCreate_xx2X_repeated':
[(lambda g,
xx1=None,xx2=None:g.merge_v(xx1).option(Merge.on_create,xx2).option(Merge.on_create,xx2))],
+ 'g_mergeVXxx1X_optionXonMatch_xx2X_optionXonMatch_xx2X_repeated': [(lambda
g,
xx1=None,xx2=None:g.merge_v(xx1).option(Merge.on_match,xx2).option(Merge.on_match,xx2))],
'g_V_age_min': [(lambda g:g.V().age.min_())],
'g_V_foo_min': [(lambda g:g.V().foo.min_())],
'g_V_name_min': [(lambda g:g.V().name.min_())],
diff --git
a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/features/StepDefinition.java
b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/features/StepDefinition.java
index ae2d844ce8..3dc37caad3 100644
---
a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/features/StepDefinition.java
+++
b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/features/StepDefinition.java
@@ -305,6 +305,10 @@ public final class StepDefinition {
@When("iterated to list")
public void iteratedToList() {
+ if ((null == traversal) && (error != null)) {
+ return; // Error probably occurred during traversal construction.
Skip to prevent NPE.
+ }
+
try {
result = traversal.toList();
} catch (Exception ex) {
@@ -314,6 +318,10 @@ public final class StepDefinition {
@When("iterated next")
public void iteratedNext() {
+ if ((null == traversal) && (error != null)) {
+ return; // Error probably occurred during traversal construction.
Skip to prevent NPE.
+ }
+
try {
result = traversal.next();
} catch (Exception ex) {
diff --git
a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/MergeEdge.feature
b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/MergeEdge.feature
index a93524277c..1bbd97f56f 100644
---
a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/MergeEdge.feature
+++
b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/MergeEdge.feature
@@ -1130,4 +1130,50 @@ Feature: Step - mergeE()
Then the result should have a count of 1
And the graph should return 2 for count of "g.V()"
And the graph should return 1 for count of "g.E().hasLabel(\"knows\")"
- And the graph should return 0 for count of
"g.E().hasLabel(\"knows\").has(\"weight\")"
\ No newline at end of file
+ And the graph should return 0 for count of
"g.E().hasLabel(\"knows\").has(\"weight\")"
+
+ # onCreate cannot be specified more than once
+ Scenario: g_mergeEXxx1X_optionXonCreate_xx2X_optionXonCreate_xx2X_repeated
+ Given the empty graph
+ And using the parameter xx1 defined as "m[{\"t[label]\": \"knows\"}]"
+ And using the parameter xx2 defined as "m[{\"created\":\"Y\"}]"
+ And the traversal of
+ """
+ g.mergeE(xx1).option(Merge.onCreate, xx2).option(Merge.onCreate, xx2)
+ """
+ When iterated to list
+ Then the traversal will raise an error with message containing text of
"option(onCreate) can only be specified once"
+
+ # onMatch cannot be specified more than once
+ Scenario: g_mergeEXxx1X_optionXonMatch_xx2X_optionXonMatch_xx2X_repeated
+ Given the empty graph
+ And using the parameter xx1 defined as "m[{\"t[label]\": \"knows\"}]"
+ And using the parameter xx2 defined as "m[{\"created\":\"Y\"}]"
+ And the traversal of
+ """
+ g.mergeE(xx1).option(Merge.onMatch, xx2).option(Merge.onMatch, xx2)
+ """
+ When iterated to list
+ Then the traversal will raise an error with message containing text of
"option(onMatch) can only be specified once"
+
+ # outV cannot be specified more than once
+ Scenario:
g_mergeEXxx1X_optionXoutV_selectXvXX_optionXoutV_selectXvXX_repeated
+ Given the empty graph
+ And using the parameter xx1 defined as "m[{\"t[label]\": \"knows\"}]"
+ And the traversal of
+ """
+ g.mergeE(xx1).option(Merge.outV, select("v")).option(Merge.outV,
select("v"))
+ """
+ When iterated to list
+ Then the traversal will raise an error with message containing text of
"option(outV) can only be specified once"
+
+ # inV cannot be specified more than once
+ Scenario: g_mergeEXxx1X_optionXinV_selectXvXX_optionXinV_selectXvXX_repeated
+ Given the empty graph
+ And using the parameter xx1 defined as "m[{\"t[label]\": \"knows\"}]"
+ And the traversal of
+ """
+ g.mergeE(xx1).option(Merge.inV, select("v")).option(Merge.inV,
select("v"))
+ """
+ When iterated to list
+ Then the traversal will raise an error with message containing text of
"option(inV) can only be specified once"
diff --git
a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/MergeVertex.feature
b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/MergeVertex.feature
index 2c2a33e7a1..fa49f42775 100644
---
a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/MergeVertex.feature
+++
b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/MergeVertex.feature
@@ -1096,4 +1096,28 @@ Feature: Step - mergeV()
When iterated to list
Then the result should have a count of 1
And the graph should return 1 for count of "g.V()"
- And the graph should return 0 for count of
"g.V().has(\"person\",\"name\",\"marko\").has(\"age\")"
\ No newline at end of file
+ And the graph should return 0 for count of
"g.V().has(\"person\",\"name\",\"marko\").has(\"age\")"
+
+ # onCreate cannot be specified more than once
+ Scenario: g_mergeVXxx1X_optionXonCreate_xx2X_optionXonCreate_xx2X_repeated
+ Given the empty graph
+ And using the parameter xx1 defined as "m[{\"t[label]\": \"person\"}]"
+ And using the parameter xx2 defined as "m[{\"created\":\"Y\"}]"
+ And the traversal of
+ """
+ g.mergeV(xx1).option(Merge.onCreate, xx2).option(Merge.onCreate, xx2)
+ """
+ When iterated to list
+ Then the traversal will raise an error with message containing text of
"option(onCreate) can only be specified once"
+
+ # onMatch cannot be specified more than once
+ Scenario: g_mergeVXxx1X_optionXonMatch_xx2X_optionXonMatch_xx2X_repeated
+ Given the empty graph
+ And using the parameter xx1 defined as "m[{\"t[label]\": \"person\"}]"
+ And using the parameter xx2 defined as "m[{\"created\":\"Y\"}]"
+ And the traversal of
+ """
+ g.mergeV(xx1).option(Merge.onMatch, xx2).option(Merge.onMatch, xx2)
+ """
+ When iterated to list
+ Then the traversal will raise an error with message containing text of
"option(onMatch) can only be specified once"