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 2022/01/25 21:39:24 UTC
[tinkerpop] 01/01: TINKERPOP-2681 mergeE() and mergeV()
This is an automated email from the ASF dual-hosted git repository.
spmallette pushed a commit to branch TINKERPOP-2681
in repository https://gitbox.apache.org/repos/asf/tinkerpop.git
commit 59c3d50671d901a42e35fba6c92cf8419affe4b1
Author: Stephen Mallette <st...@amazon.com>
AuthorDate: Wed Jan 12 10:12:16 2022 -0500
TINKERPOP-2681 mergeE() and mergeV()
---
CHANGELOG.asciidoc | 2 +
docs/src/dev/developer/for-committers.asciidoc | 1 +
docs/src/dev/io/graphbinary.asciidoc | 5 +
docs/src/dev/io/graphson.asciidoc | 190 ++++++------
docs/src/upgrade/release-3.6.x.asciidoc | 65 ++++
.../tinkerpop/gremlin/jsr223/CoreImports.java | 8 +-
.../language/grammar/GenericLiteralVisitor.java | 17 +-
.../language/grammar/GremlinBaseVisitor.java | 65 ++++
.../language/grammar/TraversalMethodVisitor.java | 76 +++++
.../grammar/TraversalSourceSpawnMethodVisitor.java | 22 ++
.../tinkerpop/gremlin/process/traversal/Merge.java | 46 +++
.../{step/TraversalOptionParent.java => Pick.java} | 13 +-
.../gremlin/process/traversal/Translator.java | 9 +-
.../traversal/dsl/graph/GraphTraversal.java | 125 +++++++-
.../traversal/dsl/graph/GraphTraversalSource.java | 82 ++++-
.../gremlin/process/traversal/dsl/graph/__.java | 44 ++-
.../traversal/step/TraversalOptionParent.java | 12 +-
.../process/traversal/step/branch/BranchStep.java | 3 +-
.../process/traversal/step/branch/ChooseStep.java | 9 +-
.../process/traversal/step/branch/UnionStep.java | 10 +-
.../process/traversal/step/map/MergeEdgeStep.java | 340 +++++++++++++++++++++
.../traversal/step/map/MergeVertexStep.java | 293 ++++++++++++++++++
.../process/traversal/step/util/event/Event.java | 6 +-
.../traversal/translator/DotNetTranslator.java | 34 ++-
.../traversal/translator/GroovyTranslator.java | 8 +-
.../traversal/translator/JavascriptTranslator.java | 4 +-
.../traversal/translator/PythonTranslator.java | 4 +-
.../gremlin/structure/io/binary/DataType.java | 1 +
.../io/binary/TypeSerializerRegistry.java | 6 +-
.../structure/io/binary/types/EnumSerializer.java | 6 +-
.../structure/io/graphson/GraphSONModule.java | 21 +-
.../io/graphson/GraphSONTypeSerializerV2d0.java | 6 +-
.../io/graphson/GraphSONTypeSerializerV3d0.java | 6 +-
.../gremlin/structure/io/gryo/GryoVersion.java | 13 +-
.../language/grammar/TraversalEnumParserTest.java | 4 +-
.../traversal/step/branch/BranchStepTest.java | 2 +-
.../traversal/step/branch/ChooseStepTest.java | 2 +-
.../gremlin/structure/io/gryo/GryoMapperTest.java | 7 +
gremlin-dotnet/build/generate.groovy | 10 +
.../Process/Traversal/GraphTraversal.cs | 64 ++++
.../Process/Traversal/GraphTraversalSource.cs | 54 +++-
.../src/Gremlin.Net/Process/Traversal/Merge.cs | 63 ++++
.../src/Gremlin.Net/Process/Traversal/__.cs | 48 +++
.../Structure/IO/GraphBinary/DataType.cs | 1 +
.../IO/GraphBinary/TypeSerializerRegistry.cs | 2 +
.../IO/GraphBinary/Types/EnumSerializer.cs | 6 +
.../Structure/IO/GraphSON/GraphSONReader.cs | 1 +
.../Structure/IO/GraphSON/MergeDeserializer.cs | 27 +-
.../Gherkin/CommonSteps.cs | 9 +-
.../Gherkin/GherkinTestRunner.cs | 5 +
.../Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs | 29 +-
.../Gherkin/IgnoreException.cs | 9 +-
.../Structure/IO/GraphBinary/GraphBinaryTests.cs | 15 +
.../Structure/IO/GraphSON/GraphSONReaderTests.cs | 12 +
.../Structure/IO/GraphSON/GraphSONWriterTests.cs | 13 +-
.../gremlin/driver/message/RequestMessage.java | 3 +-
.../GraphBinaryReaderWriterRoundTripTest.java | 4 +-
.../ast/VarAsBindingASTTransformation.groovy | 7 +-
.../jsr223/GremlinGroovyScriptEngineTest.java | 9 +-
gremlin-javascript/build/generate.groovy | 11 +
.../lib/process/graph-traversal.js | 42 +++
.../gremlin-javascript/lib/process/traversal.js | 1 +
.../test/cucumber/feature-steps.js | 8 +-
.../gremlin-javascript/test/cucumber/gremlin.js | 30 +-
gremlin-language/src/main/antlr4/Gremlin.g4 | 35 +++
.../language/corpus/DocumentationReader.java | 2 +
.../language/grammar/ReferenceGrammarTest.java | 16 +-
gremlin-python/build/generate.groovy | 12 +-
.../gremlin_python/process/graph_traversal.py | 38 +++
.../python/gremlin_python/process/traversal.py | 5 +
.../gremlin_python/structure/io/graphbinaryV1.py | 10 +-
.../src/main/python/radish/feature_steps.py | 8 +-
gremlin-python/src/main/python/radish/gremlin.py | 31 +-
.../tests/driver/test_driver_remote_connection.py | 1 +
.../tests/structure/io/test_graphbinaryV1.py | 7 +-
.../handler/WsGremlinBinaryRequestDecoder.java | 1 +
.../handler/WsGremlinTextRequestDecoder.java | 1 +
gremlin-test/features/map/MergeEdge.feature | 201 ++++++++++++
gremlin-test/features/map/MergeVertex.feature | 283 +++++++++++++++++
gremlin-test/features/sideEffect/Group.feature | 22 +-
gremlin-test/features/sideEffect/Inject.feature | 7 +-
.../tinkerpop/gremlin/features/StepDefinition.java | 30 +-
.../process/ProcessLimitedStandardSuite.java | 55 ----
.../process/traversal/step/branch/BranchTest.java | 4 +-
.../process/traversal/step/branch/ChooseTest.java | 6 +-
.../decoration/EventStrategyProcessTest.java | 132 ++++++++
.../tinkerpop/gremlin/structure/io/Model.java | 4 +-
.../_3_2_3/manual-graphson-generator.groovy | 5 +-
.../_3_2_4/manual-graphson-generator.groovy | 5 +-
.../io/gryo/_3_2_3/manual-gryo-generator.groovy | 8 +-
.../io/gryo/_3_2_4/manual-gryo-generator.groovy | 8 +-
.../traversal/step/map/TinkerMergeEdgeStep.java | 97 ++++++
.../traversal/step/map/TinkerMergeVertexStep.java | 88 ++++++
.../optimization/TinkerMergeEVStepStrategy.java | 61 ++++
.../gremlin/tinkergraph/structure/TinkerGraph.java | 4 +-
.../gremlin/tinkergraph/TinkerGraphWorld.java | 1 +
96 files changed, 2920 insertions(+), 318 deletions(-)
diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc
index 191ba91..111110d 100644
--- a/CHANGELOG.asciidoc
+++ b/CHANGELOG.asciidoc
@@ -27,6 +27,8 @@ image::https://raw.githubusercontent.com/apache/tinkerpop/master/docs/static/ima
* Changed TinkerGraph to allow identifiers to be heterogeneous when filtering.
* Prevented values of `T` to `property()` from being `null`.
* Added `fail()` step.
+* Added `mergeV()` and `mergeE()` steps.
+* Moved `TraversalOptionParent.Pick` to it's own class as `Pick`.
* Improved Gherkin test framework to allow for asserting traversal exceptions as a behavior.
* Fixed query indentation for profile metrics where indent levels were not being respected.
* `TraversalOpProcessor` no longer accepts a `String` representation of `Bytecode` for the "gremlin" argument which was left to support older versions of the drivers.
diff --git a/docs/src/dev/developer/for-committers.asciidoc b/docs/src/dev/developer/for-committers.asciidoc
index 20486b0..510c7e6 100644
--- a/docs/src/dev/developer/for-committers.asciidoc
+++ b/docs/src/dev/developer/for-committers.asciidoc
@@ -517,6 +517,7 @@ tag when one is necessary will cause provider tests to fail:
* `@AllowNullPropertyValues` - The scenario requires that the graph be configured with `AllowNullPropertyValues` as
`true` (meaning that it can store `null` values).
+* `@AllowUserSuppliedIds` - The scenario requires that the graph be configured with `UserSuppliedIds` as `true`.
* `@GraphComputerVerificationInjectionNotSupported` - The scenario will not work on with `GraphComputer` because the
`inject()` step is not supported.
* `@GraphComputerVerificationMidVNotSupported` - The scenario will not work on with `GraphComputer` because the
diff --git a/docs/src/dev/io/graphbinary.asciidoc b/docs/src/dev/io/graphbinary.asciidoc
index ecec755..1ec9c3f 100644
--- a/docs/src/dev/io/graphbinary.asciidoc
+++ b/docs/src/dev/io/graphbinary.asciidoc
@@ -107,6 +107,7 @@ Changes to existing types require new revision.
- `0x2b`: Tree
- `0x2c`: Metrics
- `0x2d`: TraversalMetrics
+- `0x2e`: Merge
- `0xfe`: Unspecified null object
- `0x00`: Custom
@@ -567,6 +568,10 @@ Where:
- `{duration}` is a `Long` describing the duration in nanoseconds.
- `{metrics}` is a `List` composed by `Metrics` items.
+==== Merge
+
+Format: a single `String` representing the enum value.
+
==== Custom
A custom type, represented as a blob value.
diff --git a/docs/src/dev/io/graphson.asciidoc b/docs/src/dev/io/graphson.asciidoc
index 46c5a7d..a20dda5 100644
--- a/docs/src/dev/io/graphson.asciidoc
+++ b/docs/src/dev/io/graphson.asciidoc
@@ -2765,46 +2765,6 @@ The following `Bytecode` example represents the traversal of `g.V().hasLabel('pe
}
----
-==== Operator
-
-[source,json]
-----
-{
- "@type" : "g:Operator",
- "@value" : "sum"
-}
-----
-
-==== Order
-
-[source,json]
-----
-{
- "@type" : "g:Order",
- "@value" : "shuffle"
-}
-----
-
-==== Pick
-
-[source,json]
-----
-{
- "@type" : "g:Pick",
- "@value" : "any"
-}
-----
-
-==== Pop
-
-[source,json]
-----
-{
- "@type" : "g:Pop",
- "@value" : "all"
-}
-----
-
==== Lambda
[source,json]
@@ -2819,6 +2779,16 @@ The following `Bytecode` example represents the traversal of `g.V().hasLabel('pe
}
----
+==== Merge
+
+[source,json]
+----
+{
+ "@type" : "g:Merge",
+ "@value" : "onMatch"
+}
+----
+
==== Metrics
[source,json]
@@ -2879,6 +2849,26 @@ The following `Bytecode` example represents the traversal of `g.V().hasLabel('pe
}
----
+==== Operator
+
+[source,json]
+----
+{
+ "@type" : "g:Operator",
+ "@value" : "sum"
+}
+----
+
+==== Order
+
+[source,json]
+----
+{
+ "@type" : "g:Order",
+ "@value" : "shuffle"
+}
+----
+
==== P
[source,json]
@@ -2998,6 +2988,26 @@ The following `Bytecode` example represents the traversal of `g.V().hasLabel('pe
}
----
+==== Pick
+
+[source,json]
+----
+{
+ "@type" : "g:Pick",
+ "@value" : "any"
+}
+----
+
+==== Pop
+
+[source,json]
+----
+{
+ "@type" : "g:Pop",
+ "@value" : "all"
+}
+----
+
==== Scope
[source,json]
@@ -5149,56 +5159,6 @@ The following `Bytecode` example represents the traversal of `g.V().hasLabel('pe
}
----
-==== Operator
-
-[source,json]
-----
-{
- "@type" : "g:Operator",
- "@value" : "sum"
-}
-----
-
-==== Order
-
-[source,json]
-----
-{
- "@type" : "g:Order",
- "@value" : "shuffle"
-}
-----
-
-==== Pick
-
-[source,json]
-----
-{
- "@type" : "g:Pick",
- "@value" : "any"
-}
-----
-
-==== Pop
-
-[source,json]
-----
-{
- "@type" : "g:Pop",
- "@value" : "all"
-}
-----
-
-==== Pick
-
-[source,json]
-----
-{
- "@type" : "g:Pick",
- "@value" : "any"
-}
-----
-
==== Lambda
[source,json]
@@ -5213,6 +5173,16 @@ The following `Bytecode` example represents the traversal of `g.V().hasLabel('pe
}
----
+==== Merge
+
+[source,json]
+----
+{
+ "@type" : "g:Merge",
+ "@value" : "onMatch"
+}
+----
+
==== Metrics
[source,json]
@@ -5271,6 +5241,26 @@ The following `Bytecode` example represents the traversal of `g.V().hasLabel('pe
}
----
+==== Operator
+
+[source,json]
+----
+{
+ "@type" : "g:Operator",
+ "@value" : "sum"
+}
+----
+
+==== Order
+
+[source,json]
+----
+{
+ "@type" : "g:Order",
+ "@value" : "shuffle"
+}
+----
+
==== P
`P` expects a single value of a `List` of values. There is special handling for `List` values when it comes to `within`, `without`, `inside`, `outside` and `between`. For `inside`, `outside` and `between`, the expectation is that the collection contain two objects (the rest will be ignored) and those two objects become the arguments to those methods. For `within` and `without`, these methods will accept an arbitrary number of objects in the collection.
@@ -5405,6 +5395,26 @@ Please see <<_p,P>> for additional information on `within`.
}
----
+==== Pick
+
+[source,json]
+----
+{
+ "@type" : "g:Pick",
+ "@value" : "any"
+}
+----
+
+==== Pop
+
+[source,json]
+----
+{
+ "@type" : "g:Pop",
+ "@value" : "all"
+}
+----
+
==== Scope
[source,json]
diff --git a/docs/src/upgrade/release-3.6.x.asciidoc b/docs/src/upgrade/release-3.6.x.asciidoc
index 6166865..93fac41 100644
--- a/docs/src/upgrade/release-3.6.x.asciidoc
+++ b/docs/src/upgrade/release-3.6.x.asciidoc
@@ -29,6 +29,71 @@ Please see the link:https://github.com/apache/tinkerpop/blob/3.6.0/CHANGELOG.asc
=== Upgrading for Users
+==== mergeV() and mergeE()
+
+One of the most commonly used patterns in Gremlin is the use of `fold().coalesce(unfold(), ...)` to perform upsert-like
+functionality. While this pattern is quite flexible, it can also be confusing to new users and for certain use cases
+challenging to get the pattern correctly implemented. For providers, the pattern is difficult to properly optimize
+because it can branch into complexity quite quickly making it hard to identify a section of Gremlin for an upsert and
+therefore is not executed as efficiently as it might have been otherwise.
+
+The new `mergeV()` and `mergeE()` steps greatly simplify this pattern and as the pattern is condensed into a single
+step it should be straightforward for providers to optimize as part of their implementations. The following example
+demonstrates just how much easier implementing a basic upsert of a vertex has gotten:
+
+[source,text]
+----
+// prior to 3.6.0, use fold().coalesce(unfold(), ...)
+gremlin> g.V().has('person','name','vadas').has('age', 27).
+......1> fold().
+......2> coalesce(unfold().property('age',30),
+......3> addV('person').property('name','vadas').property('age',27)).
+......4> elementMap()
+==>[id:2,label:person,name:vadas,age:30]
+
+// 3.6.0
+gremlin> g.mergeV([(T.label): 'person', name:'vadas', age: 27]).
+......1> option(onMatch, [age: 30]).
+......2> elementMap()
+==>[id:2,label:person,name:vadas,age:30]
+----
+
+The `fold().coalesce(unfold(), ...)` pattern was even more complicated for upserting edges, but the following example
+demonstrates how much easier `mergeE()` is to follow:
+
+[source,text]
+----
+// prior to 3.6.0, use fold().coalesce(unfold(), ...)
+gremlin> g.V().has('person','name','vadas').as('v').
+......1> V().has('software','name','ripple').
+......2> coalesce(__.inE('created').where(outV().as('v')),
+......3> addE('created').from('v').property('weight',0.5)).
+......4> elementMap()
+==>[id:0,label:created,IN:[id:5,label:software],OUT:[id:2,label:person],weight:0.5]
+
+// 3.6.0
+gremlin> ripple = g.V().has('software','name','ripple').next()
+==>v[5]
+gremlin> g.V().has('person','name','vadas').
+......1> mergeE([(T.label):'created',(IN):ripple, weight: 0.5]).
+......2> elementMap()
+==>[id:0,label:created,IN:[id:5,label:software],OUT:[id:2,label:person],weight:0.5]
+----
+
+For those currently using the `fold().coalesce(unfold(), ...)` pattern, there is no need to be concerned with
+incompatibility as a result of these new steps. That pattern is still perfectly usable and valid Gremlin, but whenever
+possible it would be best to migrate away from it as graph providers ramp up on 3.6.0 support and introduce important
+write optimizations that will make a big difference in performance.
+
+See: link:https://issues.apache.org/jira/browse/TINKERPOP-2681[TINKERPOP-2681],
+link:https://tinkerpop.apache.org/docs/3.6.0/reference/#mergee-step[mergeE()-step],
+link:https://tinkerpop.apache.org/docs/3.6.0/reference/#mergev-step[mergeV()-step]
+
+==== Moved Pick
+
+`Pick` was formerly a nested class of `TraversalOptionParent`, but has now been promoted to being a class on its own
+in `org.apache.tinkerpop.gremlin.process.traversal.Pick`.
+
==== Consistent by() Behavior
The `by()` modulator is critical to the usage of Gremlin. When used in conjunction with a step that supports it, the
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/CoreImports.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/CoreImports.java
index 194a192..2232ec2 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/CoreImports.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/CoreImports.java
@@ -55,9 +55,11 @@ import org.apache.tinkerpop.gremlin.process.computer.traversal.strategy.verifica
import org.apache.tinkerpop.gremlin.process.remote.RemoteConnection;
import org.apache.tinkerpop.gremlin.process.traversal.Bindings;
import org.apache.tinkerpop.gremlin.process.traversal.IO;
+import org.apache.tinkerpop.gremlin.process.traversal.Merge;
import org.apache.tinkerpop.gremlin.process.traversal.Operator;
import org.apache.tinkerpop.gremlin.process.traversal.Order;
import org.apache.tinkerpop.gremlin.process.traversal.P;
+import org.apache.tinkerpop.gremlin.process.traversal.Pick;
import org.apache.tinkerpop.gremlin.process.traversal.Pop;
import org.apache.tinkerpop.gremlin.process.traversal.SackFunctions;
import org.apache.tinkerpop.gremlin.process.traversal.Scope;
@@ -194,13 +196,14 @@ public final class CoreImports {
CLASS_IMPORTS.add(VertexProperty.Cardinality.class);
CLASS_IMPORTS.add(Column.class);
CLASS_IMPORTS.add(Direction.class);
+ CLASS_IMPORTS.add(Merge.class);
CLASS_IMPORTS.add(Operator.class);
CLASS_IMPORTS.add(Order.class);
CLASS_IMPORTS.add(Pop.class);
CLASS_IMPORTS.add(Scope.class);
CLASS_IMPORTS.add(T.class);
CLASS_IMPORTS.add(TraversalOptionParent.class);
- CLASS_IMPORTS.add(TraversalOptionParent.Pick.class);
+ CLASS_IMPORTS.add(Pick.class);
CLASS_IMPORTS.add(P.class);
CLASS_IMPORTS.add(TextP.class);
CLASS_IMPORTS.add(WithOptions.class);
@@ -344,12 +347,13 @@ public final class CoreImports {
Collections.addAll(ENUM_IMPORTS, VertexProperty.Cardinality.values());
Collections.addAll(ENUM_IMPORTS, Column.values());
Collections.addAll(ENUM_IMPORTS, Direction.values());
+ Collections.addAll(ENUM_IMPORTS, Merge.values());
Collections.addAll(ENUM_IMPORTS, Operator.values());
Collections.addAll(ENUM_IMPORTS, Order.values());
Collections.addAll(ENUM_IMPORTS, Pop.values());
Collections.addAll(ENUM_IMPORTS, Scope.values());
Collections.addAll(ENUM_IMPORTS, T.values());
- Collections.addAll(ENUM_IMPORTS, TraversalOptionParent.Pick.values());
+ Collections.addAll(ENUM_IMPORTS, Pick.values());
}
private CoreImports() {
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/GenericLiteralVisitor.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/GenericLiteralVisitor.java
index 336eecb..5aea5e0 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/GenericLiteralVisitor.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/GenericLiteralVisitor.java
@@ -20,9 +20,9 @@ package org.apache.tinkerpop.gremlin.language.grammar;
import org.antlr.v4.runtime.tree.ParseTree;
import org.apache.commons.text.StringEscapeUtils;
+import org.apache.tinkerpop.gremlin.process.traversal.Pick;
import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
import org.apache.tinkerpop.gremlin.process.traversal.TraversalStrategy;
-import org.apache.tinkerpop.gremlin.process.traversal.step.TraversalOptionParent;
import org.apache.tinkerpop.gremlin.structure.Direction;
import org.apache.tinkerpop.gremlin.structure.T;
import org.apache.tinkerpop.gremlin.structure.VertexProperty;
@@ -33,6 +33,7 @@ import java.math.BigInteger;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
+import java.util.Map;
import java.util.Objects;
/**
@@ -81,6 +82,13 @@ public class GenericLiteralVisitor extends GremlinBaseVisitor<Object> {
}
/**
+ * Parse a map literal context and return the map literal
+ */
+ public static Map getMapLiteral(final GremlinParser.GenericLiteralMapContext mapLiteral) {
+ return (Map) (instance().visitGenericLiteralMap(mapLiteral));
+ }
+
+ /**
* Parse a boolean literal context and return the boolean literal
*/
public static boolean getBooleanLiteral(final GremlinParser.BooleanLiteralContext booleanLiteral) {
@@ -299,6 +307,11 @@ public class GenericLiteralVisitor extends GremlinBaseVisitor<Object> {
return antlr.tvisitor.visitNestedTraversal(ctx);
}
+ @Override
+ public Object visitStructureVertex(final GremlinParser.StructureVertexContext ctx) {
+ return StructureElementVisitor.instance().visitStructureVertex(ctx);
+ }
+
/**
* {@inheritDoc}
*/
@@ -464,7 +477,7 @@ public class GenericLiteralVisitor extends GremlinBaseVisitor<Object> {
*/
@Override
public Object visitTraversalOptionParent(final GremlinParser.TraversalOptionParentContext ctx) {
- return TraversalEnumParser.parseTraversalEnumFromContext(TraversalOptionParent.Pick.class, ctx);
+ return TraversalEnumParser.parseTraversalEnumFromContext(Pick.class, ctx);
}
@Override
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/GremlinBaseVisitor.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/GremlinBaseVisitor.java
index 18f42f1..8cc7807 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/GremlinBaseVisitor.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/GremlinBaseVisitor.java
@@ -1529,4 +1529,69 @@ public class GremlinBaseVisitor<T> extends AbstractParseTreeVisitor<T> implement
public T visitStructureVertex(final GremlinParser.StructureVertexContext ctx) {
notImplemented(ctx); return null;
}
+
+ @Override
+ public T visitTraversalSourceSpawnMethod_mergeV_Map(final GremlinParser.TraversalSourceSpawnMethod_mergeV_MapContext ctx) {
+ notImplemented(ctx); return null;
+ }
+
+ @Override
+ public T visitTraversalSourceSpawnMethod_mergeV_Traversal(final GremlinParser.TraversalSourceSpawnMethod_mergeV_TraversalContext ctx) {
+ notImplemented(ctx); return null;
+ }
+
+ @Override
+ public T visitTraversalMethod_option_Merge_Map(GremlinParser.TraversalMethod_option_Merge_MapContext ctx) {
+ notImplemented(ctx); return null;
+ }
+
+ @Override
+ public T visitTraversalMethod_option_Merge_Traversal(final GremlinParser.TraversalMethod_option_Merge_TraversalContext ctx) {
+ notImplemented(ctx); return null;
+ }
+
+ @Override
+ public T visitTraversalMerge(final GremlinParser.TraversalMergeContext ctx) {
+ notImplemented(ctx); return null;
+ }
+
+ @Override
+ public T visitTraversalMethod_mergeV_Map(final GremlinParser.TraversalMethod_mergeV_MapContext ctx) {
+ notImplemented(ctx); return null;
+ }
+
+ @Override
+ public T visitTraversalMethod_mergeV_Traversal(final GremlinParser.TraversalMethod_mergeV_TraversalContext ctx) {
+ notImplemented(ctx); return null;
+ }
+
+ @Override
+ public T visitTraversalMethod_mergeE_Map(final GremlinParser.TraversalMethod_mergeE_MapContext ctx) {
+ notImplemented(ctx); return null;
+ }
+
+ @Override
+ public T visitTraversalMethod_mergeE_Traversal(final GremlinParser.TraversalMethod_mergeE_TraversalContext ctx) {
+ notImplemented(ctx); return null;
+ }
+
+ @Override
+ public T visitTraversalSourceSpawnMethod_mergeE_Map(final GremlinParser.TraversalSourceSpawnMethod_mergeE_MapContext ctx) {
+ notImplemented(ctx); return null;
+ }
+
+ @Override
+ public T visitTraversalSourceSpawnMethod_mergeE_Traversal(final GremlinParser.TraversalSourceSpawnMethod_mergeE_TraversalContext ctx) {
+ notImplemented(ctx); return null;
+ }
+
+ @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;
+ }
}
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/TraversalMethodVisitor.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/TraversalMethodVisitor.java
index e866598..ed1ae19 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/TraversalMethodVisitor.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/TraversalMethodVisitor.java
@@ -18,6 +18,7 @@
*/
package org.apache.tinkerpop.gremlin.language.grammar;
+import org.apache.tinkerpop.gremlin.process.traversal.Merge;
import org.apache.tinkerpop.gremlin.process.traversal.Operator;
import org.apache.tinkerpop.gremlin.process.traversal.Order;
import org.apache.tinkerpop.gremlin.process.traversal.Pop;
@@ -88,16 +89,73 @@ public class TraversalMethodVisitor extends TraversalRootVisitor<GraphTraversal>
return this.graphTraversal.addV(GenericLiteralVisitor.getStringLiteral(ctx.stringLiteral()));
}
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public GraphTraversal visitTraversalMethod_mergeV_Map(final GremlinParser.TraversalMethod_mergeV_MapContext ctx) {
+ return this.graphTraversal.mergeV(GenericLiteralVisitor.getMapLiteral(ctx.genericLiteralMap()));
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public GraphTraversal visitTraversalMethod_mergeV_Traversal(final GremlinParser.TraversalMethod_mergeV_TraversalContext ctx) {
+ return this.graphTraversal.mergeV(antlr.tvisitor.visitNestedTraversal(ctx.nestedTraversal()));
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Traversal visitTraversalMethod_mergeV_empty(final GremlinParser.TraversalMethod_mergeV_emptyContext ctx) {
+ return this.graphTraversal.mergeV();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Traversal visitTraversalMethod_mergeE_empty(final GremlinParser.TraversalMethod_mergeE_emptyContext ctx) {
+ return this.graphTraversal.mergeE();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public GraphTraversal visitTraversalMethod_mergeE_Map(final GremlinParser.TraversalMethod_mergeE_MapContext ctx) {
+ return this.graphTraversal.mergeE(GenericLiteralVisitor.getMapLiteral(ctx.genericLiteralMap()));
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public GraphTraversal visitTraversalMethod_mergeE_Traversal(final GremlinParser.TraversalMethod_mergeE_TraversalContext ctx) {
+ return this.graphTraversal.mergeE(antlr.tvisitor.visitNestedTraversal(ctx.nestedTraversal()));
+ }
+
+ /**
+ * {@inheritDoc}
+ */
@Override
public GraphTraversal visitTraversalMethod_addE_Traversal(final GremlinParser.TraversalMethod_addE_TraversalContext ctx) {
return this.graphTraversal.addE(antlr.tvisitor.visitNestedTraversal(ctx.nestedTraversal()));
}
+ /**
+ * {@inheritDoc}
+ */
@Override
public GraphTraversal visitTraversalMethod_addV_Traversal(final GremlinParser.TraversalMethod_addV_TraversalContext ctx) {
return this.graphTraversal.addV(antlr.tvisitor.visitNestedTraversal(ctx.nestedTraversal()));
}
+ /**
+ * {@inheritDoc}
+ */
@Override
public GraphTraversal visitTraversalMethod_addE_String(final GremlinParser.TraversalMethod_addE_StringContext ctx) {
final int childIndexOfParameterEdgeLabel = 2;
@@ -976,6 +1034,24 @@ public class TraversalMethodVisitor extends TraversalRootVisitor<GraphTraversal>
* {@inheritDoc}
*/
@Override
+ public GraphTraversal visitTraversalMethod_option_Merge_Map(final GremlinParser.TraversalMethod_option_Merge_MapContext ctx) {
+ return graphTraversal.option(TraversalEnumParser.parseTraversalEnumFromContext(Merge.class, ctx.traversalMerge()),
+ (Map) new GenericLiteralVisitor(antlr).visitGenericLiteralMap(ctx.genericLiteralMap()));
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public GraphTraversal visitTraversalMethod_option_Merge_Traversal(final GremlinParser.TraversalMethod_option_Merge_TraversalContext ctx) {
+ return this.graphTraversal.option(TraversalEnumParser.parseTraversalEnumFromContext(Merge.class, ctx.traversalMerge()),
+ antlr.tvisitor.visitNestedTraversal(ctx.nestedTraversal()));
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
public GraphTraversal visitTraversalMethod_optional(final GremlinParser.TraversalMethod_optionalContext ctx) {
return this.graphTraversal.optional(antlr.tvisitor.visitNestedTraversal(ctx.nestedTraversal()));
}
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 73e8612..f7d04f7 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
@@ -22,6 +22,8 @@ import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal;
import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource;
+import java.util.Map;
+
/**
* Use a {@link GraphTraversalSource} as the source and returns a {@link GraphTraversal} object.
*/
@@ -114,4 +116,24 @@ public class TraversalSourceSpawnMethodVisitor extends GremlinBaseVisitor<GraphT
}
return graphTraversal;
}
+
+ @Override
+ public GraphTraversal visitTraversalSourceSpawnMethod_mergeV_Map(final GremlinParser.TraversalSourceSpawnMethod_mergeV_MapContext ctx) {
+ return this.traversalSource.mergeV(GenericLiteralVisitor.getMapLiteral(ctx.genericLiteralMap()));
+ }
+
+ @Override
+ public GraphTraversal visitTraversalSourceSpawnMethod_mergeV_Traversal(final GremlinParser.TraversalSourceSpawnMethod_mergeV_TraversalContext ctx) {
+ return this.traversalSource.mergeV(anonymousVisitor.visitNestedTraversal(ctx.nestedTraversal()));
+ }
+
+ @Override
+ public GraphTraversal visitTraversalSourceSpawnMethod_mergeE_Traversal(final GremlinParser.TraversalSourceSpawnMethod_mergeE_TraversalContext ctx) {
+ return this.traversalSource.mergeE(anonymousVisitor.visitNestedTraversal(ctx.nestedTraversal()));
+ }
+
+ @Override
+ public GraphTraversal visitTraversalSourceSpawnMethod_mergeE_Map(final GremlinParser.TraversalSourceSpawnMethod_mergeE_MapContext ctx) {
+ return this.traversalSource.mergeE(GenericLiteralVisitor.getMapLiteral(ctx.genericLiteralMap()));
+ }
}
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Merge.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Merge.java
new file mode 100644
index 0000000..a64d8ce
--- /dev/null
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Merge.java
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.tinkerpop.gremlin.process.traversal;
+
+import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource;
+import org.apache.tinkerpop.gremlin.structure.Element;
+
+import java.util.Map;
+
+/**
+ * Options relevant to upsert-like steps such as {@link GraphTraversalSource#mergeV(Map)}.
+ */
+public enum Merge {
+
+ /**
+ * Allows definition of the action to take when a merge operation ends up not matching the search criteria.
+ * Typically, this event means that an {@link Element} will be created.
+ *
+ * @since 3.6.0
+ */
+ onCreate,
+
+ /**
+ * Allows definition of the action to take when a merge operation ends up successfully matching the search criteria.
+ * Typically, this event means that the matched {@link Element} will be returned.
+ *
+ * @since 3.6.0
+ */
+ onMatch
+}
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/TraversalOptionParent.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Pick.java
similarity index 66%
copy from gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/TraversalOptionParent.java
copy to gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Pick.java
index 6eb60a2..980c7dd 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/TraversalOptionParent.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Pick.java
@@ -16,16 +16,11 @@
* specific language governing permissions and limitations
* under the License.
*/
-package org.apache.tinkerpop.gremlin.process.traversal.step;
-
-import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
+package org.apache.tinkerpop.gremlin.process.traversal;
/**
- * @author Marko A. Rodriguez (http://markorodriguez.com)
+ * A token used with {@code option()}.
*/
-public interface TraversalOptionParent<M, S, E> extends TraversalParent {
-
- public static enum Pick {any, none}
-
- public void addGlobalChildOption(final M pickToken, final Traversal.Admin<S, E> traversalOption);
+public enum Pick {
+ any, none
}
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Translator.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Translator.java
index 30dccc4..0be7555 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Translator.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Translator.java
@@ -19,7 +19,6 @@
package org.apache.tinkerpop.gremlin.process.traversal;
-import org.apache.tinkerpop.gremlin.process.traversal.step.TraversalOptionParent;
import org.apache.tinkerpop.gremlin.process.traversal.strategy.TraversalStrategyProxy;
import org.apache.tinkerpop.gremlin.structure.Edge;
import org.apache.tinkerpop.gremlin.structure.Vertex;
@@ -165,9 +164,9 @@ public interface Translator<S, T> {
protected abstract String getSyntax(final VertexProperty.Cardinality o);
/**
- * Take the {@link TraversalOptionParent.Pick} argument and convert it to a string representation in the target language.
+ * Take the {@link Pick} argument and convert it to a string representation in the target language.
*/
- protected abstract String getSyntax(final TraversalOptionParent.Pick o);
+ protected abstract String getSyntax(final Pick o);
/**
* Take the numeric argument and convert it to a string representation in the target language. Languages
@@ -308,8 +307,8 @@ public interface Translator<S, T> {
return script.append(getSyntax((SackFunctions.Barrier) object));
} else if (object instanceof VertexProperty.Cardinality) {
return script.append(getSyntax((VertexProperty.Cardinality) object));
- } else if (object instanceof TraversalOptionParent.Pick) {
- return script.append(getSyntax((TraversalOptionParent.Pick) object));
+ } else if (object instanceof Pick) {
+ return script.append(getSyntax((Pick) object));
} else if (object instanceof Enum) {
return produceScript((Enum<?>) object);
} else if (object instanceof Vertex) {
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.java
index 376ed6b..7a985dc 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.java
@@ -26,15 +26,18 @@ import org.apache.tinkerpop.gremlin.process.computer.traversal.step.map.PeerPres
import org.apache.tinkerpop.gremlin.process.computer.traversal.step.map.ProgramVertexProgramStep;
import org.apache.tinkerpop.gremlin.process.computer.traversal.step.map.ShortestPathVertexProgramStep;
import org.apache.tinkerpop.gremlin.process.traversal.Failure;
+import org.apache.tinkerpop.gremlin.process.traversal.Merge;
import org.apache.tinkerpop.gremlin.process.traversal.Order;
import org.apache.tinkerpop.gremlin.process.traversal.P;
import org.apache.tinkerpop.gremlin.process.traversal.Path;
+import org.apache.tinkerpop.gremlin.process.traversal.Pick;
import org.apache.tinkerpop.gremlin.process.traversal.Pop;
import org.apache.tinkerpop.gremlin.process.traversal.Scope;
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.lambda.ColumnTraversal;
+import org.apache.tinkerpop.gremlin.process.traversal.lambda.ConstantTraversal;
import org.apache.tinkerpop.gremlin.process.traversal.lambda.FunctionTraverser;
import org.apache.tinkerpop.gremlin.process.traversal.lambda.LoopTraversal;
import org.apache.tinkerpop.gremlin.process.traversal.lambda.PredicateTraverser;
@@ -100,6 +103,8 @@ import org.apache.tinkerpop.gremlin.process.traversal.step.map.MaxGlobalStep;
import org.apache.tinkerpop.gremlin.process.traversal.step.map.MaxLocalStep;
import org.apache.tinkerpop.gremlin.process.traversal.step.map.MeanGlobalStep;
import org.apache.tinkerpop.gremlin.process.traversal.step.map.MeanLocalStep;
+import org.apache.tinkerpop.gremlin.process.traversal.step.map.MergeEdgeStep;
+import org.apache.tinkerpop.gremlin.process.traversal.step.map.MergeVertexStep;
import org.apache.tinkerpop.gremlin.process.traversal.step.map.MinGlobalStep;
import org.apache.tinkerpop.gremlin.process.traversal.step.map.MinLocalStep;
import org.apache.tinkerpop.gremlin.process.traversal.step.map.NoOpBarrierStep;
@@ -1072,6 +1077,89 @@ public interface GraphTraversal<S, E> extends Traversal<S, E> {
}
/**
+ * Performs a merge (i.e. upsert) style operation for an {@link Vertex} using the incoming {@code Map} traverser as
+ * an argument. The {@code Map} represents search criteria and will match each of the supplied key/value pairs where
+ * the keys may be {@code String} property values or a value of {@link T}. If a match is not made it will use that
+ * search criteria to create the new {@link Vertex}.
+ *
+ * @since 3.6.0
+ */
+ public default GraphTraversal<S, Vertex> mergeV() {
+ this.asAdmin().getBytecode().addStep(Symbols.mergeV);
+ final MergeVertexStep<S> step = new MergeVertexStep<>(this.asAdmin(), false);
+ return this.asAdmin().addStep(step);
+ }
+
+ /**
+ * Performs a merge (i.e. upsert) style operation for an {@link Vertex} using a {@code Map} as an argument.
+ * The {@code Map} represents search criteria and will match each of the supplied key/value pairs where the keys
+ * may be {@code String} property values or a value of {@link T}. If a match is not made it will use that search
+ * criteria to create the new {@link Vertex}.
+ *
+ * @param searchCreate This {@code Map} can have a key of {@link T} or a {@code String}.
+ * @since 3.6.0
+ */
+ public default GraphTraversal<S, Vertex> mergeV(final Map<Object, Object> searchCreate) {
+ this.asAdmin().getBytecode().addStep(Symbols.mergeV, searchCreate);
+ final MergeVertexStep<S> step = new MergeVertexStep<>(this.asAdmin(), false, searchCreate);
+ return this.asAdmin().addStep(step);
+ }
+
+ /**
+ * Performs a merge (i.e. upsert) style operation for an {@link Vertex} using a {@code Map} as an argument.
+ * The {@code Map} represents search criteria and will match each of the supplied key/value pairs where the keys
+ * may be {@code String} property values or a value of {@link T}. If a match is not made it will use that search
+ * criteria to create the new {@link Vertex}.
+ *
+ * @param searchCreate This anonymous {@link Traversal} must produce a {@code Map} that may have a keys of
+ * {@link T} or a {@code String}.
+ * @since 3.6.0
+ */
+ public default GraphTraversal<S, Vertex> mergeV(final Traversal<?, Map<Object, Object>> searchCreate) {
+ this.asAdmin().getBytecode().addStep(Symbols.mergeV, searchCreate);
+ final MergeVertexStep<S> step = new MergeVertexStep(this.asAdmin(), false, searchCreate.asAdmin());
+ return this.asAdmin().addStep(step);
+ }
+
+ /**
+ * Spawns a {@link GraphTraversal} by doing a merge (i.e. upsert) style operation for an {@link Edge} using an
+ * incoming {@code Map} as an argument.
+ *
+ * @since 3.6.0
+ */
+ public default GraphTraversal<S, Edge> mergeE() {
+ this.asAdmin().getBytecode().addStep(Symbols.mergeE);
+ final MergeEdgeStep<S> step = new MergeEdgeStep(this.asAdmin(), false);
+ return this.asAdmin().addStep(step);
+ }
+
+ /**
+ * Spawns a {@link GraphTraversal} by doing a merge (i.e. upsert) style operation for an {@link Edge} using a
+ * {@code Map} as an argument.
+ *
+ * @param searchCreate This {@code Map} can have a key of {@link T} {@link Direction} or a {@code String}.
+ * @since 3.6.0
+ */
+ public default GraphTraversal<S, Edge> mergeE(final Map<?, Object> searchCreate) {
+ this.asAdmin().getBytecode().addStep(Symbols.mergeE, searchCreate);
+ final MergeEdgeStep<S> step = new MergeEdgeStep(this.asAdmin(), false, searchCreate);
+ return this.asAdmin().addStep(step);
+ }
+
+ /**
+ * Spawns a {@link GraphTraversal} by doing a merge (i.e. upsert) style operation for an {@link Edge} using a
+ * {@code Map} as an argument.
+ *
+ * @param searchCreate This {@code Map} can have a key of {@link T} {@link Direction} or a {@code String}.
+ * @since 3.6.0
+ */
+ public default GraphTraversal<S, Edge> mergeE(final Traversal<?, Map<Object, Object>> searchCreate) {
+ this.asAdmin().getBytecode().addStep(Symbols.mergeE, searchCreate);
+ final MergeEdgeStep<S> step = new MergeEdgeStep(this.asAdmin(), false, searchCreate.asAdmin());
+ return this.asAdmin().addStep(step);
+ }
+
+ /**
* Adds an {@link Edge} with the specified edge label.
*
* @param edgeLabel the label of the newly added edge
@@ -3058,18 +3146,39 @@ public interface GraphTraversal<S, E> extends Traversal<S, E> {
////
/**
- * This step modifies {@link #choose(Function)} to specifies the available choices that might be executed.
+ * This is a step modulator to a {@link TraversalOptionParent} like {@code choose()} or {@code mergeV()} where the
+ * provided argument associated to the {@code token} is applied according to the semantics of the step. Please see
+ * the documentation of such steps to understand the usage context.
*
- * @param pick the token that would trigger this option which may be a {@link TraversalOptionParent.Pick},
- * a {@link Traversal}, {@link Predicate}, or object depending on the step being modulated.
+ * @param token the token that would trigger this option which may be a {@link Pick}, {@link Merge},
+ * a {@link Traversal}, {@link Predicate}, or object depending on the step being modulated.
* @param traversalOption the option as a traversal
* @return the traversal with the modulated step
* @see <a href="http://tinkerpop.apache.org/docs/${project.version}/reference/#choose-step" target="_blank">Reference Documentation - Choose Step</a>
+ * @see <a href="http://tinkerpop.apache.org/docs/${project.version}/reference/#mergev-step" target="_blank">Reference Documentation - MergeV Step</a>
+ * @see <a href="http://tinkerpop.apache.org/docs/${project.version}/reference/#mergee-step" target="_blank">Reference Documentation - MergeE Step</a>
* @since 3.0.0-incubating
*/
- public default <M, E2> GraphTraversal<S, E> option(final M pick, final Traversal<?, E2> traversalOption) {
- this.asAdmin().getBytecode().addStep(Symbols.option, pick, traversalOption);
- ((TraversalOptionParent<M, E, E2>) this.asAdmin().getEndStep()).addGlobalChildOption(pick, (Traversal.Admin<E, E2>) traversalOption.asAdmin());
+ public default <M, E2> GraphTraversal<S, E> option(final M token, final Traversal<?, E2> traversalOption) {
+ this.asAdmin().getBytecode().addStep(Symbols.option, token, traversalOption);
+ ((TraversalOptionParent<M, E, E2>) this.asAdmin().getEndStep()).addChildOption(token, (Traversal.Admin<E, E2>) traversalOption.asAdmin());
+ return this;
+ }
+
+ /**
+ * This is a step modulator to a {@link TraversalOptionParent} like {@code choose()} or {@code mergeV()} where the
+ * provided argument associated to the {@code token} is applied according to the semantics of the step. Please see
+ * the documentation of such steps to understand the usage context.
+ *
+ * @param m Provides a {@code Map} as the option which is the same as doing {@code constant(m)}.
+ * @return the traversal with the modulated step
+ * @see <a href="http://tinkerpop.apache.org/docs/${project.version}/reference/#mergev-step" target="_blank">Reference Documentation - MergeV Step</a>
+ * @see <a href="http://tinkerpop.apache.org/docs/${project.version}/reference/#mergee-step" target="_blank">Reference Documentation - MergeE Step</a>
+ * @since 3.6.0
+ */
+ public default <M, E2> GraphTraversal<S, E> option(final M token, final Map<Object, Object> m) {
+ this.asAdmin().getBytecode().addStep(Symbols.option, token, m);
+ ((TraversalOptionParent<M, E, E2>) this.asAdmin().getEndStep()).addChildOption(token, (Traversal.Admin<E, E2>) new ConstantTraversal<>(m).asAdmin());
return this;
}
@@ -3083,7 +3192,7 @@ public interface GraphTraversal<S, E> extends Traversal<S, E> {
*/
public default <E2> GraphTraversal<S, E> option(final Traversal<?, E2> traversalOption) {
this.asAdmin().getBytecode().addStep(Symbols.option, traversalOption);
- ((TraversalOptionParent<Object, E, E2>) this.asAdmin().getEndStep()).addGlobalChildOption(TraversalOptionParent.Pick.any, (Traversal.Admin<E, E2>) traversalOption.asAdmin());
+ ((TraversalOptionParent<Object, E, E2>) this.asAdmin().getEndStep()).addChildOption(Pick.any, (Traversal.Admin<E, E2>) traversalOption.asAdmin());
return this;
}
@@ -3186,6 +3295,8 @@ public interface GraphTraversal<S, E> extends Traversal<S, E> {
public static final String tree = "tree";
public static final String addV = "addV";
public static final String addE = "addE";
+ public static final String mergeV = "mergeV";
+ public static final String mergeE = "mergeE";
public static final String from = "from";
public static final String filter = "filter";
public static final String or = "or";
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 fbb873f..5806227 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
@@ -30,17 +30,22 @@ import org.apache.tinkerpop.gremlin.process.traversal.TraversalStrategy;
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.GraphStep;
+import org.apache.tinkerpop.gremlin.process.traversal.step.map.MergeEdgeStep;
+import org.apache.tinkerpop.gremlin.process.traversal.step.map.MergeVertexStep;
import org.apache.tinkerpop.gremlin.process.traversal.step.sideEffect.IoStep;
import org.apache.tinkerpop.gremlin.process.traversal.step.sideEffect.InjectStep;
import org.apache.tinkerpop.gremlin.process.traversal.strategy.decoration.RequirementsStrategy;
import org.apache.tinkerpop.gremlin.process.traversal.traverser.TraverserRequirement;
+import org.apache.tinkerpop.gremlin.structure.Direction;
import org.apache.tinkerpop.gremlin.structure.Edge;
import org.apache.tinkerpop.gremlin.structure.Graph;
+import org.apache.tinkerpop.gremlin.structure.T;
import org.apache.tinkerpop.gremlin.structure.Transaction;
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.Map;
import java.util.Optional;
import java.util.function.BinaryOperator;
import java.util.function.Supplier;
@@ -298,6 +303,8 @@ public class GraphTraversalSource implements TraversalSource {
/**
* Spawns a {@link GraphTraversal} by adding a vertex with the specified label. If the {@code label} is
* {@code null} then it will default to {@link Vertex#DEFAULT_LABEL}.
+ *
+ * @since 3.1.0-incubating
*/
public GraphTraversal<Vertex, Vertex> addV(final String vertexLabel) {
if (null == vertexLabel) throw new IllegalArgumentException("vertexLabel cannot be null");
@@ -310,6 +317,8 @@ public class GraphTraversalSource implements TraversalSource {
/**
* Spawns a {@link GraphTraversal} by adding a vertex with the label as determined by a {@link Traversal}. If the
* {@code vertexLabelTraversal} is {@code null} then it will default to {@link Vertex#DEFAULT_LABEL}.
+ *
+ * @since 3.3.1
*/
public GraphTraversal<Vertex, Vertex> addV(final Traversal<?, String> vertexLabelTraversal) {
if (null == vertexLabelTraversal) throw new IllegalArgumentException("vertexLabelTraversal cannot be null");
@@ -321,6 +330,8 @@ public class GraphTraversalSource implements TraversalSource {
/**
* Spawns a {@link GraphTraversal} by adding a vertex with the default label.
+ *
+ * @since 3.1.0-incubating
*/
public GraphTraversal<Vertex, Vertex> addV() {
final GraphTraversalSource clone = this.clone();
@@ -330,7 +341,9 @@ public class GraphTraversalSource implements TraversalSource {
}
/**
- * Spawns a {@link GraphTraversal} by adding a edge with the specified label.
+ * Spawns a {@link GraphTraversal} by adding an edge with the specified label.
+ *
+ * @since 3.1.0-incubating
*/
public GraphTraversal<Edge, Edge> addE(final String label) {
final GraphTraversalSource clone = this.clone();
@@ -341,6 +354,8 @@ public class GraphTraversalSource implements TraversalSource {
/**
* Spawns a {@link GraphTraversal} by adding a edge with a label as specified by the provided {@link Traversal}.
+ *
+ * @since 3.3.1
*/
public GraphTraversal<Edge, Edge> addE(final Traversal<?, String> edgeLabelTraversal) {
final GraphTraversalSource clone = this.clone();
@@ -350,6 +365,67 @@ public class GraphTraversalSource implements TraversalSource {
}
/**
+ * Spawns a {@link GraphTraversal} by doing a merge (i.e. upsert) style operation for an {@link Vertex} using a
+ * {@code Map} as an argument. The {@code Map} represents search criteria and will match each of the supplied
+ * key/value pairs where the keys may be {@code String} property values or a value of {@link T}. If a match is not
+ * made it will use that search criteria to create the new {@link Vertex}.
+ *
+ * @param searchCreate This {@code Map} can have a key of {@link T} or a {@code String}.
+ * @since 3.6.0
+ */
+ public GraphTraversal<Vertex, Vertex> mergeV(final Map<Object, Object> searchCreate) {
+ final GraphTraversalSource clone = this.clone();
+ clone.bytecode.addStep(GraphTraversal.Symbols.mergeV, searchCreate);
+ final GraphTraversal.Admin<Vertex, Vertex> traversal = new DefaultGraphTraversal<>(clone);
+ return traversal.addStep(new MergeVertexStep(traversal, true, searchCreate));
+ }
+
+ /**
+ * Spawns a {@link GraphTraversal} by doing a merge (i.e. upsert) style operation for an {@link Vertex} using a
+ * {@code Map} as an argument. The {@code Map} represents search criteria and will match each of the supplied
+ * key/value pairs where the keys may be {@code String} property values or a value of {@link T}. If a match is not
+ * made it will use that search criteria to create the new {@link Vertex}.
+ *
+ * @param searchCreate This anonymous {@link Traversal} must produce a {@code Map} that may have a keys of
+ * {@link T} or a {@code String}.
+ * @since 3.6.0
+ */
+ public <S> GraphTraversal<S, Vertex> mergeV(final Traversal<?, Map<Object, Object>> searchCreate) {
+ final GraphTraversalSource clone = this.clone();
+ clone.bytecode.addStep(GraphTraversal.Symbols.mergeV, searchCreate);
+ final GraphTraversal.Admin<S, Vertex> traversal = new DefaultGraphTraversal<>(clone);
+ return traversal.addStep(new MergeVertexStep(traversal, true, searchCreate.asAdmin()));
+ }
+
+ /**
+ * Spawns a {@link GraphTraversal} by doing a merge (i.e. upsert) style operation for an {@link Edge} using a
+ * {@code Map} as an argument.
+ *
+ * @param searchCreate This {@code Map} can have a key of {@link T} {@link Direction} or a {@code String}.
+ * @since 3.6.0
+ */
+ public GraphTraversal<Edge, Edge> mergeE(final Map<?, Object> searchCreate) {
+ final GraphTraversalSource clone = this.clone();
+ clone.bytecode.addStep(GraphTraversal.Symbols.mergeE, searchCreate);
+ final GraphTraversal.Admin<Edge, Edge> traversal = new DefaultGraphTraversal<>(clone);
+ return traversal.addStep(new MergeEdgeStep(traversal, true, searchCreate));
+ }
+
+ /**
+ * Spawns a {@link GraphTraversal} by doing a merge (i.e. upsert) style operation for an {@link Edge} using a
+ * {@code Map} as an argument.
+ *
+ * @param searchCreate This {@code Map} can have a key of {@link T} {@link Direction} or a {@code String}.
+ * @since 3.6.0
+ */
+ public GraphTraversal<Edge, Edge> mergeE(final Traversal<?, Map<Object, Object>> searchCreate) {
+ final GraphTraversalSource clone = this.clone();
+ clone.bytecode.addStep(GraphTraversal.Symbols.mergeE, searchCreate);
+ final GraphTraversal.Admin<Edge, Edge> traversal = new DefaultGraphTraversal<>(clone);
+ return traversal.addStep(new MergeEdgeStep(traversal, true, searchCreate.asAdmin()));
+ }
+
+ /**
* Spawns a {@link GraphTraversal} starting it with arbitrary values.
*/
public <S> GraphTraversal<S, S> inject(S... starts) {
@@ -364,6 +440,8 @@ public class GraphTraversalSource implements TraversalSource {
/**
* Spawns a {@link GraphTraversal} starting with all vertices or some subset of vertices as specified by their
* unique identifier.
+ *
+ * @since 3.0.0-incubating
*/
public GraphTraversal<Vertex, Vertex> V(final Object... vertexIds) {
// a single null is [null]
@@ -377,6 +455,8 @@ public class GraphTraversalSource implements TraversalSource {
/**
* Spawns a {@link GraphTraversal} starting with all edges or some subset of edges as specified by their unique
* identifier.
+ *
+ * @since 3.0.0-incubating
*/
public GraphTraversal<Edge, Edge> E(final Object... edgeIds) {
// a single null is [null]
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/__.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/__.java
index 0502ed0..6e6374f 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/__.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/__.java
@@ -516,6 +516,27 @@ public class __ {
}
/**
+ * @see GraphTraversal#mergeV()
+ */
+ public static <A> GraphTraversal<A, Vertex> mergeV() {
+ return __.<A>start().mergeV();
+ }
+
+ /**
+ * @see GraphTraversal#mergeV(Map)
+ */
+ public static <A> GraphTraversal<A, Vertex> mergeV(final Map<Object, Object> searchCreate) {
+ return __.<A>start().mergeV(searchCreate);
+ }
+
+ /**
+ * @see GraphTraversal#mergeV(Traversal)
+ */
+ public static <A> GraphTraversal<A, Vertex> mergeV(final Traversal<?, Map<Object, Object>> searchCreate) {
+ return __.<A>start().mergeV(searchCreate);
+ }
+
+ /**
* @see GraphTraversal#addE(String)
*/
public static <A> GraphTraversal<A, Edge> addE(final String edgeLabel) {
@@ -523,13 +544,34 @@ public class __ {
}
/**
- * @see GraphTraversal#addE(org.apache.tinkerpop.gremlin.process.traversal.Traversal)
+ * @see GraphTraversal#addE(Traversal)
*/
public static <A> GraphTraversal<A, Edge> addE(final Traversal<?, String> edgeLabelTraversal) {
return __.<A>start().addE(edgeLabelTraversal);
}
/**
+ * @see GraphTraversal#mergeE()
+ */
+ public static <A> GraphTraversal<A, Edge> mergeE() {
+ return __.<A>start().mergeE();
+ }
+
+ /**
+ * @see GraphTraversal#mergeE(Map)
+ */
+ public static <A> GraphTraversal<A, Edge> mergeE(final Map<?, Object> searchCreate) {
+ return __.<A>start().mergeE(searchCreate);
+ }
+
+ /**
+ * @see GraphTraversal#mergeE(Traversal)
+ */
+ public static <A> GraphTraversal<A, Edge> mergeE(final Traversal<?, Map<Object, Object>> searchCreate) {
+ return __.<A>start().mergeE(searchCreate);
+ }
+
+ /**
* @see GraphTraversal#math(String)
*/
public static <A> GraphTraversal<A, Double> math(final String expression) {
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/TraversalOptionParent.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/TraversalOptionParent.java
index 6eb60a2..354700b 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/TraversalOptionParent.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/TraversalOptionParent.java
@@ -18,14 +18,20 @@
*/
package org.apache.tinkerpop.gremlin.process.traversal.step;
+import org.apache.tinkerpop.gremlin.process.traversal.Merge;
+import org.apache.tinkerpop.gremlin.process.traversal.Pick;
import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
/**
+ * Describes steps that can be parent to a {@link Traversal} from the {@code option()} modulator.
+ *
* @author Marko A. Rodriguez (http://markorodriguez.com)
*/
public interface TraversalOptionParent<M, S, E> extends TraversalParent {
- public static enum Pick {any, none}
-
- public void addGlobalChildOption(final M pickToken, final Traversal.Admin<S, E> traversalOption);
+ /**
+ * The child as defined by the token it takes, like {@link Pick} or {@link Merge}. This traversal may be of local
+ * or global scope depending on the step implementation that works with {@code option()}.
+ */
+ public void addChildOption(final M token, final Traversal.Admin<S, E> traversalOption);
}
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/branch/BranchStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/branch/BranchStep.java
index 361ed54..085cf57 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/branch/BranchStep.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/branch/BranchStep.java
@@ -18,6 +18,7 @@
*/
package org.apache.tinkerpop.gremlin.process.traversal.step.branch;
+import org.apache.tinkerpop.gremlin.process.traversal.Pick;
import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
import org.apache.tinkerpop.gremlin.process.traversal.Traverser;
import org.apache.tinkerpop.gremlin.process.traversal.lambda.PredicateTraversal;
@@ -63,7 +64,7 @@ public class BranchStep<S, E, M> extends ComputerAwareStep<S, E> implements Trav
}
@Override
- public void addGlobalChildOption(final M pickToken, final Traversal.Admin<S, E> traversalOption) {
+ public void addChildOption(final M pickToken, final Traversal.Admin<S, E> traversalOption) {
if (pickToken instanceof Pick) {
if (this.traversalPickOptions.containsKey(pickToken))
this.traversalPickOptions.get(pickToken).add(traversalOption);
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/branch/ChooseStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/branch/ChooseStep.java
index a804d8f..86ec39e 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/branch/ChooseStep.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/branch/ChooseStep.java
@@ -18,6 +18,7 @@
*/
package org.apache.tinkerpop.gremlin.process.traversal.step.branch;
+import org.apache.tinkerpop.gremlin.process.traversal.Pick;
import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
import org.apache.tinkerpop.gremlin.process.traversal.step.map.HasNextStep;
@@ -36,18 +37,18 @@ public final class ChooseStep<S, E, M> extends BranchStep<S, E, M> {
public ChooseStep(final Traversal.Admin traversal, final Traversal.Admin<S, ?> predicateTraversal, final Traversal.Admin<S, E> trueChoice, final Traversal.Admin<S, E> falseChoice) {
this(traversal, (Traversal.Admin<S, M>) predicateTraversal.addStep(new HasNextStep<>(predicateTraversal)));
- this.addGlobalChildOption((M) Boolean.TRUE, trueChoice);
- this.addGlobalChildOption((M) Boolean.FALSE, falseChoice);
+ this.addChildOption((M) Boolean.TRUE, trueChoice);
+ this.addChildOption((M) Boolean.FALSE, falseChoice);
}
@Override
- public void addGlobalChildOption(final M pickToken, final Traversal.Admin<S, E> traversalOption) {
+ public void addChildOption(final M pickToken, final Traversal.Admin<S, E> traversalOption) {
if (pickToken instanceof Pick) {
if (Pick.any.equals(pickToken))
throw new IllegalArgumentException("Choose step can not have an any-option as only one option per traverser is allowed");
if (this.traversalPickOptions.containsKey(pickToken))
throw new IllegalArgumentException("Choose step can only have one traversal per pick token: " + pickToken);
}
- super.addGlobalChildOption(pickToken, traversalOption);
+ super.addChildOption(pickToken, traversalOption);
}
}
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 de70479..1ce6137 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
@@ -18,8 +18,8 @@
*/
package org.apache.tinkerpop.gremlin.process.traversal.step.branch;
+import org.apache.tinkerpop.gremlin.process.traversal.Pick;
import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
-import org.apache.tinkerpop.gremlin.process.traversal.step.TraversalOptionParent;
import org.apache.tinkerpop.gremlin.process.traversal.lambda.ConstantTraversal;
import org.apache.tinkerpop.gremlin.structure.util.StringFactory;
@@ -28,21 +28,21 @@ import java.util.Collections;
/**
* @author Marko A. Rodriguez (http://markorodriguez.com)
*/
-public final class UnionStep<S, E> extends BranchStep<S, E, TraversalOptionParent.Pick> {
+public final class UnionStep<S, E> extends BranchStep<S, E, Pick> {
public UnionStep(final Traversal.Admin traversal, final Traversal.Admin<?, E>... unionTraversals) {
super(traversal);
this.setBranchTraversal(new ConstantTraversal<>(Pick.any));
for (final Traversal.Admin<?, E> union : unionTraversals) {
- this.addGlobalChildOption(Pick.any, (Traversal.Admin) union);
+ this.addChildOption(Pick.any, (Traversal.Admin) union);
}
}
@Override
- public void addGlobalChildOption(final Pick pickToken, final Traversal.Admin<S, E> traversalOption) {
+ public void addChildOption(final Pick pickToken, final Traversal.Admin<S, E> traversalOption) {
if (Pick.any != pickToken)
throw new IllegalArgumentException("Union step only supports the any token: " + pickToken);
- super.addGlobalChildOption(pickToken, traversalOption);
+ super.addChildOption(pickToken, traversalOption);
}
@Override
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
new file mode 100644
index 0000000..c726490
--- /dev/null
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MergeEdgeStep.java
@@ -0,0 +1,340 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.tinkerpop.gremlin.process.traversal.step.map;
+
+import org.apache.tinkerpop.gremlin.process.traversal.Merge;
+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.process.traversal.lambda.IdentityTraversal;
+import org.apache.tinkerpop.gremlin.process.traversal.step.Mutating;
+import org.apache.tinkerpop.gremlin.process.traversal.step.TraversalOptionParent;
+import org.apache.tinkerpop.gremlin.process.traversal.step.util.Parameters;
+import org.apache.tinkerpop.gremlin.process.traversal.step.util.event.CallbackRegistry;
+import org.apache.tinkerpop.gremlin.process.traversal.step.util.event.Event;
+import org.apache.tinkerpop.gremlin.process.traversal.step.util.event.ListCallbackRegistry;
+import org.apache.tinkerpop.gremlin.process.traversal.strategy.decoration.EventStrategy;
+import org.apache.tinkerpop.gremlin.process.traversal.traverser.TraverserRequirement;
+import org.apache.tinkerpop.gremlin.process.traversal.util.TraversalUtil;
+import org.apache.tinkerpop.gremlin.structure.Direction;
+import org.apache.tinkerpop.gremlin.structure.Edge;
+import org.apache.tinkerpop.gremlin.structure.Graph;
+import org.apache.tinkerpop.gremlin.structure.Property;
+import org.apache.tinkerpop.gremlin.structure.T;
+import org.apache.tinkerpop.gremlin.structure.Vertex;
+import org.apache.tinkerpop.gremlin.structure.VertexProperty;
+import org.apache.tinkerpop.gremlin.structure.util.Attachable;
+import org.apache.tinkerpop.gremlin.structure.util.StringFactory;
+import org.apache.tinkerpop.gremlin.structure.util.empty.EmptyGraph;
+import org.apache.tinkerpop.gremlin.util.iterator.IteratorUtils;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Stream;
+
+/**
+ * Implementation for the {@code mergeE()} step covering both the start step version and the one used mid-traversal.
+ */
+public class MergeEdgeStep<S> extends FlatMapStep<S, Edge> implements Mutating<Event>,
+ TraversalOptionParent<Merge, S, Edge> {
+
+ private final boolean isStart;
+ private boolean first = true;
+ private Traversal.Admin<S,Map<Object, Object>> searchCreateTraversal;
+ private Traversal.Admin<S, Map<Object, Object>> onCreateTraversal = null;
+ private Traversal.Admin<S, Map<String, Object>> onMatchTraversal = null;
+
+ protected CallbackRegistry<Event> callbackRegistry;
+
+ public MergeEdgeStep(final Traversal.Admin traversal, final boolean isStart) {
+ this(traversal, isStart, new IdentityTraversal<>());
+ }
+
+ public MergeEdgeStep(final Traversal.Admin traversal, final boolean isStart, final Map<Object, Object> searchCreate) {
+ this(traversal, isStart, new ConstantTraversal<>(searchCreate));
+ }
+
+ public MergeEdgeStep(final Traversal.Admin traversal, final boolean isStart, final Traversal.Admin<?,Map<Object, Object>> searchCreateTraversal) {
+ super(traversal);
+ this.isStart = isStart;
+ this.searchCreateTraversal = integrateChild(searchCreateTraversal);
+ }
+
+ public Traversal.Admin<S, Map<Object, Object>> getSearchCreateTraversal() {
+ return searchCreateTraversal;
+ }
+
+ public Traversal.Admin<S, Map<Object, Object>> getOnCreateTraversal() {
+ return onCreateTraversal;
+ }
+
+ public Traversal.Admin<S, Map<String, Object>> getOnMatchTraversal() {
+ return onMatchTraversal;
+ }
+
+ /**
+ * Determines if this is a start step.
+ */
+ public boolean isStart() {
+ return isStart;
+ }
+
+ public boolean isFirst() {
+ return first;
+ }
+
+ public CallbackRegistry<Event> getCallbackRegistry() {
+ return callbackRegistry;
+ }
+
+ @Override
+ public void addChildOption(final Merge token, final Traversal.Admin<S, Edge> traversalOption) {
+ if (token == Merge.onCreate) {
+ this.onCreateTraversal = this.integrateChild(traversalOption);
+ } else if (token == Merge.onMatch) {
+ this.onMatchTraversal = this.integrateChild(traversalOption);
+ } else {
+ throw new UnsupportedOperationException(String.format("Option %s for Merge is not supported", token.name()));
+ }
+ }
+
+ @Override
+ public <S, E> List<Traversal.Admin<S, E>> getLocalChildren() {
+ final List<Traversal.Admin<S, E>> children = new ArrayList<>();
+ if (searchCreateTraversal != null) children.add((Traversal.Admin<S, E>) searchCreateTraversal);
+ if (onMatchTraversal != null) children.add((Traversal.Admin<S, E>) onMatchTraversal);
+ if (onCreateTraversal != null) children.add((Traversal.Admin<S, E>) onCreateTraversal);
+ return children;
+ }
+
+ @Override
+ public void configure(final Object... keyValues) {
+ // this is a Mutating step but property() should not be folded into this step. this exception should not
+ // end up visible to users really
+ }
+
+ @Override
+ public Parameters getParameters() {
+ // merge doesn't take fold ups of property() calls. those need to get treated as regular old PropertyStep
+ // instances. not sure if this should support with() though.....none of the other Mutating steps do.
+ return null;
+ }
+
+ @Override
+ protected Traverser.Admin<Edge> 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();
+ }
+
+ /**
+ * Use the {@code Map} of search criteria to most efficiently return a {@code Stream<Edge>} of matching elements.
+ * Providers might override this method when extending this step to provide their own optimized mechanisms for
+ * matching the list of edges. This implementation is only optimized for the {@link T#id} so any other usage
+ * will simply be in-memory filtering which could be slow.
+ */
+ protected Stream<Edge> createSearchStream(final Map<Object,Object> search) {
+ final Graph graph = this.getTraversal().getGraph().get();
+
+ final Optional<Direction> directionUsedInLookup;
+ Stream<Edge> stream;
+ // prioritize lookup by id, then use vertices as starting point if possible
+ if (search.containsKey(T.id)) {
+ stream = IteratorUtils.stream(graph.edges(search.get(T.id)));
+ directionUsedInLookup = Optional.empty();
+ } else if (search.containsKey(Direction.OUT)) {
+ stream = IteratorUtils.stream(graph.vertices(search.get(Direction.OUT))).flatMap(v -> IteratorUtils.stream(v.edges(Direction.OUT)));
+ directionUsedInLookup = Optional.of(Direction.OUT);
+ } else if (search.containsKey(Direction.IN)) {
+ stream = IteratorUtils.stream(graph.vertices(search.get(Direction.IN))).flatMap(v -> IteratorUtils.stream(v.edges(Direction.IN)));
+ directionUsedInLookup = Optional.of(Direction.IN);
+ } else {
+ stream = IteratorUtils.stream(graph.edges());
+ directionUsedInLookup = Optional.empty();
+ }
+
+ // in-memory filter is not going to be nice here. it will be up to graphs to optimize this step as they do
+ // for other Mutation steps
+ stream = stream.filter(e -> {
+ // try to match on all search criteria skipping T.id as it was handled above
+ return search.entrySet().stream().filter(
+ kv -> kv.getKey() != T.id && !(directionUsedInLookup.isPresent() && kv.getKey() == directionUsedInLookup.get())).
+ allMatch(kv -> {
+ if (kv.getKey() == T.label) {
+ return e.label().equals(kv.getValue());
+ } else if (kv.getKey() instanceof Direction) {
+ final Direction direction = (Direction) kv.getKey();
+
+ // try to take advantage of string id conversions of the graph by doing a lookup rather
+ // than direct compare on id
+ final Iterator<Vertex> found = graph.vertices(kv.getValue());
+ return found.hasNext() && e.vertices(direction).next().equals(found.next());
+ } else {
+ final Property<Object> vp = e.property(kv.getKey().toString());
+ return vp.isPresent() && kv.getValue().equals(vp.value());
+ }
+ });
+ });
+
+ return stream;
+ }
+
+ @Override
+ protected Iterator<Edge> flatMap(final Traverser.Admin<S> traverser) {
+ final S possibleVertex = traverser.get();
+ final Map<Object,Object> searchCreate = TraversalUtil.apply(traverser, searchCreateTraversal);
+
+ Vertex outV = (Vertex) searchCreate.getOrDefault(Direction.OUT, possibleVertex);
+ Vertex inV = (Vertex) searchCreate.getOrDefault(Direction.IN, possibleVertex);
+
+ if (inV instanceof Attachable)
+ inV = ((Attachable<Vertex>) inV)
+ .attach(Attachable.Method.getOrCreate(this.getTraversal().getGraph().orElse(EmptyGraph.instance())));
+ if (outV instanceof Attachable)
+ outV = ((Attachable<Vertex>) outV)
+ .attach(Attachable.Method.getOrCreate(this.getTraversal().getGraph().orElse(EmptyGraph.instance())));
+
+ final Vertex fromV = outV;
+ final Vertex toV = inV;
+
+ Stream<Edge> stream = createSearchStream(searchCreate);
+ stream = stream.map(e -> {
+ // if no onMatch is defined then there is no update - return the edge unchanged
+ if (null == onMatchTraversal) return e;
+
+ // assume good input from GraphTraversal - folks might drop in a T here even though it is immutable
+ final Map<String, Object> onMatchMap = TraversalUtil.apply(traverser, onMatchTraversal);
+ onMatchMap.forEach((key, value) -> {
+ // trigger callbacks for eventing - in this case, it's a EdgePropertyChangedEvent. if there's no
+ // registry/callbacks then just set the property
+ if (this.callbackRegistry != null && !callbackRegistry.getCallbacks().isEmpty()) {
+ final EventStrategy eventStrategy = getTraversal().getStrategies().getStrategy(EventStrategy.class).get();
+ final Property<?> p = e.property(key);
+ final Property<Object> oldValue = p.isPresent() ? eventStrategy.detach(e.property(key)) : null;
+ final Event.EdgePropertyChangedEvent vpce = new Event.EdgePropertyChangedEvent(eventStrategy.detach(e), oldValue, value);
+ this.callbackRegistry.getCallbacks().forEach(c -> c.accept(vpce));
+ }
+ e.property(key, value);
+ });
+
+ return e;
+ });
+
+ // if the stream has something then there is a match (possibly updated) and is returned, otherwise a new
+ // edge is created
+ final Iterator<Edge> edges = stream.iterator();
+ if (edges.hasNext()) {
+ return edges;
+ } else {
+ final Edge edge;
+
+ // if there is an onCreateTraversal then the search criteria is ignored for the creation as it is provided
+ // by way of the traversal which will return the Map
+ final Map<Object,Object> m = null == onCreateTraversal ? searchCreate : TraversalUtil.apply(traverser, onCreateTraversal);
+ final List<Object> keyValues = new ArrayList<>();
+ String label = Edge.DEFAULT_LABEL;
+ for (Map.Entry<Object, Object> entry : m.entrySet()) {
+ // skip Direction keys as they are already handled in getting fromV/toV
+ if (entry.getKey() instanceof Direction) continue;
+
+ if (entry.getKey().equals(T.label)) {
+ label = (String) entry.getValue();
+ } else {
+ keyValues.add(entry.getKey());
+ keyValues.add(entry.getValue());
+ }
+ }
+
+ edge = fromV.addEdge(label, toV, keyValues.toArray(new Object[keyValues.size()]));
+
+ // trigger callbacks for eventing - in this case, it's a VertexAddedEvent
+ if (this.callbackRegistry != null && !callbackRegistry.getCallbacks().isEmpty()) {
+ final EventStrategy eventStrategy = getTraversal().getStrategies().getStrategy(EventStrategy.class).get();
+ final Event.EdgeAddedEvent vae = new Event.EdgeAddedEvent(eventStrategy.detach(edge));
+ this.callbackRegistry.getCallbacks().forEach(c -> c.accept(vae));
+ }
+
+ return IteratorUtils.of(edge);
+ }
+ }
+
+ @Override
+ public CallbackRegistry<Event> getMutatingCallbackRegistry() {
+ if (null == callbackRegistry) callbackRegistry = new ListCallbackRegistry<>();
+ return callbackRegistry;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = super.hashCode();
+ if (searchCreateTraversal != null)
+ result ^= searchCreateTraversal.hashCode();
+ if (onCreateTraversal != null)
+ result ^= onCreateTraversal.hashCode();
+ if (onMatchTraversal != null)
+ result ^= onMatchTraversal.hashCode();
+ return result;
+ }
+
+ @Override
+ public void reset() {
+ super.reset();
+ first = true;
+ searchCreateTraversal.reset();
+ if (onCreateTraversal != null) onCreateTraversal.reset();
+ if (onMatchTraversal != null) onMatchTraversal.reset();
+ }
+
+ @Override
+ public Set<TraverserRequirement> getRequirements() {
+ return this.getSelfAndChildRequirements();
+ }
+
+ @Override
+ public String toString() {
+ return StringFactory.stepString(this, searchCreateTraversal, onCreateTraversal, onMatchTraversal);
+ }
+
+ @Override
+ public void setTraversal(final Traversal.Admin<?, ?> parentTraversal) {
+ super.setTraversal(parentTraversal);
+ this.integrateChild(searchCreateTraversal);
+ this.integrateChild(onCreateTraversal);
+ this.integrateChild(onMatchTraversal);
+ }
+
+ @Override
+ public MergeEdgeStep<S> clone() {
+ final MergeEdgeStep<S> clone = (MergeEdgeStep<S>) super.clone();
+ clone.searchCreateTraversal = searchCreateTraversal.clone();
+ clone.onCreateTraversal = onCreateTraversal != null ? onCreateTraversal.clone() : null;
+ clone.onMatchTraversal = onMatchTraversal != null ? onMatchTraversal.clone() : null;
+ return clone;
+ }
+}
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MergeVertexStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MergeVertexStep.java
new file mode 100644
index 0000000..c32a18c
--- /dev/null
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MergeVertexStep.java
@@ -0,0 +1,293 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.tinkerpop.gremlin.process.traversal.step.map;
+
+import org.apache.tinkerpop.gremlin.process.traversal.Merge;
+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.process.traversal.lambda.IdentityTraversal;
+import org.apache.tinkerpop.gremlin.process.traversal.step.Mutating;
+import org.apache.tinkerpop.gremlin.process.traversal.step.TraversalOptionParent;
+import org.apache.tinkerpop.gremlin.process.traversal.step.util.Parameters;
+import org.apache.tinkerpop.gremlin.process.traversal.step.util.event.CallbackRegistry;
+import org.apache.tinkerpop.gremlin.process.traversal.step.util.event.Event;
+import org.apache.tinkerpop.gremlin.process.traversal.step.util.event.ListCallbackRegistry;
+import org.apache.tinkerpop.gremlin.process.traversal.strategy.decoration.EventStrategy;
+import org.apache.tinkerpop.gremlin.process.traversal.traverser.TraverserRequirement;
+import org.apache.tinkerpop.gremlin.process.traversal.util.TraversalUtil;
+import org.apache.tinkerpop.gremlin.structure.Graph;
+import org.apache.tinkerpop.gremlin.structure.Property;
+import org.apache.tinkerpop.gremlin.structure.T;
+import org.apache.tinkerpop.gremlin.structure.Vertex;
+import org.apache.tinkerpop.gremlin.structure.VertexProperty;
+import org.apache.tinkerpop.gremlin.structure.util.StringFactory;
+import org.apache.tinkerpop.gremlin.util.iterator.IteratorUtils;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Stream;
+
+/**
+ * Implementation for the {@code mergeV()} step covering both the start step version and the one used mid-traversal.
+ */
+public class MergeVertexStep<S> extends FlatMapStep<S, Vertex> implements Mutating<Event>,
+ TraversalOptionParent<Merge, S, Vertex> {
+
+ private final boolean isStart;
+ private boolean first = true;
+ private Traversal.Admin<S,Map<Object, Object>> searchCreateTraversal;
+ private Traversal.Admin<S, Map<Object, Object>> onCreateTraversal = null;
+ private Traversal.Admin<S, Map<String, Object>> onMatchTraversal = null;
+
+ protected CallbackRegistry<Event> callbackRegistry;
+
+ public MergeVertexStep(final Traversal.Admin traversal, final boolean isStart) {
+ this(traversal, isStart, new IdentityTraversal());
+ }
+
+ public MergeVertexStep(final Traversal.Admin traversal, final boolean isStart, final Map<Object, Object> searchCreate) {
+ this(traversal, isStart, new ConstantTraversal<>(searchCreate));
+ }
+
+ public MergeVertexStep(final Traversal.Admin traversal, final boolean isStart, final Traversal.Admin<S,Map<Object, Object>> searchCreateTraversal) {
+ super(traversal);
+ this.isStart = isStart;
+ this.searchCreateTraversal = integrateChild(searchCreateTraversal);
+ }
+
+ public Traversal.Admin<S, Map<Object, Object>> getSearchCreateTraversal() {
+ return searchCreateTraversal;
+ }
+
+ public Traversal.Admin<S, Map<Object, Object>> getOnCreateTraversal() {
+ return onCreateTraversal;
+ }
+
+ public Traversal.Admin<S, Map<String, Object>> getOnMatchTraversal() {
+ return onMatchTraversal;
+ }
+
+ /**
+ * Determines if this is a start step.
+ */
+ public boolean isStart() {
+ return isStart;
+ }
+
+ public boolean isFirst() {
+ return first;
+ }
+
+ public CallbackRegistry<Event> getCallbackRegistry() {
+ return callbackRegistry;
+ }
+
+ @Override
+ public void addChildOption(final Merge token, final Traversal.Admin<S, Vertex> traversalOption) {
+ if (token == Merge.onCreate) {
+ this.onCreateTraversal = this.integrateChild(traversalOption);
+ } else if (token == Merge.onMatch) {
+ this.onMatchTraversal = this.integrateChild(traversalOption);
+ } else {
+ throw new UnsupportedOperationException(String.format("Option %s for Merge is not supported", token.name()));
+ }
+ }
+
+ @Override
+ public <S, E> List<Traversal.Admin<S, E>> getLocalChildren() {
+ final List<Traversal.Admin<S, E>> children = new ArrayList<>();
+ if (searchCreateTraversal != null) children.add((Traversal.Admin<S, E>) searchCreateTraversal);
+ if (onMatchTraversal != null) children.add((Traversal.Admin<S, E>) onMatchTraversal);
+ if (onCreateTraversal != null) children.add((Traversal.Admin<S, E>) onCreateTraversal);
+ return children;
+ }
+
+ @Override
+ public void configure(final Object... keyValues) {
+ // this is a Mutating step but property() should not be folded into this step. this exception should not
+ // end up visible to users really
+ }
+
+ @Override
+ public Parameters getParameters() {
+ // merge doesn't take fold ups of property() calls. those need to get treated as regular old PropertyStep
+ // instances. not sure if this should support with() though.....none of the other Mutating steps do.
+ return null;
+ }
+
+ @Override
+ protected Traverser.Admin<Vertex> 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();
+ }
+
+ /**
+ * Use the {@code Map} of search criteria to most efficiently return a {@code Stream<Vertex>} of matching elements.
+ * Providers might override this method when extending this step to provide their own optimized mechanisms for
+ * matching the list of vertices. This implementation is only optimized for the {@link T#id} so any other usage
+ * will simply be in-memory filtering which could be slow.
+ */
+ protected Stream<Vertex> createSearchStream(final Map<Object,Object> search) {
+ final Graph graph = this.getTraversal().getGraph().get();
+
+ Stream<Vertex> stream;
+ // prioritize lookup by id
+ if (search.containsKey(T.id))
+ stream = IteratorUtils.stream(graph.vertices(search.get(T.id)));
+ else
+ stream = IteratorUtils.stream(graph.vertices());
+
+ // in-memory filter is not going to be nice here. it will be up to graphs to optimize this step as they do
+ // for other Mutation steps
+ stream = stream.filter(v -> {
+ // try to match on all search criteria skipping T.id as it was handled above
+ return search.entrySet().stream().filter(kv -> kv.getKey() != T.id).allMatch(kv -> {
+ if (kv.getKey() == T.label) {
+ return v.label().equals(kv.getValue());
+ } else {
+ final VertexProperty<Object> vp = v.property(kv.getKey().toString());
+ return vp.isPresent() && kv.getValue().equals(vp.value());
+ }
+ });
+ });
+
+ return stream;
+ }
+
+ @Override
+ protected Iterator<Vertex> flatMap(final Traverser.Admin<S> traverser) {
+ final Map<Object,Object> searchCreate = TraversalUtil.apply(traverser, searchCreateTraversal);
+
+ Stream<Vertex> stream = createSearchStream(searchCreate);
+ stream = stream.map(v -> {
+ // if no onMatch is defined then there is no update - return the vertex unchanged
+ if (null == onMatchTraversal) return v;
+
+ // assume good input from GraphTraversal - folks might drop in a T here even though it is immutable
+ final Map<String, Object> onMatchMap = TraversalUtil.apply(traverser, onMatchTraversal);
+ onMatchMap.forEach((key, value) -> {
+ // trigger callbacks for eventing - in this case, it's a VertexPropertyChangedEvent. if there's no
+ // registry/callbacks then just set the property
+ if (this.callbackRegistry != null && !callbackRegistry.getCallbacks().isEmpty()) {
+ final EventStrategy eventStrategy = getTraversal().getStrategies().getStrategy(EventStrategy.class).get();
+ final Property<?> p = v.property(key);
+ final Property<Object> oldValue = p.isPresent() ? eventStrategy.detach(v.property(key)) : null;
+ final Event.VertexPropertyChangedEvent vpce = new Event.VertexPropertyChangedEvent(eventStrategy.detach(v), oldValue, value);
+ this.callbackRegistry.getCallbacks().forEach(c -> c.accept(vpce));
+ }
+ v.property(key, value);
+ });
+
+ return v;
+ });
+
+ // if the stream has something then there is a match (possibly updated) and is returned, otherwise a new
+ // vertex is created
+ final Iterator<Vertex> vertices = stream.iterator();
+ if (vertices.hasNext()) {
+ return vertices;
+ } else {
+ final Vertex vertex;
+
+ // if there is an onCreateTraversal then the search criteria is ignored for the creation as it is provided
+ // by way of the traversal which will return the Map
+ final Map<Object,Object> m = null == onCreateTraversal ? searchCreate : TraversalUtil.apply(traverser, onCreateTraversal);
+ final List<Object> keyValues = new ArrayList<>();
+ for (Map.Entry<Object, Object> entry : m.entrySet()) {
+ keyValues.add(entry.getKey());
+ keyValues.add(entry.getValue());
+ }
+ vertex = this.getTraversal().getGraph().get().addVertex(keyValues.toArray(new Object[keyValues.size()]));
+
+ // trigger callbacks for eventing - in this case, it's a VertexAddedEvent
+ if (this.callbackRegistry != null && !callbackRegistry.getCallbacks().isEmpty()) {
+ final EventStrategy eventStrategy = getTraversal().getStrategies().getStrategy(EventStrategy.class).get();
+ final Event.VertexAddedEvent vae = new Event.VertexAddedEvent(eventStrategy.detach(vertex));
+ this.callbackRegistry.getCallbacks().forEach(c -> c.accept(vae));
+ }
+
+ return IteratorUtils.of(vertex);
+ }
+ }
+
+ @Override
+ public CallbackRegistry<Event> getMutatingCallbackRegistry() {
+ if (null == callbackRegistry) callbackRegistry = new ListCallbackRegistry<>();
+ return callbackRegistry;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = super.hashCode();
+ if (searchCreateTraversal != null)
+ result ^= searchCreateTraversal.hashCode();
+ if (onCreateTraversal != null)
+ result ^= onCreateTraversal.hashCode();
+ if (onMatchTraversal != null)
+ result ^= onMatchTraversal.hashCode();
+ return result;
+ }
+
+ @Override
+ public void reset() {
+ super.reset();
+ first = true;
+ searchCreateTraversal.reset();
+ if (onCreateTraversal != null) onCreateTraversal.reset();
+ if (onMatchTraversal != null) onMatchTraversal.reset();
+ }
+
+ @Override
+ public Set<TraverserRequirement> getRequirements() {
+ return this.getSelfAndChildRequirements();
+ }
+
+ @Override
+ public String toString() {
+ return StringFactory.stepString(this, searchCreateTraversal, onCreateTraversal, onMatchTraversal);
+ }
+
+ @Override
+ public void setTraversal(final Traversal.Admin<?, ?> parentTraversal) {
+ super.setTraversal(parentTraversal);
+ this.integrateChild(searchCreateTraversal);
+ this.integrateChild(onCreateTraversal);
+ this.integrateChild(onMatchTraversal);
+ }
+
+ @Override
+ public MergeVertexStep<S> clone() {
+ final MergeVertexStep<S> clone = (MergeVertexStep<S>) super.clone();
+ clone.searchCreateTraversal = searchCreateTraversal.clone();
+ clone.onCreateTraversal = onCreateTraversal != null ? onCreateTraversal.clone() : null;
+ clone.onMatchTraversal = onMatchTraversal != null ? onMatchTraversal.clone() : null;
+ return clone;
+ }
+}
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/util/event/Event.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/util/event/Event.java
index 732c30b..fa34dfa 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/util/event/Event.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/util/event/Event.java
@@ -129,7 +129,8 @@ public interface Event {
}
/**
- * Represents an action where a {@link VertexProperty} is modified on a {@link Vertex}.
+ * Represents an action where a {@link VertexProperty} is modified on a {@link Vertex}. If the {@link Property} is
+ * new then the {@code oldValue} will be {@code null}.
*/
class VertexPropertyChangedEvent extends ElementPropertyChangedEvent {
@@ -144,7 +145,8 @@ public interface Event {
}
/**
- * Represents an action where a {@link Property} is modified on a {@link VertexProperty}.
+ * Represents an action where a {@link Property} is modified on a {@link VertexProperty}. If the {@link Property} is
+ * new then the {@code oldValue} will be {@code null}.
*/
class VertexPropertyPropertyChangedEvent extends ElementPropertyChangedEvent {
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/translator/DotNetTranslator.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/translator/DotNetTranslator.java
index 4f375e5..6bd3442 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/translator/DotNetTranslator.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/translator/DotNetTranslator.java
@@ -19,18 +19,19 @@
package org.apache.tinkerpop.gremlin.process.traversal.translator;
-import org.apache.commons.configuration2.ConfigurationConverter;
import org.apache.commons.text.StringEscapeUtils;
import org.apache.tinkerpop.gremlin.process.traversal.Bytecode;
+import org.apache.tinkerpop.gremlin.process.traversal.Merge;
import org.apache.tinkerpop.gremlin.process.traversal.P;
+import org.apache.tinkerpop.gremlin.process.traversal.Pick;
import org.apache.tinkerpop.gremlin.process.traversal.SackFunctions;
import org.apache.tinkerpop.gremlin.process.traversal.Script;
import org.apache.tinkerpop.gremlin.process.traversal.TextP;
import org.apache.tinkerpop.gremlin.process.traversal.Translator;
+import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
import org.apache.tinkerpop.gremlin.process.traversal.TraversalSource;
import org.apache.tinkerpop.gremlin.process.traversal.TraversalStrategy;
import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal;
-import org.apache.tinkerpop.gremlin.process.traversal.step.TraversalOptionParent;
import org.apache.tinkerpop.gremlin.process.traversal.strategy.TraversalStrategyProxy;
import org.apache.tinkerpop.gremlin.process.traversal.util.ConnectiveP;
import org.apache.tinkerpop.gremlin.process.traversal.util.OrP;
@@ -171,7 +172,7 @@ public final class DotNetTranslator implements Translator.ScriptTranslator {
}
@Override
- protected String getSyntax(final TraversalOptionParent.Pick o) {
+ protected String getSyntax(final Pick o) {
return "Pick." + SymbolHelper.toCSharp(o.toString());
}
@@ -346,6 +347,33 @@ public final class DotNetTranslator implements Translator.ScriptTranslator {
script.append(", (").append(castSecondArgTo).append(") ");
convertToScript(instruction.getArguments()[1]);
script.append(",");
+ } else if (methodName.equals(GraphTraversal.Symbols.mergeE) || methodName.equals(GraphTraversal.Symbols.mergeV)) {
+ // there must be at least one argument - if null go with Map
+ final Object instArg = instruction.getArguments()[0];
+ if (null == instArg) {
+ script.append("(IDictionary<object,object>) null");
+ } else {
+ if (instArg instanceof Traversal) {
+ script.append("(ITraversal) ");
+ } else {
+ script.append("(IDictionary<object,object>) ");
+ }
+ convertToScript(instArg);
+ script.append(")");
+ }
+ } else if (methodName.equals(GraphTraversal.Symbols.option) &&
+ instruction.getArguments().length == 2 && instruction.getArguments()[0] instanceof Merge) {
+ final Object[] instArgs = instruction.getArguments();
+ // trying to catch option(Merge,Traversal|Map)
+ convertToScript(instArgs[0]);
+ script.append(", ");
+ if (instArgs[1] instanceof Traversal) {
+ script.append("(ITraversal) ");
+ } else {
+ script.append("(IDictionary<object,object>) ");
+ }
+ convertToScript(instArgs[1]);
+ script.append(")");
} else {
final Object[] instArgs = instruction.getArguments();
for (int idx = 0; idx < instArgs.length; idx++) {
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/translator/GroovyTranslator.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/translator/GroovyTranslator.java
index ef544b4..0143c27 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/translator/GroovyTranslator.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/translator/GroovyTranslator.java
@@ -24,14 +24,12 @@ import org.apache.commons.text.StringEscapeUtils;
import org.apache.tinkerpop.gremlin.jsr223.CoreImports;
import org.apache.tinkerpop.gremlin.process.traversal.Bytecode;
import org.apache.tinkerpop.gremlin.process.traversal.P;
+import org.apache.tinkerpop.gremlin.process.traversal.Pick;
import org.apache.tinkerpop.gremlin.process.traversal.SackFunctions;
import org.apache.tinkerpop.gremlin.process.traversal.Script;
import org.apache.tinkerpop.gremlin.process.traversal.TextP;
import org.apache.tinkerpop.gremlin.process.traversal.Translator;
-import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
-import org.apache.tinkerpop.gremlin.process.traversal.TraversalSource;
import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal;
-import org.apache.tinkerpop.gremlin.process.traversal.step.TraversalOptionParent;
import org.apache.tinkerpop.gremlin.process.traversal.strategy.TraversalStrategyProxy;
import org.apache.tinkerpop.gremlin.process.traversal.util.ConnectiveP;
import org.apache.tinkerpop.gremlin.process.traversal.util.OrP;
@@ -175,8 +173,8 @@ public final class GroovyTranslator implements Translator.ScriptTranslator {
}
@Override
- protected String getSyntax(final TraversalOptionParent.Pick o) {
- return "TraversalOptionParent.Pick." + o.toString();
+ protected String getSyntax(final Pick o) {
+ return "Pick." + o.toString();
}
@Override
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/translator/JavascriptTranslator.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/translator/JavascriptTranslator.java
index e9cf748..f95d288 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/translator/JavascriptTranslator.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/translator/JavascriptTranslator.java
@@ -23,13 +23,13 @@ import org.apache.commons.configuration2.ConfigurationConverter;
import org.apache.commons.text.StringEscapeUtils;
import org.apache.tinkerpop.gremlin.process.traversal.Bytecode;
import org.apache.tinkerpop.gremlin.process.traversal.P;
+import org.apache.tinkerpop.gremlin.process.traversal.Pick;
import org.apache.tinkerpop.gremlin.process.traversal.SackFunctions;
import org.apache.tinkerpop.gremlin.process.traversal.Script;
import org.apache.tinkerpop.gremlin.process.traversal.TextP;
import org.apache.tinkerpop.gremlin.process.traversal.Translator;
import org.apache.tinkerpop.gremlin.process.traversal.TraversalSource;
import org.apache.tinkerpop.gremlin.process.traversal.TraversalStrategy;
-import org.apache.tinkerpop.gremlin.process.traversal.step.TraversalOptionParent;
import org.apache.tinkerpop.gremlin.process.traversal.strategy.TraversalStrategyProxy;
import org.apache.tinkerpop.gremlin.process.traversal.util.ConnectiveP;
import org.apache.tinkerpop.gremlin.process.traversal.util.OrP;
@@ -167,7 +167,7 @@ public final class JavascriptTranslator implements Translator.ScriptTranslator {
}
@Override
- protected String getSyntax(final TraversalOptionParent.Pick o) {
+ protected String getSyntax(final Pick o) {
return "Pick." + o.toString();
}
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/translator/PythonTranslator.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/translator/PythonTranslator.java
index 09e4447..987fa3e 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/translator/PythonTranslator.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/translator/PythonTranslator.java
@@ -23,6 +23,7 @@ import org.apache.commons.configuration2.ConfigurationConverter;
import org.apache.commons.text.StringEscapeUtils;
import org.apache.tinkerpop.gremlin.process.traversal.Bytecode;
import org.apache.tinkerpop.gremlin.process.traversal.P;
+import org.apache.tinkerpop.gremlin.process.traversal.Pick;
import org.apache.tinkerpop.gremlin.process.traversal.SackFunctions;
import org.apache.tinkerpop.gremlin.process.traversal.Script;
import org.apache.tinkerpop.gremlin.process.traversal.TextP;
@@ -30,7 +31,6 @@ import org.apache.tinkerpop.gremlin.process.traversal.Translator;
import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
import org.apache.tinkerpop.gremlin.process.traversal.TraversalSource;
import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal;
-import org.apache.tinkerpop.gremlin.process.traversal.step.TraversalOptionParent;
import org.apache.tinkerpop.gremlin.process.traversal.strategy.TraversalStrategyProxy;
import org.apache.tinkerpop.gremlin.process.traversal.util.ConnectiveP;
import org.apache.tinkerpop.gremlin.process.traversal.util.OrP;
@@ -188,7 +188,7 @@ public final class PythonTranslator implements Translator.ScriptTranslator {
}
@Override
- protected String getSyntax(final TraversalOptionParent.Pick o) {
+ protected String getSyntax(final Pick o) {
return "Pick." + resolveSymbol(o.toString());
}
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/binary/DataType.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/binary/DataType.java
index 0159d4f..75b8060 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/binary/DataType.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/binary/DataType.java
@@ -70,6 +70,7 @@ public enum DataType {
TREE(0X2B),
METRICS(0x2C),
TRAVERSALMETRICS(0x2D),
+ MERGE(0x2E),
CHAR(0X80),
DURATION(0X81),
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/binary/TypeSerializerRegistry.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/binary/TypeSerializerRegistry.java
index ced0088..4edcad2 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/binary/TypeSerializerRegistry.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/binary/TypeSerializerRegistry.java
@@ -18,6 +18,8 @@
*/
package org.apache.tinkerpop.gremlin.structure.io.binary;
+import org.apache.tinkerpop.gremlin.process.traversal.Merge;
+import org.apache.tinkerpop.gremlin.process.traversal.Pick;
import org.apache.tinkerpop.gremlin.structure.io.binary.types.*;
import org.apache.tinkerpop.gremlin.process.traversal.Bytecode;
import org.apache.tinkerpop.gremlin.process.traversal.Operator;
@@ -30,7 +32,6 @@ import org.apache.tinkerpop.gremlin.process.traversal.Scope;
import org.apache.tinkerpop.gremlin.process.traversal.TextP;
import org.apache.tinkerpop.gremlin.process.traversal.TraversalStrategy;
import org.apache.tinkerpop.gremlin.process.traversal.Traverser;
-import org.apache.tinkerpop.gremlin.process.traversal.step.TraversalOptionParent;
import org.apache.tinkerpop.gremlin.process.traversal.step.util.BulkSet;
import org.apache.tinkerpop.gremlin.process.traversal.step.util.Tree;
import org.apache.tinkerpop.gremlin.process.traversal.util.AndP;
@@ -117,9 +118,10 @@ public class TypeSerializerRegistry {
new RegistryEntry<>(VertexProperty.Cardinality.class, EnumSerializer.CardinalitySerializer),
new RegistryEntry<>(Column.class, EnumSerializer.ColumnSerializer),
new RegistryEntry<>(Direction.class, EnumSerializer.DirectionSerializer),
+ new RegistryEntry<>(Merge.class, EnumSerializer.MergeSerializer),
new RegistryEntry<>(Operator.class, EnumSerializer.OperatorSerializer),
new RegistryEntry<>(Order.class, EnumSerializer.OrderSerializer),
- new RegistryEntry<>(TraversalOptionParent.Pick.class, EnumSerializer.PickSerializer),
+ new RegistryEntry<>(Pick.class, EnumSerializer.PickSerializer),
new RegistryEntry<>(Pop.class, EnumSerializer.PopSerializer),
new RegistryEntry<>(Lambda.class, new LambdaSerializer()),
new RegistryEntry<>(P.class, new PSerializer<>(DataType.P, P.class)),
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/binary/types/EnumSerializer.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/binary/types/EnumSerializer.java
index 12e2d15..41cf7fc 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/binary/types/EnumSerializer.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/binary/types/EnumSerializer.java
@@ -18,6 +18,8 @@
*/
package org.apache.tinkerpop.gremlin.structure.io.binary.types;
+import org.apache.tinkerpop.gremlin.process.traversal.Merge;
+import org.apache.tinkerpop.gremlin.process.traversal.Pick;
import org.apache.tinkerpop.gremlin.structure.io.binary.DataType;
import org.apache.tinkerpop.gremlin.structure.io.binary.GraphBinaryReader;
import org.apache.tinkerpop.gremlin.structure.io.binary.GraphBinaryWriter;
@@ -26,7 +28,6 @@ import org.apache.tinkerpop.gremlin.process.traversal.Order;
import org.apache.tinkerpop.gremlin.process.traversal.Pop;
import org.apache.tinkerpop.gremlin.process.traversal.SackFunctions;
import org.apache.tinkerpop.gremlin.process.traversal.Scope;
-import org.apache.tinkerpop.gremlin.process.traversal.step.TraversalOptionParent;
import org.apache.tinkerpop.gremlin.structure.Column;
import org.apache.tinkerpop.gremlin.structure.Direction;
import org.apache.tinkerpop.gremlin.structure.T;
@@ -47,9 +48,10 @@ public class EnumSerializer<E extends Enum> extends SimpleTypeSerializer<E> {
public static final EnumSerializer<VertexProperty.Cardinality> CardinalitySerializer = new EnumSerializer<>(DataType.CARDINALITY, VertexProperty.Cardinality::valueOf);
public static final EnumSerializer<Column> ColumnSerializer = new EnumSerializer<>(DataType.COLUMN, Column::valueOf);
public static final EnumSerializer<Direction> DirectionSerializer = new EnumSerializer<>(DataType.DIRECTION, Direction::valueOf);
+ public static final EnumSerializer<Merge> MergeSerializer = new EnumSerializer<>(DataType.MERGE, Merge::valueOf);
public static final EnumSerializer<Operator> OperatorSerializer = new EnumSerializer<>(DataType.OPERATOR, Operator::valueOf);
public static final EnumSerializer<Order> OrderSerializer = new EnumSerializer<>(DataType.ORDER, Order::valueOf);
- public static final EnumSerializer<TraversalOptionParent.Pick> PickSerializer = new EnumSerializer<>(DataType.PICK, TraversalOptionParent.Pick::valueOf);
+ public static final EnumSerializer<Pick> PickSerializer = new EnumSerializer<>(DataType.PICK, Pick::valueOf);
public static final EnumSerializer<Pop> PopSerializer = new EnumSerializer<>(DataType.POP, Pop::valueOf);
public static final EnumSerializer<Scope> ScopeSerializer = new EnumSerializer<>(DataType.SCOPE, Scope::valueOf);
public static final EnumSerializer<T> TSerializer = new EnumSerializer<>(DataType.T, T::valueOf);
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/GraphSONModule.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/GraphSONModule.java
index eda0d37..82d818f 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/GraphSONModule.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/GraphSONModule.java
@@ -21,10 +21,12 @@ package org.apache.tinkerpop.gremlin.structure.io.graphson;
import org.apache.tinkerpop.gremlin.process.computer.traversal.strategy.decoration.VertexProgramStrategy;
import org.apache.tinkerpop.gremlin.process.computer.traversal.strategy.optimization.GraphFilterStrategy;
import org.apache.tinkerpop.gremlin.process.traversal.Bytecode;
+import org.apache.tinkerpop.gremlin.process.traversal.Merge;
import org.apache.tinkerpop.gremlin.process.traversal.Operator;
import org.apache.tinkerpop.gremlin.process.traversal.Order;
import org.apache.tinkerpop.gremlin.process.traversal.P;
import org.apache.tinkerpop.gremlin.process.traversal.Path;
+import org.apache.tinkerpop.gremlin.process.traversal.Pick;
import org.apache.tinkerpop.gremlin.process.traversal.Pop;
import org.apache.tinkerpop.gremlin.process.traversal.SackFunctions;
import org.apache.tinkerpop.gremlin.process.traversal.Scope;
@@ -32,7 +34,6 @@ import org.apache.tinkerpop.gremlin.process.traversal.TextP;
import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
import org.apache.tinkerpop.gremlin.process.traversal.TraversalStrategy;
import org.apache.tinkerpop.gremlin.process.traversal.Traverser;
-import org.apache.tinkerpop.gremlin.process.traversal.step.TraversalOptionParent;
import org.apache.tinkerpop.gremlin.process.traversal.step.util.BulkSet;
import org.apache.tinkerpop.gremlin.process.traversal.step.util.Tree;
import org.apache.tinkerpop.gremlin.process.traversal.strategy.decoration.ConnectiveStrategy;
@@ -174,11 +175,12 @@ abstract class GraphSONModule extends TinkerPopJacksonModule {
VertexProperty.Cardinality.class,
Column.class,
Direction.class,
+ Merge.class,
Operator.class,
Order.class,
Pop.class,
SackFunctions.Barrier.class,
- TraversalOptionParent.Pick.class,
+ Pick.class,
Scope.class,
T.class).forEach(e -> put(e, e.getSimpleName()));
Arrays.asList(
@@ -257,12 +259,13 @@ abstract class GraphSONModule extends TinkerPopJacksonModule {
Stream.of(VertexProperty.Cardinality.class,
Column.class,
Direction.class,
+ Merge.class,
Operator.class,
Order.class,
Pop.class,
SackFunctions.Barrier.class,
Scope.class,
- TraversalOptionParent.Pick.class,
+ Pick.class,
T.class).forEach(e -> addSerializer(e, new TraversalSerializersV3d0.EnumJacksonSerializer()));
addSerializer(P.class, new TraversalSerializersV3d0.PJacksonSerializer());
addSerializer(Lambda.class, new TraversalSerializersV3d0.LambdaJacksonSerializer());
@@ -299,12 +302,13 @@ abstract class GraphSONModule extends TinkerPopJacksonModule {
Stream.of(VertexProperty.Cardinality.values(),
Column.values(),
Direction.values(),
+ Merge.values(),
Operator.values(),
Order.values(),
Pop.values(),
SackFunctions.Barrier.values(),
Scope.values(),
- TraversalOptionParent.Pick.values(),
+ Pick.values(),
T.values()).flatMap(Stream::of).forEach(e -> addDeserializer(e.getClass(), new TraversalSerializersV3d0.EnumJacksonDeserializer(e.getDeclaringClass())));
addDeserializer(P.class, new TraversalSerializersV3d0.PJacksonDeserializer());
addDeserializer(TextP.class, new TraversalSerializersV3d0.TextPJacksonDeserializer());
@@ -412,11 +416,12 @@ abstract class GraphSONModule extends TinkerPopJacksonModule {
VertexProperty.Cardinality.class,
Column.class,
Direction.class,
+ Merge.class,
Operator.class,
Order.class,
Pop.class,
SackFunctions.Barrier.class,
- TraversalOptionParent.Pick.class,
+ Pick.class,
Scope.class,
T.class).forEach(e -> put(e, e.getSimpleName()));
Arrays.asList(
@@ -491,12 +496,13 @@ abstract class GraphSONModule extends TinkerPopJacksonModule {
Stream.of(VertexProperty.Cardinality.class,
Column.class,
Direction.class,
+ Merge.class,
Operator.class,
Order.class,
Pop.class,
SackFunctions.Barrier.class,
Scope.class,
- TraversalOptionParent.Pick.class,
+ Pick.class,
T.class).forEach(e -> addSerializer(e, new TraversalSerializersV2d0.EnumJacksonSerializer()));
addSerializer(P.class, new TraversalSerializersV2d0.PJacksonSerializer());
addSerializer(Lambda.class, new TraversalSerializersV2d0.LambdaJacksonSerializer());
@@ -527,12 +533,13 @@ abstract class GraphSONModule extends TinkerPopJacksonModule {
Stream.of(VertexProperty.Cardinality.values(),
Column.values(),
Direction.values(),
+ Merge.values(),
Operator.values(),
Order.values(),
Pop.values(),
SackFunctions.Barrier.values(),
Scope.values(),
- TraversalOptionParent.Pick.values(),
+ Pick.values(),
T.values()).flatMap(Stream::of).forEach(e -> addDeserializer(e.getClass(), new TraversalSerializersV2d0.EnumJacksonDeserializer(e.getDeclaringClass())));
addDeserializer(P.class, new TraversalSerializersV2d0.PJacksonDeserializer());
addDeserializer(TextP.class, new TraversalSerializersV2d0.TextPJacksonDeserializer());
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/GraphSONTypeSerializerV2d0.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/GraphSONTypeSerializerV2d0.java
index 3fdb50a..e50037c 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/GraphSONTypeSerializerV2d0.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/GraphSONTypeSerializerV2d0.java
@@ -21,11 +21,11 @@ package org.apache.tinkerpop.gremlin.structure.io.graphson;
import org.apache.tinkerpop.gremlin.process.traversal.Operator;
import org.apache.tinkerpop.gremlin.process.traversal.Order;
import org.apache.tinkerpop.gremlin.process.traversal.Path;
+import org.apache.tinkerpop.gremlin.process.traversal.Pick;
import org.apache.tinkerpop.gremlin.process.traversal.Pop;
import org.apache.tinkerpop.gremlin.process.traversal.SackFunctions;
import org.apache.tinkerpop.gremlin.process.traversal.Scope;
import org.apache.tinkerpop.gremlin.process.traversal.Traverser;
-import org.apache.tinkerpop.gremlin.process.traversal.step.TraversalOptionParent;
import org.apache.tinkerpop.gremlin.process.traversal.util.Metrics;
import org.apache.tinkerpop.gremlin.process.traversal.util.TraversalMetrics;
import org.apache.tinkerpop.gremlin.structure.Column;
@@ -131,8 +131,8 @@ public class GraphSONTypeSerializerV2d0 extends AbstractGraphSONTypeSerializer {
mapped = Pop.class;
else if (SackFunctions.Barrier.class.isAssignableFrom(c))
mapped = SackFunctions.Barrier.class;
- else if (TraversalOptionParent.Pick.class.isAssignableFrom(c))
- mapped = TraversalOptionParent.Pick.class;
+ else if (Pick.class.isAssignableFrom(c))
+ mapped = Pick.class;
else if (Scope.class.isAssignableFrom(c))
mapped = Scope.class;
else if (T.class.isAssignableFrom(c))
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/GraphSONTypeSerializerV3d0.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/GraphSONTypeSerializerV3d0.java
index eca91ef..4349e47 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/GraphSONTypeSerializerV3d0.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/GraphSONTypeSerializerV3d0.java
@@ -21,11 +21,11 @@ package org.apache.tinkerpop.gremlin.structure.io.graphson;
import org.apache.tinkerpop.gremlin.process.traversal.Operator;
import org.apache.tinkerpop.gremlin.process.traversal.Order;
import org.apache.tinkerpop.gremlin.process.traversal.Path;
+import org.apache.tinkerpop.gremlin.process.traversal.Pick;
import org.apache.tinkerpop.gremlin.process.traversal.Pop;
import org.apache.tinkerpop.gremlin.process.traversal.SackFunctions;
import org.apache.tinkerpop.gremlin.process.traversal.Scope;
import org.apache.tinkerpop.gremlin.process.traversal.Traverser;
-import org.apache.tinkerpop.gremlin.process.traversal.step.TraversalOptionParent;
import org.apache.tinkerpop.gremlin.process.traversal.step.util.BulkSet;
import org.apache.tinkerpop.gremlin.process.traversal.step.util.Tree;
import org.apache.tinkerpop.gremlin.process.traversal.util.Metrics;
@@ -165,8 +165,8 @@ public class GraphSONTypeSerializerV3d0 extends AbstractGraphSONTypeSerializer {
mapped = Pop.class;
else if (SackFunctions.Barrier.class.isAssignableFrom(c))
mapped = SackFunctions.Barrier.class;
- else if (TraversalOptionParent.Pick.class.isAssignableFrom(c))
- mapped = TraversalOptionParent.Pick.class;
+ else if (Pick.class.isAssignableFrom(c))
+ mapped = Pick.class;
else if (Scope.class.isAssignableFrom(c))
mapped = Scope.class;
else if (T.class.isAssignableFrom(c))
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/gryo/GryoVersion.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/gryo/GryoVersion.java
index bd2b1b0..a5774bb 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/gryo/GryoVersion.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/gryo/GryoVersion.java
@@ -26,15 +26,16 @@ import org.apache.tinkerpop.gremlin.process.computer.util.MapMemory;
import org.apache.tinkerpop.gremlin.process.remote.traversal.DefaultRemoteTraverser;
import org.apache.tinkerpop.gremlin.process.traversal.Bytecode;
import org.apache.tinkerpop.gremlin.process.traversal.Contains;
+import org.apache.tinkerpop.gremlin.process.traversal.Merge;
import org.apache.tinkerpop.gremlin.process.traversal.Operator;
import org.apache.tinkerpop.gremlin.process.traversal.Order;
import org.apache.tinkerpop.gremlin.process.traversal.P;
import org.apache.tinkerpop.gremlin.process.traversal.Path;
+import org.apache.tinkerpop.gremlin.process.traversal.Pick;
import org.apache.tinkerpop.gremlin.process.traversal.Pop;
import org.apache.tinkerpop.gremlin.process.traversal.SackFunctions;
import org.apache.tinkerpop.gremlin.process.traversal.Scope;
import org.apache.tinkerpop.gremlin.process.traversal.TextP;
-import org.apache.tinkerpop.gremlin.process.traversal.step.TraversalOptionParent;
import org.apache.tinkerpop.gremlin.process.traversal.step.filter.RangeGlobalStep;
import org.apache.tinkerpop.gremlin.process.traversal.step.map.FoldStep;
import org.apache.tinkerpop.gremlin.process.traversal.step.map.GroupCountStep;
@@ -329,7 +330,8 @@ public enum GryoVersion {
add(GryoTypeReg.of(Column.class, 132));
add(GryoTypeReg.of(Pop.class, 133));
add(GryoTypeReg.of(SackFunctions.Barrier.class, 135));
- add(GryoTypeReg.of(TraversalOptionParent.Pick.class, 137));
+ add(GryoTypeReg.of(Pick.class, 137));
+ add(GryoTypeReg.of(Merge.class, 196)); // ***LAST ID***
add(GryoTypeReg.of(HashSetSupplier.class, 136, new UtilSerializers.HashSetSupplierSerializer()));
add(GryoTypeReg.of(MultiComparator.class, 165));
@@ -343,7 +345,7 @@ public enum GryoVersion {
add(GryoTypeReg.of(MatchStep.GreedyMatchAlgorithm.class, 144));
add(GryoTypeReg.of(AdjacentToIncidentStrategy.class, 145));
add(GryoTypeReg.of(ByModulatorOptimizationStrategy.class, 191));
- add(GryoTypeReg.of(ProductiveByStrategy.class, 195, new JavaSerializer())); // ***LAST ID***
+ add(GryoTypeReg.of(ProductiveByStrategy.class, 195, new JavaSerializer()));
add(GryoTypeReg.of(CountStrategy.class, 155));
add(GryoTypeReg.of(FilterRankingStrategy.class, 146));
add(GryoTypeReg.of(IdentityRemovalStrategy.class, 147));
@@ -529,7 +531,8 @@ public enum GryoVersion {
add(GryoTypeReg.of(Column.class, 132));
add(GryoTypeReg.of(Pop.class, 133));
add(GryoTypeReg.of(SackFunctions.Barrier.class, 135));
- add(GryoTypeReg.of(TraversalOptionParent.Pick.class, 137));
+ add(GryoTypeReg.of(Pick.class, 137));
+ add(GryoTypeReg.of(Merge.class, 196)); // ***LAST ID***
add(GryoTypeReg.of(HashSetSupplier.class, 136, new UtilSerializers.HashSetSupplierSerializer()));
add(GryoTypeReg.of(MultiComparator.class, 165));
@@ -586,7 +589,7 @@ public enum GryoVersion {
add(GryoTypeReg.of(MatchStep.GreedyMatchAlgorithm.class, 144));
add(GryoTypeReg.of(AdjacentToIncidentStrategy.class, 145));
add(GryoTypeReg.of(ByModulatorOptimizationStrategy.class, 191));
- add(GryoTypeReg.of(ProductiveByStrategy.class, 195, new JavaSerializer())); // ***LAST ID***
+ add(GryoTypeReg.of(ProductiveByStrategy.class, 195, new JavaSerializer()));
add(GryoTypeReg.of(CountStrategy.class, 155));
add(GryoTypeReg.of(FilterRankingStrategy.class, 146));
add(GryoTypeReg.of(IdentityRemovalStrategy.class, 147));
diff --git a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/language/grammar/TraversalEnumParserTest.java b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/language/grammar/TraversalEnumParserTest.java
index 0d490df..6ed0d3f 100644
--- a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/language/grammar/TraversalEnumParserTest.java
+++ b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/language/grammar/TraversalEnumParserTest.java
@@ -53,13 +53,14 @@ public class TraversalEnumParserTest {
{"org.apache.tinkerpop.gremlin.process.traversal.Scope", "traversalScope"},
{"org.apache.tinkerpop.gremlin.process.traversal.Order", "traversalOrder"},
{"org.apache.tinkerpop.gremlin.process.traversal.Pop", "traversalPop"},
+ {"org.apache.tinkerpop.gremlin.process.traversal.Pick", "traversalOptionParent"},
+ {"org.apache.tinkerpop.gremlin.process.traversal.Merge", "traversalMerge"},
{"org.apache.tinkerpop.gremlin.process.traversal.SackFunctions$Barrier", "traversalSackMethod"},
{"org.apache.tinkerpop.gremlin.process.traversal.Operator", "traversalOperator"},
{"org.apache.tinkerpop.gremlin.structure.T", "traversalToken"},
{"org.apache.tinkerpop.gremlin.structure.Column", "traversalColumn"},
{"org.apache.tinkerpop.gremlin.structure.Direction", "traversalDirection"},
{"org.apache.tinkerpop.gremlin.structure.VertexProperty$Cardinality", "traversalCardinality"},
- {"org.apache.tinkerpop.gremlin.process.traversal.step.TraversalOptionParent$Pick", "traversalOptionParent"},
});
}
@@ -91,6 +92,7 @@ public class TraversalEnumParserTest {
return Arrays.asList(new Object[][]{
{"org.apache.tinkerpop.gremlin.process.traversal.Scope", "traversalScope"},
{"org.apache.tinkerpop.gremlin.process.traversal.Order", "traversalOrder"},
+ {"org.apache.tinkerpop.gremlin.process.traversal.Merge", "traversalMerge"},
{"org.apache.tinkerpop.gremlin.structure.T", "traversalToken"}
});
}
diff --git a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/step/branch/BranchStepTest.java b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/step/branch/BranchStepTest.java
index f68af08..629ea72 100644
--- a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/step/branch/BranchStepTest.java
+++ b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/step/branch/BranchStepTest.java
@@ -28,7 +28,7 @@ import java.util.List;
import static org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__.in;
import static org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__.out;
import static org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__.values;
-import static org.apache.tinkerpop.gremlin.process.traversal.step.TraversalOptionParent.Pick.none;
+import static org.apache.tinkerpop.gremlin.process.traversal.Pick.none;
/**
* @author Daniel Kuppitz (http://gremlin.guru)
diff --git a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/step/branch/ChooseStepTest.java b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/step/branch/ChooseStepTest.java
index 885da77..604ae20 100644
--- a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/step/branch/ChooseStepTest.java
+++ b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/step/branch/ChooseStepTest.java
@@ -29,7 +29,7 @@ import java.util.List;
import static org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__.in;
import static org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__.out;
import static org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__.values;
-import static org.apache.tinkerpop.gremlin.process.traversal.step.TraversalOptionParent.Pick.none;
+import static org.apache.tinkerpop.gremlin.process.traversal.Pick.none;
/**
* @author Daniel Kuppitz (http://gremlin.guru)
diff --git a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/structure/io/gryo/GryoMapperTest.java b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/structure/io/gryo/GryoMapperTest.java
index fcb040a..656d542 100644
--- a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/structure/io/gryo/GryoMapperTest.java
+++ b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/structure/io/gryo/GryoMapperTest.java
@@ -20,6 +20,7 @@ package org.apache.tinkerpop.gremlin.structure.io.gryo;
import org.apache.tinkerpop.gremlin.process.remote.traversal.DefaultRemoteTraverser;
import org.apache.tinkerpop.gremlin.process.traversal.Bytecode;
+import org.apache.tinkerpop.gremlin.process.traversal.Merge;
import org.apache.tinkerpop.gremlin.process.traversal.util.TraversalExplanation;
import org.apache.tinkerpop.gremlin.structure.Vertex;
import org.apache.tinkerpop.gremlin.structure.io.IoX;
@@ -377,6 +378,12 @@ public class GryoMapperTest {
assertThat(Arrays.equals(bb.array(), serializeDeserialize(bb, ByteBuffer.class).array()), is(true));
}
+ @Test
+ public void shouldHandleMerge() throws Exception {
+ final Merge merge = Merge.onCreate;
+ assertEquals(merge, serializeDeserialize(merge, Merge.class));
+ }
+
public <T> T serializeDeserialize(final Object o, final Class<T> clazz) throws Exception {
final Kryo kryo = builder.get().create().createMapper();
try (final ByteArrayOutputStream stream = new ByteArrayOutputStream()) {
diff --git a/gremlin-dotnet/build/generate.groovy b/gremlin-dotnet/build/generate.groovy
index 0d6e8ba..fedf0dd 100644
--- a/gremlin-dotnet/build/generate.groovy
+++ b/gremlin-dotnet/build/generate.groovy
@@ -31,6 +31,16 @@ import java.nio.file.Paths
import static org.apache.tinkerpop.gremlin.process.traversal.AnonymousTraversalSource.traversal
+// getting an exception like:
+// > InvocationTargetException: javax.script.ScriptException: groovy.lang.MissingMethodException: No signature of
+// > method: org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.DefaultGraphTraversal.mergeE() is applicable for
+// > argument types: (String) values: [4ffdea36-4a0e-4681-acba-e76875d1b25b]
+// usually means bindings are not being extracted properly by VarAsBindingASTTransformation which typically happens
+// when a step is taking an argument that cannot properly resolve to the type required by the step itself. there are
+// special cases in that VarAsBindingASTTransformation class which might need to be adjusted. Editing the
+// GremlinGroovyScriptEngineTest#shouldProduceBindingsForVars() with the failing step and argument can typically make
+// this issue relatively easy to debug and enforce.
+
// file is overwritten on each generation
radishGremlinFile = new File("${projectBaseDir}/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs")
diff --git a/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/GraphTraversal.cs b/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/GraphTraversal.cs
index ebd1cdf..03521f0 100644
--- a/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/GraphTraversal.cs
+++ b/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/GraphTraversal.cs
@@ -1090,6 +1090,61 @@ namespace Gremlin.Net.Process.Traversal
}
/// <summary>
+ /// Adds the mergeE step to this <see cref="GraphTraversal{SType, EType}" />.
+ /// </summary>
+ public GraphTraversal<S, Edge> MergeE ()
+ {
+ Bytecode.AddStep("mergeE");
+ return Wrap<S, Edge>(this);
+ }
+
+ /// <summary>
+ /// Adds the mergeE step to this <see cref="GraphTraversal{SType, EType}" />.
+ /// </summary>
+ public GraphTraversal<S, Edge> MergeE (IDictionary<object,object> m)
+ {
+ Bytecode.AddStep("mergeE", m);
+ return Wrap<S, Edge>(this);
+ }
+
+ /// <summary>
+ /// Adds the mergeE step to this <see cref="GraphTraversal{SType, EType}" />.
+ /// </summary>
+ public GraphTraversal<S, Edge> MergeE (ITraversal t)
+ {
+ Bytecode.AddStep("mergeE", t);
+ return Wrap<S, Edge>(this);
+ }
+
+ /// <summary>
+ /// Adds the mergeV step to this <see cref="GraphTraversal{SType, EType}" />.
+ /// </summary>
+ public GraphTraversal<S, Vertex> MergeV ()
+ {
+ Bytecode.AddStep("mergeV");
+ return Wrap<S, Vertex>(this);
+ }
+
+ /// <summary>
+ /// Adds the mergeV step to this <see cref="GraphTraversal{SType, EType}" />.
+ /// </summary>
+ public GraphTraversal<S, Vertex> MergeV (IDictionary<object,object> m)
+ {
+ Bytecode.AddStep("mergeV", m);
+ return Wrap<S, Vertex>(this);
+ }
+
+ /// <summary>
+ /// Adds the mergeV step to this <see cref="GraphTraversal{SType, EType}" />.
+ /// </summary>
+ public GraphTraversal<S, Vertex> MergeV (ITraversal t)
+ {
+ Bytecode.AddStep("mergeV", t);
+ return Wrap<S, Vertex>(this);
+ }
+
+
+ /// <summary>
/// Adds the min step to this <see cref="GraphTraversal{SType, EType}" />.
/// </summary>
public GraphTraversal<S, E2> Min<E2> ()
@@ -1137,6 +1192,15 @@ namespace Gremlin.Net.Process.Traversal
/// <summary>
/// Adds the option step to this <see cref="GraphTraversal{SType, EType}" />.
/// </summary>
+ public GraphTraversal<S, E> Option (object pickToken, IDictionary<object,object> traversalOption)
+ {
+ Bytecode.AddStep("option", pickToken, traversalOption);
+ return Wrap<S, E>(this);
+ }
+
+ /// <summary>
+ /// Adds the option step to this <see cref="GraphTraversal{SType, EType}" />.
+ /// </summary>
public GraphTraversal<S, E> Option (ITraversal traversalOption)
{
Bytecode.AddStep("option", traversalOption);
diff --git a/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/GraphTraversalSource.cs b/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/GraphTraversalSource.cs
index ae5b1d9..5ad2f7d 100644
--- a/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/GraphTraversalSource.cs
+++ b/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/GraphTraversalSource.cs
@@ -336,7 +336,7 @@ namespace Gremlin.Net.Process.Traversal
public GraphTraversal<Edge, Edge> AddE(string label)
{
var traversal = new GraphTraversal<Edge, Edge>(TraversalStrategies, new Bytecode(Bytecode));
- traversal.Bytecode.AddStep("addE", label);
+ traversal.Bytecode.AddStep("addE", label);
return traversal;
}
@@ -347,7 +347,29 @@ namespace Gremlin.Net.Process.Traversal
public GraphTraversal<Edge, Edge> AddE(ITraversal edgeLabelTraversal)
{
var traversal = new GraphTraversal<Edge, Edge>(TraversalStrategies, new Bytecode(Bytecode));
- traversal.Bytecode.AddStep("addE", edgeLabelTraversal);
+ traversal.Bytecode.AddStep("addE", edgeLabelTraversal);
+ return traversal;
+ }
+
+ /// <summary>
+ /// Spawns a <see cref="GraphTraversal{SType, EType}" /> off this graph traversal source and adds the mergeE step to that
+ /// traversal.
+ /// </summary>
+ public GraphTraversal<Edge, Edge> MergeE(IDictionary<object,object> m)
+ {
+ var traversal = new GraphTraversal<Edge, Edge>(TraversalStrategies, new Bytecode(Bytecode));
+ traversal.Bytecode.AddStep("mergeE", m);
+ return traversal;
+ }
+
+ /// <summary>
+ /// Spawns a <see cref="GraphTraversal{SType, EType}" /> off this graph traversal source and adds the mergeE step to that
+ /// traversal.
+ /// </summary>
+ public GraphTraversal<Edge, Edge> MergeE(ITraversal t)
+ {
+ var traversal = new GraphTraversal<Edge, Edge>(TraversalStrategies, new Bytecode(Bytecode));
+ traversal.Bytecode.AddStep("mergeE", t);
return traversal;
}
@@ -358,7 +380,7 @@ namespace Gremlin.Net.Process.Traversal
public GraphTraversal<Vertex, Vertex> AddV()
{
var traversal = new GraphTraversal<Vertex, Vertex>(TraversalStrategies, new Bytecode(Bytecode));
- traversal.Bytecode.AddStep("addV");
+ traversal.Bytecode.AddStep("addV");
return traversal;
}
@@ -369,7 +391,7 @@ namespace Gremlin.Net.Process.Traversal
public GraphTraversal<Vertex, Vertex> AddV(string label)
{
var traversal = new GraphTraversal<Vertex, Vertex>(TraversalStrategies, new Bytecode(Bytecode));
- traversal.Bytecode.AddStep("addV", label);
+ traversal.Bytecode.AddStep("addV", label);
return traversal;
}
@@ -380,7 +402,29 @@ namespace Gremlin.Net.Process.Traversal
public GraphTraversal<Vertex, Vertex> AddV(ITraversal vertexLabelTraversal)
{
var traversal = new GraphTraversal<Vertex, Vertex>(TraversalStrategies, new Bytecode(Bytecode));
- traversal.Bytecode.AddStep("addV", vertexLabelTraversal);
+ traversal.Bytecode.AddStep("addV", vertexLabelTraversal);
+ return traversal;
+ }
+
+ /// <summary>
+ /// Spawns a <see cref="GraphTraversal{SType, EType}" /> off this graph traversal source and adds the mergeV step to that
+ /// traversal.
+ /// </summary>
+ public GraphTraversal<Vertex, Vertex> MergeV(IDictionary<object,object> m)
+ {
+ var traversal = new GraphTraversal<Vertex, Vertex>(TraversalStrategies, new Bytecode(Bytecode));
+ traversal.Bytecode.AddStep("mergeV", m);
+ return traversal;
+ }
+
+ /// <summary>
+ /// Spawns a <see cref="GraphTraversal{SType, EType}" /> off this graph traversal source and adds the mergeV step to that
+ /// traversal.
+ /// </summary>
+ public GraphTraversal<Vertex, Vertex> MergeV(ITraversal t)
+ {
+ var traversal = new GraphTraversal<Vertex, Vertex>(TraversalStrategies, new Bytecode(Bytecode));
+ traversal.Bytecode.AddStep("mergeV", t);
return traversal;
}
diff --git a/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/Merge.cs b/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/Merge.cs
new file mode 100644
index 0000000..b62fc8f
--- /dev/null
+++ b/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/Merge.cs
@@ -0,0 +1,63 @@
+#region License
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#endregion
+
+using System;
+using System.Collections.Generic;
+
+namespace Gremlin.Net.Process.Traversal
+{
+#pragma warning disable 1591
+
+ public class Merge : EnumWrapper, IFunction
+ {
+ private Merge(string enumValue)
+ : base("Merge", enumValue)
+ {
+ }
+
+ public static Merge OnCreate => new Merge("onCreate");
+
+ public static Merge OnMatch => new Merge("onMatch");
+
+ private static readonly IDictionary<string, Merge> Properties = new Dictionary<string, Merge>
+ {
+ { "onCreate", OnCreate },
+ { "onMatch", OnMatch },
+ };
+
+ /// <summary>
+ /// Gets the Merge enumeration by value.
+ /// </summary>
+ public static Merge GetByValue(string value)
+ {
+ if (!Properties.TryGetValue(value, out var property))
+ {
+ throw new ArgumentException($"No matching Merge for value '{value}'");
+ }
+ return property;
+ }
+ }
+
+
+#pragma warning restore 1591
+}
\ No newline at end of file
diff --git a/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/__.cs b/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/__.cs
index acad588..62c19e0 100644
--- a/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/__.cs
+++ b/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/__.cs
@@ -812,6 +812,54 @@ namespace Gremlin.Net.Process.Traversal
}
/// <summary>
+ /// Spawns a <see cref="GraphTraversal{SType, EType}" /> and adds the mergeE step to that traversal.
+ /// </summary>
+ public static GraphTraversal<object, Edge> MergeE ()
+ {
+ return new GraphTraversal<object, Edge>().MergeE();
+ }
+
+ /// <summary>
+ /// Spawns a <see cref="GraphTraversal{SType, EType}" /> and adds the mergeE step to that traversal.
+ /// </summary>
+ public static GraphTraversal<object, Edge> MergeE (IDictionary<object,object> m)
+ {
+ return new GraphTraversal<object, Edge>().MergeE(m);
+ }
+
+ /// <summary>
+ /// Spawns a <see cref="GraphTraversal{SType, EType}" /> and adds the mergeE step to that traversal.
+ /// </summary>
+ public static GraphTraversal<object, Edge> MergeE (ITraversal t)
+ {
+ return new GraphTraversal<object, Edge>().MergeE(t);
+ }
+
+ /// <summary>
+ /// Spawns a <see cref="GraphTraversal{SType, EType}" /> and adds the mergeV step to that traversal.
+ /// </summary>
+ public static GraphTraversal<object, Vertex> MergeV ()
+ {
+ return new GraphTraversal<object, Vertex>().MergeV();
+ }
+
+ /// <summary>
+ /// Adds the mergeV step to this <see cref="GraphTraversal{SType, EType}" />.
+ /// </summary>
+ public static GraphTraversal<object, Vertex> MergeV (IDictionary<object,object> m)
+ {
+ return new GraphTraversal<object, Vertex>().MergeV(m);
+ }
+
+ /// <summary>
+ /// Adds the mergeV step to this <see cref="GraphTraversal{SType, EType}" />.
+ /// </summary>
+ public static GraphTraversal<object, Vertex> MergeV (ITraversal t)
+ {
+ return new GraphTraversal<object, Vertex>().MergeV(t);
+ }
+
+ /// <summary>
/// Spawns a <see cref="GraphTraversal{SType, EType}" /> and adds the min step to that traversal.
/// </summary>
public static GraphTraversal<object, E2> Min<E2>()
diff --git a/gremlin-dotnet/src/Gremlin.Net/Structure/IO/GraphBinary/DataType.cs b/gremlin-dotnet/src/Gremlin.Net/Structure/IO/GraphBinary/DataType.cs
index 3e65ad0..ee33cee 100644
--- a/gremlin-dotnet/src/Gremlin.Net/Structure/IO/GraphBinary/DataType.cs
+++ b/gremlin-dotnet/src/Gremlin.Net/Structure/IO/GraphBinary/DataType.cs
@@ -55,6 +55,7 @@ namespace Gremlin.Net.Structure.IO.GraphBinary
public static readonly DataType Cardinality = new DataType(0x16);
public static readonly DataType Column = new DataType(0x17);
public static readonly DataType Direction = new DataType(0x18);
+ public static readonly DataType Merge = new DataType(0x2E);
public static readonly DataType Operator = new DataType(0x19);
public static readonly DataType Order = new DataType(0x1A);
public static readonly DataType Pick = new DataType(0x1B);
diff --git a/gremlin-dotnet/src/Gremlin.Net/Structure/IO/GraphBinary/TypeSerializerRegistry.cs b/gremlin-dotnet/src/Gremlin.Net/Structure/IO/GraphBinary/TypeSerializerRegistry.cs
index 87c28e6..d30b5ca 100644
--- a/gremlin-dotnet/src/Gremlin.Net/Structure/IO/GraphBinary/TypeSerializerRegistry.cs
+++ b/gremlin-dotnet/src/Gremlin.Net/Structure/IO/GraphBinary/TypeSerializerRegistry.cs
@@ -60,6 +60,7 @@ namespace Gremlin.Net.Structure.IO.GraphBinary
{typeof(Cardinality), EnumSerializers.CardinalitySerializer},
{typeof(Column), EnumSerializers.ColumnSerializer},
{typeof(Direction), EnumSerializers.DirectionSerializer},
+ {typeof(Merge), EnumSerializers.MergeSerializer},
{typeof(Operator), EnumSerializers.OperatorSerializer},
{typeof(Order), EnumSerializers.OrderSerializer},
{typeof(Pick), EnumSerializers.PickSerializer},
@@ -107,6 +108,7 @@ namespace Gremlin.Net.Structure.IO.GraphBinary
{DataType.Cardinality, EnumSerializers.CardinalitySerializer},
{DataType.Column, EnumSerializers.ColumnSerializer},
{DataType.Direction, EnumSerializers.DirectionSerializer},
+ {DataType.Merge, EnumSerializers.MergeSerializer},
{DataType.Operator, EnumSerializers.OperatorSerializer},
{DataType.Order, EnumSerializers.OrderSerializer},
{DataType.Pick, EnumSerializers.PickSerializer},
diff --git a/gremlin-dotnet/src/Gremlin.Net/Structure/IO/GraphBinary/Types/EnumSerializer.cs b/gremlin-dotnet/src/Gremlin.Net/Structure/IO/GraphBinary/Types/EnumSerializer.cs
index d561c2a..5bc6080 100644
--- a/gremlin-dotnet/src/Gremlin.Net/Structure/IO/GraphBinary/Types/EnumSerializer.cs
+++ b/gremlin-dotnet/src/Gremlin.Net/Structure/IO/GraphBinary/Types/EnumSerializer.cs
@@ -58,6 +58,12 @@ namespace Gremlin.Net.Structure.IO.GraphBinary.Types
new EnumSerializer<Direction>(DataType.Direction, Direction.GetByValue);
/// <summary>
+ /// A serializer for <see cref="Merge"/> values.
+ /// </summary>
+ public static readonly EnumSerializer<Merge> MergeSerializer =
+ new EnumSerializer<Merge>(DataType.Merge, Merge.GetByValue);
+
+ /// <summary>
/// A serializer for <see cref="Operator"/> values.
/// </summary>
public static readonly EnumSerializer<Operator> OperatorSerializer =
diff --git a/gremlin-dotnet/src/Gremlin.Net/Structure/IO/GraphSON/GraphSONReader.cs b/gremlin-dotnet/src/Gremlin.Net/Structure/IO/GraphSON/GraphSONReader.cs
index 16901bd..3208966 100644
--- a/gremlin-dotnet/src/Gremlin.Net/Structure/IO/GraphSON/GraphSONReader.cs
+++ b/gremlin-dotnet/src/Gremlin.Net/Structure/IO/GraphSON/GraphSONReader.cs
@@ -45,6 +45,7 @@ namespace Gremlin.Net.Structure.IO.GraphSON
{"g:Float", new FloatConverter()},
{"g:Double", new DoubleConverter()},
{"g:Direction", new DirectionDeserializer()},
+ {"g:Merge", new MergeDeserializer()},
{"g:UUID", new UuidDeserializer()},
{"g:Date", new DateDeserializer()},
{"g:Timestamp", new DateDeserializer()},
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/TraversalOptionParent.java b/gremlin-dotnet/src/Gremlin.Net/Structure/IO/GraphSON/MergeDeserializer.cs
similarity index 62%
copy from gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/TraversalOptionParent.java
copy to gremlin-dotnet/src/Gremlin.Net/Structure/IO/GraphSON/MergeDeserializer.cs
index 6eb60a2..26b6869 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/TraversalOptionParent.java
+++ b/gremlin-dotnet/src/Gremlin.Net/Structure/IO/GraphSON/MergeDeserializer.cs
@@ -1,3 +1,5 @@
+#region License
+
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
@@ -7,7 +9,7 @@
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
@@ -16,16 +18,19 @@
* specific language governing permissions and limitations
* under the License.
*/
-package org.apache.tinkerpop.gremlin.process.traversal.step;
-
-import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
-/**
- * @author Marko A. Rodriguez (http://markorodriguez.com)
- */
-public interface TraversalOptionParent<M, S, E> extends TraversalParent {
+#endregion
- public static enum Pick {any, none}
+using System.Text.Json;
+using Gremlin.Net.Process.Traversal;
- public void addGlobalChildOption(final M pickToken, final Traversal.Admin<S, E> traversalOption);
-}
+namespace Gremlin.Net.Structure.IO.GraphSON
+{
+ internal class MergeDeserializer : IGraphSONDeserializer
+ {
+ public dynamic Objectify(JsonElement graphsonObject, GraphSONReader reader)
+ {
+ return Merge.GetByValue(graphsonObject.GetString());
+ }
+ }
+}
\ No newline at end of file
diff --git a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/CommonSteps.cs b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/CommonSteps.cs
index 03b429a..fcac51a 100644
--- a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/CommonSteps.cs
+++ b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/CommonSteps.cs
@@ -379,7 +379,14 @@ namespace Gremlin.Net.IntegrationTest.Gherkin
private static Vertex ToVertex(string name, string graphName)
{
- return ScenarioData.GetByGraphName(graphName).Vertices[name];
+ if (ScenarioData.GetByGraphName(graphName).Vertices.ContainsKey(name))
+ {
+ return ScenarioData.GetByGraphName(graphName).Vertices[name];
+ }
+ else
+ {
+ return new Vertex(name);
+ }
}
private static Edge ToEdge(string name, string graphName)
diff --git a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/GherkinTestRunner.cs b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/GherkinTestRunner.cs
index 0209c7b..365bedb 100644
--- a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/GherkinTestRunner.cs
+++ b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/GherkinTestRunner.cs
@@ -87,6 +87,11 @@ namespace Gremlin.Net.IntegrationTest.Gherkin
},
{"g_V_properties_order", IgnoreReason.VertexPropertyNotSupportedInGherkin},
{"g_V_properties_order_id", IgnoreReason.VertexPropertyNotSupportedInGherkin},
+ {"g_withSideEffectXc_label_person_name_markoX_withSideEffectXm_age_19X_injectX0X_mergeVXselectXcXX_optionXonMatch_selectXmXX_option", IgnoreReason.MergeVEWithTraversalNotSupportedInTranslation},
+ {"g_withSideEffectXc_label_person_name_markoX_withSideEffectXm_age_19X_mergeVXselectXcXX_optionXonMatch_selectXmXX_option", IgnoreReason.MergeVEWithTraversalNotSupportedInTranslation},
+ {"g_withSideEffectXc_label_person_name_stephenX_withSideEffectXm_label_person_name_stephen_age_19X_injectX0X_mergeVXselectXcXX_optionXonCreate_selectXmXX_option", IgnoreReason.MergeVEWithTraversalNotSupportedInTranslation},
+ {"g_injectXlabel_person_name_marko_label_person_name_stephenX_mergeVXidentityX", IgnoreReason.MergeVEWithTraversalNotSupportedInTranslation},
+ {"g_withSideEffectXc_label_person_name_stephenX_withSideEffectXm_label_person_name_stephen_age_19X_mergeVXselectXcXX_optionXonCreate_selectXmXX_option", IgnoreReason.MergeVEWithTraversalNotSupportedInTranslation},
};
private static class Keywords
diff --git a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs
index 021f484..6a2f5ef 100644
--- a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs
+++ b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs
@@ -500,6 +500,32 @@ namespace Gremlin.Net.IntegrationTest.Gherkin
{"g_withStrategiesXProductiveByStrategyX_V_aggregateXaX_byXfooX_capXaX_unfold_mean", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.WithStrategies(new ProductiveByStrategy(productiveKeys: new List<object> {})).V().Aggregate("a").By("foo").Cap<object>("a").Unfold<object>().Mean<object>()}},
{"g_injectXnull_10_20_nullX_mean", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject<object>(null,p["xx1"],p["xx2"],null).Mean<object>()}},
{"g_injectXlistXnull_10_20_nullXX_meanXlocalX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject(p["xx1"]).Mean<object>(Scope.Local)}},
+ {"g_V_mergeEXlabel_self_weight_05X", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.AddV("person").Property("name","marko").Property("age",29), (g,p) =>g.V().MergeE((IDictionary<object,object>) p["xx1"]), (g,p) =>g.E().HasLabel("self").Has("weight",0.5)}},
+ {"g_mergeEXlabel_knows_out_marko_in_vadasX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.AddV("person").Property(T.Id,100).Property("name","marko").AddV("person").Property(T.Id,101).Property("name","vadas"), (g,p) =>g.MergeE((IDictionary<object,object>) p["xx1"]), (g,p) =>g.V().Has("person","name","marko").Out("knows").Has("person","name","vadas")}},
+ {"g_mergeEXlabel_knows_out_marko_in_vadas_weight_05X_exists", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.AddV("person").Property(T.Id,100).Property("name","marko").As("a").AddV("person").Property(T.Id,101).Property("name","vadas").As("b").AddE("knows").From("a").To("b"), (g,p) =>g.MergeE((IDictionary<object,object>) p["xx1"]), (g,p) =>g.V().Has("person","name","marko").OutE("knows").Has("weight",0.5).InV().Has("person","name"," [...]
+ {"g_mergeEXlabel_knows_out_marko_in_vadas_weight_05X", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.MergeE((IDictionary<object,object>) p["xx1"]), (g,p) =>g.V(), (g,p) =>g.E().HasLabel("knows").Has("weight",0.5)}},
+ {"g_mergeEXlabel_knows_out_marko_in_vadasX_optionXonCreate_created_YX_optionXonMatch_created_NX", 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.OnMatch, (IDictionary<object,object>) p["xx3"]), (g,p) =>g.V(), (g,p) =>g.E().HasLabel("knows").Has("created","Y"), (g,p) =>g.E().HasLabel("knows").Has("created","N")}},
+ {"g_mergeEXlabel_knows_out_marko_in_vadasX_optionXonCreate_created_YX_optionXonMatch_created_NX_exists", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.AddV("person").Property(T.Id,100).Property("name","marko").As("a").AddV("person").Property(T.Id,101).Property("name","vadas").As("b").AddE("knows").From("a").To("b"), (g,p) =>g.MergeE((IDictionary<object,object>) p["xx1"]).Option(Merge.OnCreate, (IDictionary<object,object>) p["xx2"] [...]
+ {"g_mergeEXlabel_knows_out_marko_in_vadasX_optionXonCreate_created_YX_optionXonMatch_created_NX_exists_updated", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.AddV("person").Property(T.Id,100).Property("name","marko").As("a").AddV("person").Property(T.Id,101).Property("name","vadas").As("b").AddE("knows").From("a").To("b").Property("created","Y"), (g,p) =>g.MergeE((IDictionary<object,object>) p["xx1"]).Option(Merge.OnCreate, (IDic [...]
+ {"g_V_hasXperson_name_marko_X_mergeEXlabel_knowsX_optionXonCreate_created_YX_optionXonMatch_created_NX_exists_updated", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.AddV("person").Property(T.Id,100).Property("name","marko").As("a").AddV("person").Property(T.Id,101).Property("name","vadas").As("b").AddE("knows").From("a").To("b").Property("created","Y").AddE("knows").From("a").To("b"), (g,p) =>g.V().Has("person","name","marko").Me [...]
+ {"g_VX100X_VX101X_mergeEXlabel_knows_out_marko_in_vadasX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.AddV("person").Property(T.Id,100).Property("name","marko"), (g,p) =>g.V(100).V(101).MergeE((IDictionary<object,object>) p["xx1"]), (g,p) =>g.V(), (g,p) =>g.E()}},
+ {"g_injectXlabel_knows_out_marko_in_vadasX_mergeE", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.AddV("person").Property(T.Id,100).Property("name","marko").AddV("person").Property(T.Id,101).Property("name","vadas"), (g,p) =>g.Inject(p["xx1"]).MergeE(), (g,p) =>g.V().Has("person","name","marko").Out("knows").Has("person","name","vadas")}},
+ {"g_mergeVXlabel_person_name_stephenX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.AddV("person").Property("name","marko").Property("age",29).As("marko"), (g,p) =>g.MergeV((IDictionary<object,object>) p["xx1"]), (g,p) =>g.V().Has("person","name","stephen")}},
+ {"g_mergeVXlabel_person_name_markoX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.AddV("person").Property("name","marko").Property("age",29).As("marko"), (g,p) =>g.MergeV((IDictionary<object,object>) p["xx1"]), (g,p) =>g.V().Has("person","name","marko")}},
+ {"g_mergeVXlabel_person_name_stephenX_optionXonCreate_label_person_name_stephen_age_19X_option", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.AddV("person").Property("name","marko").Property("age",29).As("marko"), (g,p) =>g.MergeV((IDictionary<object,object>) p["xx1"]).Option(Merge.OnCreate, (IDictionary<object,object>) p["xx2"]), (g,p) =>g.V().Has("person","name","stephen").Has("age",19)}},
+ {"g_mergeVXlabel_person_name_markoX_optionXonMatch_age_19X_option", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.AddV("person").Property("name","marko").Property("age",29).As("marko"), (g,p) =>g.MergeV((IDictionary<object,object>) p["xx1"]).Option(Merge.OnMatch, (IDictionary<object,object>) p["xx2"]), (g,p) =>g.V().Has("person","name","marko").Has("age",19)}},
+ {"g_withSideEffectXc_label_person_name_stephenX_withSideEffectXm_label_person_name_stephen_age_19X_mergeVXselectXcXX_optionXonCreate_selectXmXX_option", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.AddV("person").Property("name","marko").Property("age",29).As("marko"), (g,p) =>g.WithSideEffect("c",p["xx1"]).WithSideEffect("m",p["xx2"]).MergeV((IDictionary<object,object>) __.Select<object>("c")).Option(Merge.OnCreate, (IDictionary [...]
+ {"g_withSideEffectXc_label_person_name_markoX_withSideEffectXm_age_19X_mergeVXselectXcXX_optionXonMatch_selectXmXX_option", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.AddV("person").Property("name","marko").Property("age",29).As("marko"), (g,p) =>g.WithSideEffect("c",p["xx1"]).WithSideEffect("m",p["xx2"]).MergeV((IDictionary<object,object>) __.Select<object>("c")).Option(Merge.OnMatch, (IDictionary<object,object>) __.Select<obj [...]
+ {"g_mergeVXlabel_person_name_markoX_propertyXname_vadas_acl_publicX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.AddV("person").Property("name","marko").Property("age",29).As("marko"), (g,p) =>g.MergeV((IDictionary<object,object>) p["xx1"]).Property("name","vadas","acl","public"), (g,p) =>g.V().Properties<object>("name").HasValue("vadas").Has("acl","public")}},
+ {"g_injectX0X_mergeVXlabel_person_name_stephenX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.AddV("person").Property("name","marko").Property("age",29).As("marko"), (g,p) =>g.Inject(0).MergeV((IDictionary<object,object>) p["xx1"]), (g,p) =>g.V().Has("person","name","stephen")}},
+ {"g_injectX0X_mergeVXlabel_person_name_markoX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.AddV("person").Property("name","marko").Property("age",29).As("marko"), (g,p) =>g.Inject(0).MergeV((IDictionary<object,object>) p["xx1"]), (g,p) =>g.V().Has("person","name","marko")}},
+ {"g_injectX0X_mergeVXlabel_person_name_stephenX_optionXonCreate_label_person_name_stephen_age_19X_option", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.AddV("person").Property("name","marko").Property("age",29).As("marko"), (g,p) =>g.Inject(0).MergeV((IDictionary<object,object>) p["xx1"]).Option(Merge.OnCreate, (IDictionary<object,object>) p["xx2"]), (g,p) =>g.V().Has("person","name","stephen").Has("age",19)}},
+ {"g_injectX0X_mergeVXlabel_person_name_markoX_optionXonMatch_age_19X_option", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.AddV("person").Property("name","marko").Property("age",29).As("marko"), (g,p) =>g.Inject(0).MergeV((IDictionary<object,object>) p["xx1"]).Option(Merge.OnMatch, (IDictionary<object,object>) p["xx2"]), (g,p) =>g.V().Has("person","name","marko").Has("age",19)}},
+ {"g_withSideEffectXc_label_person_name_stephenX_withSideEffectXm_label_person_name_stephen_age_19X_injectX0X_mergeVXselectXcXX_optionXonCreate_selectXmXX_option", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.AddV("person").Property("name","marko").Property("age",29).As("marko"), (g,p) =>g.WithSideEffect("c",p["xx1"]).WithSideEffect("m",p["xx2"]).Inject(0).MergeV((IDictionary<object,object>) __.Select<object>("c")).Option(Merge.On [...]
+ {"g_withSideEffectXc_label_person_name_markoX_withSideEffectXm_age_19X_injectX0X_mergeVXselectXcXX_optionXonMatch_selectXmXX_option", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.AddV("person").Property("name","marko").Property("age",29).As("marko"), (g,p) =>g.WithSideEffect("c",p["xx1"]).WithSideEffect("m",p["xx2"]).Inject(0).MergeV((IDictionary<object,object>) __.Select<object>("c")).Option(Merge.OnMatch, (IDictionary<object,ob [...]
+ {"g_injectX0X_mergeVXlabel_person_name_markoX_propertyXname_vadas_acl_publicX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.AddV("person").Property("name","marko").Property("age",29).As("marko"), (g,p) =>g.Inject(0).MergeV((IDictionary<object,object>) p["xx1"]).Property("name","vadas","acl","public"), (g,p) =>g.V().Properties<object>("name").HasValue("vadas").Has("acl","public")}},
+ {"g_injectXlabel_person_name_marko_label_person_name_stephenX_mergeVXidentityX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.AddV("person").Property("name","marko").Property("age",29).As("marko"), (g,p) =>g.Inject(p["xx1"],p["xx2"]).MergeV((IDictionary<object,object>) __.Identity()), (g,p) =>g.V().Has("person","name","stephen"), (g,p) =>g.V().Has("person","name","marko"), (g,p) =>g.V()}},
+ {"g_injectXlabel_person_name_marko_label_person_name_stephenX_mergeV", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.AddV("person").Property("name","marko").Property("age",29).As("marko"), (g,p) =>g.Inject(p["xx1"],p["xx2"]).MergeV(), (g,p) =>g.V().Has("person","name","stephen"), (g,p) =>g.V().Has("person","name","marko"), (g,p) =>g.V()}},
{"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>()}},
@@ -807,7 +833,6 @@ namespace Gremlin.Net.IntegrationTest.Gherkin
{"g_V_outXfollowedByX_group_byXsongTypeX_byXbothE_group_byXlabelX_byXweight_sumXX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Out("followedBy").Group<object,object>().By("songType").By(__.BothE().Group<object,object>().By(T.Label).By(__.Values<object>("weight").Sum<object>()))}},
{"g_V_groupXmX_byXnameX_byXinXknowsX_nameX_capXmX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Group("m").By("name").By(__.In("knows").Values<object>("name")).Cap<object>("m")}},
{"g_V_group_byXlabelX_byXbothE_groupXaX_byXlabelX_byXweight_sumX_weight_sumX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Group<object,object>().By(T.Label).By(__.BothE().Group("a").By(T.Label).By(__.Values<object>("weight").Sum<object>()).Values<object>("weight").Sum<object>())}},
- {"g_withSideEffectXa__marko_666_noone_blahX_V_groupXaX_byXnameX_byXoutE_label_foldX_capXaX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.WithSideEffect("a",p["xx1"]).V().Group("a").By("name").By(__.OutE().Label().Fold()).Cap<object>("a").Unfold<object>().Group<object,object>().By(Column.Keys).By(__.Select<object>(Column.Values).Order(Scope.Local).By(Order.Asc))}},
{"g_V_hasLabelXpersonX_asXpX_outXcreatedX_group_byXnameX_byXselectXpX_valuesXageX_sumX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().HasLabel("person").As("p").Out("created").Group<object,object>().By("name").By(__.Select<object>("p").Values<object>("age").Sum<object>())}},
{"g_V_hasLabelXpersonX_asXpX_outXcreatedX_groupXaX_byXnameX_byXselectXpX_valuesXageX_sumX_capXaX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().HasLabel("person").As("p").Out("created").Group("a").By("name").By(__.Select<object>("p").Values<object>("age").Sum<object>()).Cap<object>("a")}},
{"g_V_group_byXlabelX_byXlabel_countX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Group<object,object>().By(__.Label()).By(__.Label().Count())}},
@@ -830,7 +855,7 @@ namespace Gremlin.Net.IntegrationTest.Gherkin
{"g_V_hasXperson_name_markoX_bothXknowsX_groupCount_byXvaluesXnameX_foldX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Has("person","name","marko").Both("knows").GroupCount<object>().By(__.Values<object>("name").Fold())}},
{"g_VX1X_out_injectXv2X_name", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V(p["vid1"]).Out().Inject((Vertex) p["v2"]).Values<object>("name")}},
{"g_VX1X_out_name_injectXdanielX_asXaX_mapXlengthX_path", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V(p["vid1"]).Out().Values<object>("name").Inject("daniel").As("a").Map<object>((IFunction) p["l1"]).Path()}},
- {"g_VX1X_injectXg_VX4XX_out_name", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V(p["vid1"]).Inject((Vertex) p["v4"]).Out().Values<object>("name")}},
+ {"g_VX1X_injectXg_VX4XX_out_name", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V(p["vid1"]).Inject((Vertex) p["v2"]).Out().Values<object>("name")}},
{"g_injectXnull_1_3_nullX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject<object>(null,1,3,null)}},
{"g_injectX10_20_null_20_10_10X_groupCountXxX_dedup_asXyX_projectXa_bX_by_byXselectXxX_selectXselectXyXXX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject<object>(10,20,null,20,10,10).GroupCount("x").Dedup().As("y").Project<object>("a","b").By().By(__.Select<object>("x").Select<object>(__.Select<object>("y")))}},
{"g_injectXname_marko_age_nullX_selectXname_ageX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject(p["xx1"]).Select<object>("name","age")}},
diff --git a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/IgnoreException.cs b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/IgnoreException.cs
index 2fa881b..8f1bbac 100644
--- a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/IgnoreException.cs
+++ b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/IgnoreException.cs
@@ -70,6 +70,13 @@ namespace Gremlin.Net.IntegrationTest.Gherkin
/// <summary>
/// Need a Gherkin parser for VertexProperty results: https://issues.apache.org/jira/browse/TINKERPOP-2686
/// </summary>
- VertexPropertyNotSupportedInGherkin
+ VertexPropertyNotSupportedInGherkin,
+
+ /// <summary>
+ /// VarAsBindingASTTransformation isn't capable of properly casting arguments so the DotNetTranslator can't
+ /// produce the right Gremlin. These tests could be static translated but there are a lot of them so it would
+ /// be better to solve this more directly.
+ /// </summary>
+ MergeVEWithTraversalNotSupportedInTranslation
}
}
\ No newline at end of file
diff --git a/gremlin-dotnet/test/Gremlin.Net.UnitTest/Structure/IO/GraphBinary/GraphBinaryTests.cs b/gremlin-dotnet/test/Gremlin.Net.UnitTest/Structure/IO/GraphBinary/GraphBinaryTests.cs
index 7978005..eecb385 100644
--- a/gremlin-dotnet/test/Gremlin.Net.UnitTest/Structure/IO/GraphBinary/GraphBinaryTests.cs
+++ b/gremlin-dotnet/test/Gremlin.Net.UnitTest/Structure/IO/GraphBinary/GraphBinaryTests.cs
@@ -535,6 +535,21 @@ namespace Gremlin.Net.UnitTest.Structure.IO.GraphBinary
Assert.Equal(expected, actual);
}
+
+ [Fact]
+ public async Task TestMerge()
+ {
+ var expected = Merge.OnCreate;
+ var writer = CreateGraphBinaryWriter();
+ var reader = CreateGraphBinaryReader();
+ var serializationStream = new MemoryStream();
+
+ await writer.WriteAsync(expected, serializationStream);
+ serializationStream.Position = 0;
+ var actual = await reader.ReadAsync(serializationStream);
+
+ Assert.Equal(expected, actual);
+ }
[Fact]
public async Task TestOperator()
diff --git a/gremlin-dotnet/test/Gremlin.Net.UnitTest/Structure/IO/GraphSON/GraphSONReaderTests.cs b/gremlin-dotnet/test/Gremlin.Net.UnitTest/Structure/IO/GraphSON/GraphSONReaderTests.cs
index 5bc232b..2c33a1e 100644
--- a/gremlin-dotnet/test/Gremlin.Net.UnitTest/Structure/IO/GraphSON/GraphSONReaderTests.cs
+++ b/gremlin-dotnet/test/Gremlin.Net.UnitTest/Structure/IO/GraphSON/GraphSONReaderTests.cs
@@ -312,6 +312,18 @@ namespace Gremlin.Net.UnitTest.Structure.IO.GraphSON
Assert.Equal(Direction.Out, deserializedValue);
}
+
+ [Theory, MemberData(nameof(Versions))]
+ public void ShouldDeserializeMerge(int version)
+ {
+ const string serializedValue = "{\"@type\":\"g:Merge\",\"@value\":\"onMatch\"}";
+ var reader = CreateStandardGraphSONReader(version);
+
+ var jsonElement = JsonSerializer.Deserialize<JsonElement>(serializedValue);
+ var deserializedValue = reader.ToObject(jsonElement);
+
+ Assert.Equal(Merge.OnMatch, deserializedValue);
+ }
[Fact]
public void ShouldDeserializePathFromGraphSON2()
diff --git a/gremlin-dotnet/test/Gremlin.Net.UnitTest/Structure/IO/GraphSON/GraphSONWriterTests.cs b/gremlin-dotnet/test/Gremlin.Net.UnitTest/Structure/IO/GraphSON/GraphSONWriterTests.cs
index ce593f7..9247831 100644
--- a/gremlin-dotnet/test/Gremlin.Net.UnitTest/Structure/IO/GraphSON/GraphSONWriterTests.cs
+++ b/gremlin-dotnet/test/Gremlin.Net.UnitTest/Structure/IO/GraphSON/GraphSONWriterTests.cs
@@ -258,7 +258,7 @@ namespace Gremlin.Net.UnitTest.Structure.IO.GraphSON
}
[Theory, MemberData(nameof(Versions))]
- public void ShouldSerializeEnum(int version)
+ public void ShouldSerializeDirection(int version)
{
var writer = CreateGraphSONWriter(version);
@@ -269,6 +269,17 @@ namespace Gremlin.Net.UnitTest.Structure.IO.GraphSON
}
[Theory, MemberData(nameof(Versions))]
+ public void ShouldSerializeMerge(int version)
+ {
+ var writer = CreateGraphSONWriter(version);
+
+ var serializedEnum = writer.WriteObject(Merge.OnMatch);
+
+ var expectedGraphSON = "{\"@type\":\"g:Merge\",\"@value\":\"onMatch\"}";
+ Assert.Equal(expectedGraphSON, serializedEnum);
+ }
+
+ [Theory, MemberData(nameof(Versions))]
public void ShouldSerializeList(int version)
{
var writer = CreateGraphSONWriter(version);
diff --git a/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/message/RequestMessage.java b/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/message/RequestMessage.java
index dc10baf..aeebf35 100644
--- a/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/message/RequestMessage.java
+++ b/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/message/RequestMessage.java
@@ -18,6 +18,7 @@
*/
package org.apache.tinkerpop.gremlin.driver.message;
+import org.apache.tinkerpop.gremlin.driver.Tokens;
import org.apache.tinkerpop.gremlin.structure.util.ElementHelper;
import java.util.HashMap;
@@ -34,7 +35,7 @@ public final class RequestMessage {
/**
* An "invalid" message. Used internally only.
*/
- public static final RequestMessage INVALID = new RequestMessage("invalid");
+ public static final RequestMessage INVALID = new RequestMessage(Tokens.OPS_INVALID);
private final UUID requestId;
private final String op;
diff --git a/gremlin-driver/src/test/java/org/apache/tinkerpop/gremlin/driver/ser/binary/GraphBinaryReaderWriterRoundTripTest.java b/gremlin-driver/src/test/java/org/apache/tinkerpop/gremlin/driver/ser/binary/GraphBinaryReaderWriterRoundTripTest.java
index 3fd54e2..1800331 100644
--- a/gremlin-driver/src/test/java/org/apache/tinkerpop/gremlin/driver/ser/binary/GraphBinaryReaderWriterRoundTripTest.java
+++ b/gremlin-driver/src/test/java/org/apache/tinkerpop/gremlin/driver/ser/binary/GraphBinaryReaderWriterRoundTripTest.java
@@ -27,13 +27,13 @@ import org.apache.tinkerpop.gremlin.process.traversal.Bytecode;
import org.apache.tinkerpop.gremlin.process.traversal.Operator;
import org.apache.tinkerpop.gremlin.process.traversal.Order;
import org.apache.tinkerpop.gremlin.process.traversal.P;
+import org.apache.tinkerpop.gremlin.process.traversal.Pick;
import org.apache.tinkerpop.gremlin.process.traversal.Pop;
import org.apache.tinkerpop.gremlin.process.traversal.SackFunctions;
import org.apache.tinkerpop.gremlin.process.traversal.Scope;
import org.apache.tinkerpop.gremlin.process.traversal.TextP;
import org.apache.tinkerpop.gremlin.process.traversal.TraversalSource;
import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource;
-import org.apache.tinkerpop.gremlin.process.traversal.step.TraversalOptionParent;
import org.apache.tinkerpop.gremlin.process.traversal.step.util.BulkSet;
import org.apache.tinkerpop.gremlin.process.traversal.step.util.Tree;
import org.apache.tinkerpop.gremlin.process.traversal.strategy.TraversalStrategyProxy;
@@ -221,7 +221,7 @@ public class GraphBinaryReaderWriterRoundTripTest {
new Object[] {"Operator", Operator.sum, null},
new Object[] {"Operator", Operator.div, null},
new Object[] {"Order", Order.desc, null},
- new Object[] {"Pick", TraversalOptionParent.Pick.any, null},
+ new Object[] {"Pick", Pick.any, null},
new Object[] {"Pop", Pop.mixed, null},
new Object[] {"Scope", Scope.global, null},
new Object[] {"T", T.label, null},
diff --git a/gremlin-groovy/src/main/groovy/org/apache/tinkerpop/gremlin/groovy/jsr223/ast/VarAsBindingASTTransformation.groovy b/gremlin-groovy/src/main/groovy/org/apache/tinkerpop/gremlin/groovy/jsr223/ast/VarAsBindingASTTransformation.groovy
index 1da973d..ea3c65a 100644
--- a/gremlin-groovy/src/main/groovy/org/apache/tinkerpop/gremlin/groovy/jsr223/ast/VarAsBindingASTTransformation.groovy
+++ b/gremlin-groovy/src/main/groovy/org/apache/tinkerpop/gremlin/groovy/jsr223/ast/VarAsBindingASTTransformation.groovy
@@ -20,6 +20,7 @@
package org.apache.tinkerpop.gremlin.groovy.jsr223.ast
import org.apache.tinkerpop.gremlin.process.traversal.Order
+import org.apache.tinkerpop.gremlin.process.traversal.Traversal
import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal
import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__
import org.apache.tinkerpop.gremlin.process.traversal.Translator
@@ -115,7 +116,11 @@ class VarAsBindingASTTransformation implements ASTTransformation {
case GraphTraversal.Symbols.by:
if (i == 1) bindingValue = new PropertyExpression(new ClassExpression(new ClassNode(Order)), "desc")
break
- case GraphTraversal.Symbols.fail:
+ case GraphTraversal.Symbols.mergeE:
+ case GraphTraversal.Symbols.mergeV:
+ bindingValue = new MethodCallExpression(new ClassExpression(new ClassNode(Collections)), "emptyMap", new TupleExpression())
+ break
+ case GraphTraversal.Symbols.option:
if (i == 1) bindingValue = new MethodCallExpression(new ClassExpression(new ClassNode(Collections)), "emptyMap", new TupleExpression())
break
}
diff --git a/gremlin-groovy/src/test/java/org/apache/tinkerpop/gremlin/groovy/jsr223/GremlinGroovyScriptEngineTest.java b/gremlin-groovy/src/test/java/org/apache/tinkerpop/gremlin/groovy/jsr223/GremlinGroovyScriptEngineTest.java
index 3baf20e..66b2dbd 100644
--- a/gremlin-groovy/src/test/java/org/apache/tinkerpop/gremlin/groovy/jsr223/GremlinGroovyScriptEngineTest.java
+++ b/gremlin-groovy/src/test/java/org/apache/tinkerpop/gremlin/groovy/jsr223/GremlinGroovyScriptEngineTest.java
@@ -484,6 +484,8 @@ public class GremlinGroovyScriptEngineTest {
final Traversal.Admin<Vertex, Vertex> t = (Traversal.Admin<Vertex, Vertex>)
engine.eval("g.V(v1Id).has(\"person\",\"age\",29).has('person','active',x).in(\"knows\")." +
System.lineSeparator() +
+ "mergeE(m1).mergeV(m2).option(Merge.onCreate,m3)." +
+ System.lineSeparator() +
"choose(__.out().count()).option(two, __.values(\"name\")).option(three, __.values(\"age\"))." +
System.lineSeparator() +
"filter(outE().count().is(y))." +
@@ -498,15 +500,18 @@ public class GremlinGroovyScriptEngineTest {
final String gremlinAsPython = translator.translate(bytecode).getScript();
final Map<String,Object> bytecodeBindings = bytecode.getBindings();
- assertEquals(7, bytecodeBindings.size());
+ assertEquals(10, bytecodeBindings.size());
assertThat(bytecodeBindings.containsKey("x"), is(true));
assertThat(bytecodeBindings.containsKey("y"), is(true));
assertThat(bytecodeBindings.containsKey("v1Id"), is(true));
assertThat(bytecodeBindings.containsKey("l"), is(true));
assertThat(bytecodeBindings.containsKey("o"), is(true));
+ assertThat(bytecodeBindings.containsKey("m1"), is(true));
+ assertThat(bytecodeBindings.containsKey("m2"), is(true));
+ assertThat(bytecodeBindings.containsKey("m3"), is(true));
assertThat(bytecodeBindings.containsKey("two"), is(true));
assertThat(bytecodeBindings.containsKey("three"), is(true));
- assertEquals("g.V(v1Id).has('person','age',29).has('person','active',x).in_('knows').choose(__.out().count()).option(two,__.name).option(three,__.age).filter_(__.outE().count().is_(y)).map(l).order().by('name',o)", gremlinAsPython);
+ assertEquals("g.V(v1Id).has('person','age',29).has('person','active',x).in_('knows').mergeE(m1).mergeV(m2).option(Merge.onCreate,m3).choose(__.out().count()).option(two,__.name).option(three,__.age).filter_(__.outE().count().is_(y)).map(l).order().by('name',o)", gremlinAsPython);
}
}
\ No newline at end of file
diff --git a/gremlin-javascript/build/generate.groovy b/gremlin-javascript/build/generate.groovy
index 013f840..1b52ea4 100644
--- a/gremlin-javascript/build/generate.groovy
+++ b/gremlin-javascript/build/generate.groovy
@@ -31,6 +31,16 @@ import java.nio.file.Paths
import static org.apache.tinkerpop.gremlin.process.traversal.AnonymousTraversalSource.traversal
+// getting an exception like:
+// > InvocationTargetException: javax.script.ScriptException: groovy.lang.MissingMethodException: No signature of
+// > method: org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.DefaultGraphTraversal.mergeE() is applicable for
+// > argument types: (String) values: [4ffdea36-4a0e-4681-acba-e76875d1b25b]
+// usually means bindings are not being extracted properly by VarAsBindingASTTransformation which typically happens
+// when a step is taking an argument that cannot properly resolve to the type required by the step itself. there are
+// special cases in that VarAsBindingASTTransformation class which might need to be adjusted. Editing the
+// GremlinGroovyScriptEngineTest#shouldProduceBindingsForVars() with the failing step and argument can typically make
+// this issue relatively easy to debug and enforce.
+
// file is overwritten on each generation
radishGremlinFile = new File("${projectBaseDir}/gremlin-javascript/src/main/javascript/gremlin-javascript/test/cucumber/gremlin.js")
@@ -84,6 +94,7 @@ radishGremlinFile.withWriter('UTF-8') { Writer writer ->
' IN: traversalModule.direction.in,\n' +
' OUT: traversalModule.direction.out\n' +
'};\n' +
+ 'const Merge = traversalModule.merge;\n' +
'const P = traversalModule.P;\n' +
'const Pick = traversalModule.pick\n' +
'const Pop = traversalModule.pop\n' +
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 6c49ec5..ae3d7f3 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
@@ -212,6 +212,16 @@ class GraphTraversalSource {
const b = new Bytecode(this.bytecode).addStep('addE', args);
return new this.graphTraversalClass(this.graph, new TraversalStrategies(this.traversalStrategies), b);
}
+
+ /**
+ * mergeV GraphTraversalSource step method.
+ * @param {...Object} args
+ * @returns {GraphTraversal}
+ */
+ mergeE(...args) {
+ const b = new Bytecode(this.bytecode).addStep('mergeE', args);
+ return new this.graphTraversalClass(this.graph, new TraversalStrategies(this.traversalStrategies), b);
+ }
/**
* addV GraphTraversalSource step method.
@@ -222,6 +232,16 @@ class GraphTraversalSource {
const b = new Bytecode(this.bytecode).addStep('addV', args);
return new this.graphTraversalClass(this.graph, new TraversalStrategies(this.traversalStrategies), b);
}
+
+ /**
+ * mergeV GraphTraversalSource step method.
+ * @param {...Object} args
+ * @returns {GraphTraversal}
+ */
+ mergeV(...args) {
+ const b = new Bytecode(this.bytecode).addStep('mergeV', args);
+ return new this.graphTraversalClass(this.graph, new TraversalStrategies(this.traversalStrategies), b);
+ }
/**
* inject GraphTraversalSource step method.
@@ -810,6 +830,26 @@ class GraphTraversal extends Traversal {
this.bytecode.addStep('mean', args);
return this;
}
+
+ /**
+ * Graph traversal mergeE method.
+ * @param {...Object} args
+ * @returns {GraphTraversal}
+ */
+ mergeE(...args) {
+ this.bytecode.addStep("mergeE", args);
+ return this;
+ }
+
+ /**
+ * Graph traversal mergeV method.
+ * @param {...Object} args
+ * @returns {GraphTraversal}
+ */
+ mergeV(...args) {
+ this.bytecode.addStep("mergeV", args);
+ return this;
+ }
/**
* Graph traversal min method.
@@ -1365,6 +1405,8 @@ const statics = {
math: (...args) => callOnEmptyTraversal('math', args),
max: (...args) => callOnEmptyTraversal('max', args),
mean: (...args) => callOnEmptyTraversal('mean', args),
+ mergeE: (...args) => callOnEmptyTraversal("mergeE", args),
+ mergeV: (...args) => callOnEmptyTraversal("mergeV", args),
min: (...args) => callOnEmptyTraversal('min', args),
not: (...args) => callOnEmptyTraversal('not', args),
optional: (...args) => callOnEmptyTraversal('optional', args),
diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/process/traversal.js b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/process/traversal.js
index f72c3f9..bbffeea 100644
--- a/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/process/traversal.js
+++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/process/traversal.js
@@ -496,6 +496,7 @@ module.exports = {
direction: toEnum('Direction', 'BOTH IN OUT'),
graphSONVersion: toEnum('GraphSONVersion', 'V1_0 V2_0 V3_0'),
gryoVersion: toEnum('GryoVersion', 'V1_0 V3_0'),
+ merge: toEnum('Merge', 'onCreate onMatch'),
operator: toEnum('Operator', 'addAll and assign div max min minus mult or sum sumLong'),
order: toEnum('Order', 'asc desc shuffle'),
pick: toEnum('Pick', 'any none'),
diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/test/cucumber/feature-steps.js b/gremlin-javascript/src/main/javascript/gremlin-javascript/test/cucumber/feature-steps.js
index d500feb..ba23650 100644
--- a/gremlin-javascript/src/main/javascript/gremlin-javascript/test/cucumber/feature-steps.js
+++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/test/cucumber/feature-steps.js
@@ -273,7 +273,13 @@ function toNumeric(stringValue) {
}
function toVertex(name) {
- return this.getData().vertices.get(name);
+ // some vertices are cached, like those from toy graphs but some are just references. if they are
+ // not cached then they are meant to be references.
+ const vertices = this.getData().vertices;
+ if (vertices.has(name))
+ return this.getData().vertices.get(name);
+ else
+ return new graphModule.Vertex(name, "vertex")
}
function toVertexId(name) {
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 0a452a6..2d6db86 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
@@ -36,6 +36,7 @@ const Direction = {
IN: traversalModule.direction.in,
OUT: traversalModule.direction.out
};
+const Merge = traversalModule.merge;
const P = traversalModule.P;
const Pick = traversalModule.pick
const Pop = traversalModule.pop
@@ -488,6 +489,32 @@ const gremlins = {
g_withStrategiesXProductiveByStrategyX_V_aggregateXaX_byXfooX_capXaX_unfold_mean: [function({g}) { return g.withStrategies(new ProductiveByStrategy({productiveKeys:[]})).V().aggregate("a").by("foo").cap("a").unfold().mean() }],
g_injectXnull_10_20_nullX_mean: [function({g, xx1, xx2}) { return g.inject(null,xx1,xx2,null).mean() }],
g_injectXlistXnull_10_20_nullXX_meanXlocalX: [function({g, xx1}) { return g.inject(xx1).mean(Scope.local) }],
+ g_V_mergeEXlabel_self_weight_05X: [function({g, xx1}) { return g.addV("person").property("name","marko").property("age",29) }, function({g, xx1}) { return g.V().mergeE(xx1) }, function({g, xx1}) { return g.E().hasLabel("self").has("weight",0.5) }],
+ g_mergeEXlabel_knows_out_marko_in_vadasX: [function({g, xx1}) { return g.addV("person").property(T.id,100).property("name","marko").addV("person").property(T.id,101).property("name","vadas") }, function({g, xx1}) { return g.mergeE(xx1) }, function({g, xx1}) { return g.V().has("person","name","marko").out("knows").has("person","name","vadas") }],
+ g_mergeEXlabel_knows_out_marko_in_vadas_weight_05X_exists: [function({g, xx1}) { return g.addV("person").property(T.id,100).property("name","marko").as("a").addV("person").property(T.id,101).property("name","vadas").as("b").addE("knows").from_("a").to("b") }, function({g, xx1}) { return g.mergeE(xx1) }, function({g, xx1}) { return g.V().has("person","name","marko").outE("knows").has("weight",0.5).inV().has("person","name","vadas") }, function({g, xx1}) { return g.V().has("person","na [...]
+ g_mergeEXlabel_knows_out_marko_in_vadas_weight_05X: [function({g, xx1}) { return g.mergeE(xx1) }, function({g, xx1}) { return g.V() }, function({g, xx1}) { return g.E().hasLabel("knows").has("weight",0.5) }],
+ g_mergeEXlabel_knows_out_marko_in_vadasX_optionXonCreate_created_YX_optionXonMatch_created_NX: [function({g, xx1, xx3, xx2}) { return g.mergeE(xx1).option(Merge.onCreate,xx2).option(Merge.onMatch,xx3) }, function({g, xx1, xx3, xx2}) { return g.V() }, function({g, xx1, xx3, xx2}) { return g.E().hasLabel("knows").has("created","Y") }, function({g, xx1, xx3, xx2}) { return g.E().hasLabel("knows").has("created","N") }],
+ g_mergeEXlabel_knows_out_marko_in_vadasX_optionXonCreate_created_YX_optionXonMatch_created_NX_exists: [function({g, xx1, xx3, xx2}) { return g.addV("person").property(T.id,100).property("name","marko").as("a").addV("person").property(T.id,101).property("name","vadas").as("b").addE("knows").from_("a").to("b") }, function({g, xx1, xx3, xx2}) { return g.mergeE(xx1).option(Merge.onCreate,xx2).option(Merge.onMatch,xx3) }, function({g, xx1, xx3, xx2}) { return g.V() }, function({g, xx1, xx [...]
+ g_mergeEXlabel_knows_out_marko_in_vadasX_optionXonCreate_created_YX_optionXonMatch_created_NX_exists_updated: [function({g, xx1, xx3, xx2}) { return g.addV("person").property(T.id,100).property("name","marko").as("a").addV("person").property(T.id,101).property("name","vadas").as("b").addE("knows").from_("a").to("b").property("created","Y") }, function({g, xx1, xx3, xx2}) { return g.mergeE(xx1).option(Merge.onCreate,xx2).option(Merge.onMatch,xx3) }, function({g, xx1, xx3, xx2}) { retu [...]
+ g_V_hasXperson_name_marko_X_mergeEXlabel_knowsX_optionXonCreate_created_YX_optionXonMatch_created_NX_exists_updated: [function({g, xx1, xx3, xx2}) { return g.addV("person").property(T.id,100).property("name","marko").as("a").addV("person").property(T.id,101).property("name","vadas").as("b").addE("knows").from_("a").to("b").property("created","Y").addE("knows").from_("a").to("b") }, function({g, xx1, xx3, xx2}) { return g.V().has("person","name","marko").mergeE(xx1).option(Merge.onCre [...]
+ g_VX100X_VX101X_mergeEXlabel_knows_out_marko_in_vadasX: [function({g, xx1}) { return g.addV("person").property(T.id,100).property("name","marko") }, function({g, xx1}) { return g.V(100).V(101).mergeE(xx1) }, function({g, xx1}) { return g.V() }, function({g, xx1}) { return g.E() }],
+ g_injectXlabel_knows_out_marko_in_vadasX_mergeE: [function({g, xx1}) { return g.addV("person").property(T.id,100).property("name","marko").addV("person").property(T.id,101).property("name","vadas") }, function({g, xx1}) { return g.inject(xx1).mergeE() }, function({g, xx1}) { return g.V().has("person","name","marko").out("knows").has("person","name","vadas") }],
+ g_mergeVXlabel_person_name_stephenX: [function({g, xx1}) { return g.addV("person").property("name","marko").property("age",29).as("marko") }, function({g, xx1}) { return g.mergeV(xx1) }, function({g, xx1}) { return g.V().has("person","name","stephen") }],
+ g_mergeVXlabel_person_name_markoX: [function({g, xx1}) { return g.addV("person").property("name","marko").property("age",29).as("marko") }, function({g, xx1}) { return g.mergeV(xx1) }, function({g, xx1}) { return g.V().has("person","name","marko") }],
+ g_mergeVXlabel_person_name_stephenX_optionXonCreate_label_person_name_stephen_age_19X_option: [function({g, xx1, xx2}) { return g.addV("person").property("name","marko").property("age",29).as("marko") }, function({g, xx1, xx2}) { return g.mergeV(xx1).option(Merge.onCreate,xx2) }, function({g, xx1, xx2}) { return g.V().has("person","name","stephen").has("age",19) }],
+ g_mergeVXlabel_person_name_markoX_optionXonMatch_age_19X_option: [function({g, xx1, xx2}) { return g.addV("person").property("name","marko").property("age",29).as("marko") }, function({g, xx1, xx2}) { return g.mergeV(xx1).option(Merge.onMatch,xx2) }, function({g, xx1, xx2}) { return g.V().has("person","name","marko").has("age",19) }],
+ g_withSideEffectXc_label_person_name_stephenX_withSideEffectXm_label_person_name_stephen_age_19X_mergeVXselectXcXX_optionXonCreate_selectXmXX_option: [function({g, xx1, xx2}) { return g.addV("person").property("name","marko").property("age",29).as("marko") }, function({g, xx1, xx2}) { return g.withSideEffect("c",xx1).withSideEffect("m",xx2).mergeV(__.select("c")).option(Merge.onCreate,__.select("m")) }, function({g, xx1, xx2}) { return g.V().has("person","name","stephen").has("age",19) }],
+ g_withSideEffectXc_label_person_name_markoX_withSideEffectXm_age_19X_mergeVXselectXcXX_optionXonMatch_selectXmXX_option: [function({g, xx1, xx2}) { return g.addV("person").property("name","marko").property("age",29).as("marko") }, function({g, xx1, xx2}) { return g.withSideEffect("c",xx1).withSideEffect("m",xx2).mergeV(__.select("c")).option(Merge.onMatch,__.select("m")) }, function({g, xx1, xx2}) { return g.V().has("person","name","marko").has("age",19) }],
+ g_mergeVXlabel_person_name_markoX_propertyXname_vadas_acl_publicX: [function({g, xx1}) { return g.addV("person").property("name","marko").property("age",29).as("marko") }, function({g, xx1}) { return g.mergeV(xx1).property("name","vadas","acl","public") }, function({g, xx1}) { return g.V().properties("name").hasValue("vadas").has("acl","public") }],
+ g_injectX0X_mergeVXlabel_person_name_stephenX: [function({g, xx1}) { return g.addV("person").property("name","marko").property("age",29).as("marko") }, function({g, xx1}) { return g.inject(0).mergeV(xx1) }, function({g, xx1}) { return g.V().has("person","name","stephen") }],
+ g_injectX0X_mergeVXlabel_person_name_markoX: [function({g, xx1}) { return g.addV("person").property("name","marko").property("age",29).as("marko") }, function({g, xx1}) { return g.inject(0).mergeV(xx1) }, function({g, xx1}) { return g.V().has("person","name","marko") }],
+ g_injectX0X_mergeVXlabel_person_name_stephenX_optionXonCreate_label_person_name_stephen_age_19X_option: [function({g, xx1, xx2}) { return g.addV("person").property("name","marko").property("age",29).as("marko") }, function({g, xx1, xx2}) { return g.inject(0).mergeV(xx1).option(Merge.onCreate,xx2) }, function({g, xx1, xx2}) { return g.V().has("person","name","stephen").has("age",19) }],
+ g_injectX0X_mergeVXlabel_person_name_markoX_optionXonMatch_age_19X_option: [function({g, xx1, xx2}) { return g.addV("person").property("name","marko").property("age",29).as("marko") }, function({g, xx1, xx2}) { return g.inject(0).mergeV(xx1).option(Merge.onMatch,xx2) }, function({g, xx1, xx2}) { return g.V().has("person","name","marko").has("age",19) }],
+ g_withSideEffectXc_label_person_name_stephenX_withSideEffectXm_label_person_name_stephen_age_19X_injectX0X_mergeVXselectXcXX_optionXonCreate_selectXmXX_option: [function({g, xx1, xx2}) { return g.addV("person").property("name","marko").property("age",29).as("marko") }, function({g, xx1, xx2}) { return g.withSideEffect("c",xx1).withSideEffect("m",xx2).inject(0).mergeV(__.select("c")).option(Merge.onCreate,__.select("m")) }, function({g, xx1, xx2}) { return g.V().has("person","name","s [...]
+ g_withSideEffectXc_label_person_name_markoX_withSideEffectXm_age_19X_injectX0X_mergeVXselectXcXX_optionXonMatch_selectXmXX_option: [function({g, xx1, xx2}) { return g.addV("person").property("name","marko").property("age",29).as("marko") }, function({g, xx1, xx2}) { return g.withSideEffect("c",xx1).withSideEffect("m",xx2).inject(0).mergeV(__.select("c")).option(Merge.onMatch,__.select("m")) }, function({g, xx1, xx2}) { return g.V().has("person","name","marko").has("age",19) }],
+ g_injectX0X_mergeVXlabel_person_name_markoX_propertyXname_vadas_acl_publicX: [function({g, xx1}) { return g.addV("person").property("name","marko").property("age",29).as("marko") }, function({g, xx1}) { return g.inject(0).mergeV(xx1).property("name","vadas","acl","public") }, function({g, xx1}) { return g.V().properties("name").hasValue("vadas").has("acl","public") }],
+ g_injectXlabel_person_name_marko_label_person_name_stephenX_mergeVXidentityX: [function({g, xx1, xx2}) { return g.addV("person").property("name","marko").property("age",29).as("marko") }, function({g, xx1, xx2}) { return g.inject(xx1,xx2).mergeV(__.identity()) }, function({g, xx1, xx2}) { return g.V().has("person","name","stephen") }, function({g, xx1, xx2}) { return g.V().has("person","name","marko") }, function({g, xx1, xx2}) { return g.V() }],
+ g_injectXlabel_person_name_marko_label_person_name_stephenX_mergeV: [function({g, xx1, xx2}) { return g.addV("person").property("name","marko").property("age",29).as("marko") }, function({g, xx1, xx2}) { return g.inject(xx1,xx2).mergeV() }, function({g, xx1, xx2}) { return g.V().has("person","name","stephen") }, function({g, xx1, xx2}) { return g.V().has("person","name","marko") }, function({g, xx1, xx2}) { return g.V() }],
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() }],
@@ -795,7 +822,6 @@ const gremlins = {
g_V_outXfollowedByX_group_byXsongTypeX_byXbothE_group_byXlabelX_byXweight_sumXX: [function({g}) { return g.V().out("followedBy").group().by("songType").by(__.bothE().group().by(T.label).by(__.values("weight").sum())) }],
g_V_groupXmX_byXnameX_byXinXknowsX_nameX_capXmX: [function({g}) { return g.V().group("m").by("name").by(__.in_("knows").values("name")).cap("m") }],
g_V_group_byXlabelX_byXbothE_groupXaX_byXlabelX_byXweight_sumX_weight_sumX: [function({g}) { return g.V().group().by(T.label).by(__.bothE().group("a").by(T.label).by(__.values("weight").sum()).values("weight").sum()) }],
- g_withSideEffectXa__marko_666_noone_blahX_V_groupXaX_byXnameX_byXoutE_label_foldX_capXaX: [function({g, xx1}) { return g.withSideEffect("a",xx1).V().group("a").by("name").by(__.outE().label().fold()).cap("a").unfold().group().by(Column.keys).by(__.select(Column.values).order(Scope.local).by(Order.asc)) }],
g_V_hasLabelXpersonX_asXpX_outXcreatedX_group_byXnameX_byXselectXpX_valuesXageX_sumX: [function({g}) { return g.V().hasLabel("person").as("p").out("created").group().by("name").by(__.select("p").values("age").sum()) }],
g_V_hasLabelXpersonX_asXpX_outXcreatedX_groupXaX_byXnameX_byXselectXpX_valuesXageX_sumX_capXaX: [function({g}) { return g.V().hasLabel("person").as("p").out("created").group("a").by("name").by(__.select("p").values("age").sum()).cap("a") }],
g_V_group_byXlabelX_byXlabel_countX: [function({g}) { return g.V().group().by(__.label()).by(__.label().count()) }],
@@ -818,7 +844,7 @@ const gremlins = {
g_V_hasXperson_name_markoX_bothXknowsX_groupCount_byXvaluesXnameX_foldX: [function({g}) { return g.V().has("person","name","marko").both("knows").groupCount().by(__.values("name").fold()) }],
g_VX1X_out_injectXv2X_name: [function({g, vid1, v2}) { return g.V(vid1).out().inject(v2).values("name") }],
g_VX1X_out_name_injectXdanielX_asXaX_mapXlengthX_path: [function({g, l1, vid1}) { return g.V(vid1).out().values("name").inject("daniel").as("a").map(l1).path() }],
- g_VX1X_injectXg_VX4XX_out_name: [function({g, vid1, v4}) { return g.V(vid1).inject(v4).out().values("name") }],
+ g_VX1X_injectXg_VX4XX_out_name: [function({g, vid1, v2}) { return g.V(vid1).inject(v2).out().values("name") }],
g_injectXnull_1_3_nullX: [function({g}) { return g.inject(null,1,3,null) }],
g_injectX10_20_null_20_10_10X_groupCountXxX_dedup_asXyX_projectXa_bX_by_byXselectXxX_selectXselectXyXXX: [function({g}) { return g.inject(10,20,null,20,10,10).groupCount("x").dedup().as("y").project("a","b").by().by(__.select("x").select(__.select("y"))) }],
g_injectXname_marko_age_nullX_selectXname_ageX: [function({g, xx1}) { return g.inject(xx1).select("name","age") }],
diff --git a/gremlin-language/src/main/antlr4/Gremlin.g4 b/gremlin-language/src/main/antlr4/Gremlin.g4
index 78d8928..7095e7b 100644
--- a/gremlin-language/src/main/antlr4/Gremlin.g4
+++ b/gremlin-language/src/main/antlr4/Gremlin.g4
@@ -97,6 +97,8 @@ traversalSourceSpawnMethod
| traversalSourceSpawnMethod_addV
| traversalSourceSpawnMethod_E
| traversalSourceSpawnMethod_V
+ | traversalSourceSpawnMethod_mergeE
+ | traversalSourceSpawnMethod_mergeV
| traversalSourceSpawnMethod_inject
| traversalSourceSpawnMethod_io
;
@@ -128,6 +130,16 @@ traversalSourceSpawnMethod_io
: 'io' LPAREN stringLiteral RPAREN
;
+traversalSourceSpawnMethod_mergeV
+ : 'mergeV' LPAREN genericLiteralMap RPAREN #traversalSourceSpawnMethod_mergeV_Map
+ | 'mergeV' LPAREN nestedTraversal RPAREN #traversalSourceSpawnMethod_mergeV_Traversal
+ ;
+
+traversalSourceSpawnMethod_mergeE
+ : 'mergeE' LPAREN genericLiteralMap RPAREN #traversalSourceSpawnMethod_mergeE_Map
+ | 'mergeE' LPAREN nestedTraversal RPAREN #traversalSourceSpawnMethod_mergeE_Traversal
+ ;
+
chainedTraversal
: traversalMethod
| chainedTraversal DOT traversalMethod
@@ -157,6 +169,8 @@ traversalMethod
: traversalMethod_V
| traversalMethod_addE
| traversalMethod_addV
+ | traversalMethod_mergeE
+ | traversalMethod_mergeV
| traversalMethod_aggregate
| traversalMethod_and
| traversalMethod_as
@@ -272,6 +286,19 @@ traversalMethod_addV
| 'addV' LPAREN nestedTraversal RPAREN #traversalMethod_addV_Traversal
;
+traversalMethod_mergeV
+ : 'mergeV' LPAREN RPAREN #traversalMethod_mergeV_empty
+ | 'mergeV' LPAREN genericLiteralMap RPAREN #traversalMethod_mergeV_Map
+ | 'mergeV' LPAREN nestedTraversal RPAREN #traversalMethod_mergeV_Traversal
+ ;
+
+traversalMethod_mergeE
+ : 'mergeE' LPAREN RPAREN #traversalMethod_mergeE_empty
+ | 'mergeE' LPAREN genericLiteralMap RPAREN #traversalMethod_mergeE_Map
+ | 'mergeE' LPAREN nestedTraversal RPAREN #traversalMethod_mergeE_Traversal
+ ;
+
+
traversalMethod_aggregate
: 'aggregate' LPAREN traversalScope COMMA stringLiteral RPAREN #traversalMethod_aggregate_Scope_String
| 'aggregate' LPAREN stringLiteral RPAREN #traversalMethod_aggregate_String
@@ -531,6 +558,8 @@ traversalMethod_not
traversalMethod_option
: 'option' LPAREN traversalPredicate COMMA nestedTraversal RPAREN #traversalMethod_option_Predicate_Traversal
+ | 'option' LPAREN traversalMerge COMMA genericLiteralMap RPAREN #traversalMethod_option_Merge_Map
+ | 'option' LPAREN traversalMerge COMMA nestedTraversal RPAREN #traversalMethod_option_Merge_Traversal
| 'option' LPAREN genericLiteral COMMA nestedTraversal RPAREN #traversalMethod_option_Object_Traversal
| 'option' LPAREN nestedTraversal RPAREN #traversalMethod_option_Traversal
;
@@ -837,6 +866,11 @@ traversalToken
| 'value' | 'T.value'
;
+traversalMerge
+ : 'onCreate' | 'Merge.onCreate'
+ | 'onMatch' | 'Merge.onMatch'
+ ;
+
traversalOrder
: 'incr' | 'Order.incr'
| 'decr' | 'Order.decr'
@@ -1310,6 +1344,7 @@ genericLiteral
| traversalCardinality
| traversalDirection
| traversalOptionParent
+ | structureVertex
| genericLiteralCollection
| genericLiteralRange
| nestedTraversal
diff --git a/gremlin-language/src/main/java/org/apache/tinkerpop/gremlin/language/corpus/DocumentationReader.java b/gremlin-language/src/main/java/org/apache/tinkerpop/gremlin/language/corpus/DocumentationReader.java
index ba7c749..6c091e5 100644
--- a/gremlin-language/src/main/java/org/apache/tinkerpop/gremlin/language/corpus/DocumentationReader.java
+++ b/gremlin-language/src/main/java/org/apache/tinkerpop/gremlin/language/corpus/DocumentationReader.java
@@ -105,6 +105,8 @@ public class DocumentationReader {
replace("vMarko", "\"marko\"").
replace("vPeter", "\"peter\"").
replace("vStephen", "\"stephen\"").
+ replace("v1", "new Vertex(1,\"vertex\")").
+ replace("v2", "new Vertex(2,\"vertex\")").
replace("input.head()", "\"head\"").
replace("input.tail().size()", "6").
replace("input.tail()", "\"tail\"").
diff --git a/gremlin-language/src/test/java/org/apache/tinkerpop/gremlin/language/grammar/ReferenceGrammarTest.java b/gremlin-language/src/test/java/org/apache/tinkerpop/gremlin/language/grammar/ReferenceGrammarTest.java
index b3d3f29..b241c59 100644
--- a/gremlin-language/src/test/java/org/apache/tinkerpop/gremlin/language/grammar/ReferenceGrammarTest.java
+++ b/gremlin-language/src/test/java/org/apache/tinkerpop/gremlin/language/grammar/ReferenceGrammarTest.java
@@ -49,10 +49,20 @@ public class ReferenceGrammarTest extends AbstractGrammarTest {
private static final String featureDir = Paths.get("..", "gremlin-test", "features").toString();
private static final String docsDir = Paths.get("..", "docs", "src").toString();
- private static final Pattern vertexPattern = Pattern.compile(".*v\\d.*");
private static final Pattern edgePattern = Pattern.compile(".*e\\d.*");
private static final List<Pair<Pattern, BiFunction<String,String,String>>> stringMatcherConverters = new ArrayList<Pair<Pattern, BiFunction<String,String,String>>>() {{
+ add(Pair.with(Pattern.compile("m\\[(.*)\\]"), (k,v) -> {
+ // can't handled embedded maps because of the string replace below on the curly braces
+ final String[] items = v.replace("{", "").replace("}", "").split(",");
+ final String listItems = Stream.of(items).map(String::trim).map(x -> {
+ final String[] pair = x.split(":");
+ final String convertedKey = String.format("%s", pair[0]);
+ final String convertedValue = String.format("%s", pair[1]);
+ return String.format("%s:%s", convertedKey, convertedValue);
+ }).collect(Collectors.joining(","));
+ return String.format("[%s]", listItems);
+ }));
add(Pair.with(Pattern.compile("l\\[\\]"), (k,v) -> "[]"));
add(Pair.with(Pattern.compile("l\\[(.*)\\]"), (k,v) -> {
final String[] items = v.split(",");
@@ -60,6 +70,7 @@ public class ReferenceGrammarTest extends AbstractGrammarTest {
return String.format("[%s]", listItems);
}));
add(Pair.with(Pattern.compile("v\\[(.+)\\]"), (k,v) -> "\"1\""));
+ add(Pair.with(Pattern.compile("v(\\d)"), (k,v) -> String.format("new Vertex(%s, \"vertex\")", v)));
add(Pair.with(Pattern.compile("e\\[(.+)\\]"), (k,v) -> "\"1\""));
add(Pair.with(Pattern.compile("d\\[(.*)\\]\\.?.*"), (k,v) -> v));
add(Pair.with(Pattern.compile("m\\[(.*)\\]"), (k,v) -> v.replace('{','[').replace('}', ']')));
@@ -89,11 +100,12 @@ public class ReferenceGrammarTest extends AbstractGrammarTest {
@Test
public void test_parse() {
+ // can't handle maps with complex embedded types at the moment - basically one Gremlin statement at this point
+ assumeThat("Complex embedded types are not supported", query.contains("l[\"666\"]"), is(false));
assumeThat("Lambdas are not supported", query.contains("Lambda.function("), is(false));
// start of a closure
assumeThat("Lambdas are not supported", query.contains("{"), is(false));
assumeThat("withComputer() step is not supported", query.startsWith("g.withComputer("), is(false));
- assumeThat("Vertex instances are not supported", vertexPattern.matcher(query).matches(), is(false));
assumeThat("Edge instances are not supported", edgePattern.matcher(query).matches(), is(false));
assumeThat("fill() terminator is not supported", query.contains("fill("), is(false));
assumeThat("withoutStrategies() is not supported", query.contains("withoutStrategies("), is(false));
diff --git a/gremlin-python/build/generate.groovy b/gremlin-python/build/generate.groovy
index f0a3d45..0c6dd42 100644
--- a/gremlin-python/build/generate.groovy
+++ b/gremlin-python/build/generate.groovy
@@ -31,6 +31,16 @@ import java.nio.file.Paths
import static org.apache.tinkerpop.gremlin.process.traversal.AnonymousTraversalSource.traversal
+// getting an exception like:
+// > InvocationTargetException: javax.script.ScriptException: groovy.lang.MissingMethodException: No signature of
+// > method: org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.DefaultGraphTraversal.mergeE() is applicable for
+// > argument types: (String) values: [4ffdea36-4a0e-4681-acba-e76875d1b25b]
+// usually means bindings are not being extracted properly by VarAsBindingASTTransformation which typically happens
+// when a step is taking an argument that cannot properly resolve to the type required by the step itself. there are
+// special cases in that VarAsBindingASTTransformation class which might need to be adjusted. Editing the
+// GremlinGroovyScriptEngineTest#shouldProduceBindingsForVars() with the failing step and argument can typically make
+// this issue relatively easy to debug and enforce.
+
// file is overwritten on each generation
radishGremlinFile = new File("${projectBaseDir}/gremlin-python/src/main/python/radish/gremlin.py")
@@ -77,7 +87,7 @@ radishGremlinFile.withWriter('UTF-8') { Writer writer ->
'from gremlin_python.process.traversal import TraversalStrategy\n' +
'from gremlin_python.process.graph_traversal import __\n' +
'from gremlin_python.structure.graph import Graph\n' +
- 'from gremlin_python.process.traversal import Barrier, Cardinality, P, TextP, Pop, Scope, Column, Order, Direction, T, Pick, Operator, IO, WithOptions\n')
+ 'from gremlin_python.process.traversal import Barrier, Cardinality, P, TextP, Pop, Scope, Column, Order, Direction, Merge, T, Pick, Operator, IO, WithOptions\n')
// 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
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 525bc9d..c8e8ae4 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
@@ -144,6 +144,16 @@ class GraphTraversalSource(object):
traversal.bytecode.add_step("addV", *args)
return traversal
+ def mergeV(self, *args):
+ traversal = self.get_graph_traversal()
+ traversal.bytecode.add_step("mergeV", *args)
+ return traversal
+
+ def mergeE(self, *args):
+ traversal = self.get_graph_traversal()
+ traversal.bytecode.add_step("mergeE", *args)
+ return traversal
+
def inject(self, *args):
traversal = self.get_graph_traversal()
traversal.bytecode.add_step("inject", *args)
@@ -401,6 +411,14 @@ class GraphTraversal(Traversal):
self.bytecode.add_step("mean", *args)
return self
+ def mergeE(self, *args):
+ self.bytecode.add_step("mergeE", *args)
+ return self
+
+ def mergeV(self, *args):
+ self.bytecode.add_step("mergeV", *args)
+ return self
+
def min_(self, *args):
self.bytecode.add_step("min", *args)
return self
@@ -827,6 +845,14 @@ class __(object, metaclass=MagicType):
return cls.graph_traversal(None, None, Bytecode()).mean(*args)
@classmethod
+ def mergeE(cls, *args):
+ return cls.graph_traversal(None, None, Bytecode()).mergeE(*args)
+
+ @classmethod
+ def mergeV(cls, *args):
+ return cls.graph_traversal(None, None, Bytecode()).mergeV(*args)
+
+ @classmethod
def min_(cls, *args):
return cls.graph_traversal(None, None, Bytecode()).min_(*args)
@@ -1254,6 +1280,14 @@ def mean(*args):
return __.mean(*args)
+def mergeE(*args):
+ return __.mergeE(*args)
+
+
+def mergeV(*args):
+ return __.mergeV(*args)
+
+
def min_(*args):
return __.min_(*args)
@@ -1514,6 +1548,10 @@ statics.add_static('max_', max_)
statics.add_static('mean', mean)
+statics.add_static('mergeE', mergeE)
+
+statics.add_static('mergeV', mergeV)
+
statics.add_static('min_', min_)
statics.add_static('not_', not_)
diff --git a/gremlin-python/src/main/python/gremlin_python/process/traversal.py b/gremlin-python/src/main/python/gremlin_python/process/traversal.py
index f7bd31d..e24948c 100644
--- a/gremlin-python/src/main/python/gremlin_python/process/traversal.py
+++ b/gremlin-python/src/main/python/gremlin_python/process/traversal.py
@@ -152,6 +152,11 @@ GryoVersion = Enum('GryoVersion', ' V1_0 V3_0')
statics.add_static('V1_0', GryoVersion.V1_0)
statics.add_static('V3_0', GryoVersion.V3_0)
+Merge = Enum('Merge', ' onCreate onMatch')
+
+statics.add_static('onCreate', Merge.onCreate)
+statics.add_static('onMatch', Merge.onMatch)
+
Order = Enum('Order', ' asc desc shuffle')
statics.add_static('shuffle', Order.shuffle)
diff --git a/gremlin-python/src/main/python/gremlin_python/structure/io/graphbinaryV1.py b/gremlin-python/src/main/python/gremlin_python/structure/io/graphbinaryV1.py
index a5a258a..f39197a 100644
--- a/gremlin-python/src/main/python/gremlin_python/structure/io/graphbinaryV1.py
+++ b/gremlin-python/src/main/python/gremlin_python/structure/io/graphbinaryV1.py
@@ -33,8 +33,8 @@ from datetime import timedelta
from gremlin_python import statics
from gremlin_python.statics import FloatType, FunctionType, IntType, LongType, TypeType, DictType, ListType, SetType, \
SingleByte, ByteBufferType, GremlinType, SingleChar
-from gremlin_python.process.traversal import Barrier, Binding, Bytecode, Cardinality, Column, Direction, Operator, \
- Order, Pick, Pop, P, Scope, TextP, Traversal, Traverser, \
+from gremlin_python.process.traversal import Barrier, Binding, Bytecode, Cardinality, Column, Direction, Merge, \
+ Operator, Order, Pick, Pop, P, Scope, TextP, Traversal, Traverser, \
TraversalStrategy, T
from gremlin_python.process.graph_traversal import GraphTraversal
from gremlin_python.structure.graph import Graph, Edge, Property, Vertex, VertexProperty, Path
@@ -96,6 +96,7 @@ class DataType(Enum):
tree = 0x2b # not supported - no tree object in Python yet
metrics = 0x2c
traversalmetrics = 0x2d
+ merge = 0x2e
char = 0x80
duration = 0x81
inetaddress = 0x82 # todo
@@ -850,6 +851,11 @@ class PSerializer(_GraphBinaryTypeIO):
return to_extend
+class MergeIO(_EnumIO):
+ graphbinary_type = DataType.merge
+ python_type = Merge
+
+
class ScopeIO(_EnumIO):
graphbinary_type = DataType.scope
python_type = Scope
diff --git a/gremlin-python/src/main/python/radish/feature_steps.py b/gremlin-python/src/main/python/radish/feature_steps.py
index 66bfc45..6d0a72d 100644
--- a/gremlin-python/src/main/python/radish/feature_steps.py
+++ b/gremlin-python/src/main/python/radish/feature_steps.py
@@ -20,7 +20,7 @@
import json
import re
from gremlin_python.statics import long
-from gremlin_python.structure.graph import Path
+from gremlin_python.structure.graph import Path, Vertex
from gremlin_python.process.anonymous_traversal import traversal
from gremlin_python.process.graph_traversal import __
from gremlin_python.process.traversal import Barrier, Cardinality, P, TextP, Pop, Scope, Column, Order, Direction, T, Pick, Operator, IO, WithOptions
@@ -221,7 +221,11 @@ def __find_cached_element(ctx, graph_name, identifier, element_type):
else:
cache = ctx.lookup_v[graph_name] if element_type == "v" else ctx.lookup_e[graph_name]
- return cache[identifier]
+ # try to lookup the element - if it can't be found then it must be a reference Vertex
+ if identifier in cache:
+ return cache[identifier]
+ else:
+ return Vertex(identifier)
def _convert_results(val):
diff --git a/gremlin-python/src/main/python/radish/gremlin.py b/gremlin-python/src/main/python/radish/gremlin.py
index 5ec3509..16ee2a2 100644
--- a/gremlin-python/src/main/python/radish/gremlin.py
+++ b/gremlin-python/src/main/python/radish/gremlin.py
@@ -29,7 +29,7 @@ from gremlin_python.process.anonymous_traversal import traversal
from gremlin_python.process.traversal import TraversalStrategy
from gremlin_python.process.graph_traversal import __
from gremlin_python.structure.graph import Graph
-from gremlin_python.process.traversal import Barrier, Cardinality, P, TextP, Pop, Scope, Column, Order, Direction, T, Pick, Operator, IO, WithOptions
+from gremlin_python.process.traversal import Barrier, Cardinality, P, TextP, Pop, Scope, Column, Order, Direction, Merge, T, Pick, Operator, IO, WithOptions
world.gremlins = {
'g_V_branchXlabel_eq_person__a_bX_optionXa__ageX_optionXb__langX_optionXb__nameX': [(lambda g, l1=None:g.V().branch(l1).option('a',__.age).option('b',__.lang).option('b',__.name))],
@@ -473,6 +473,32 @@ world.gremlins = {
'g_withStrategiesXProductiveByStrategyX_V_aggregateXaX_byXfooX_capXaX_unfold_mean': [(lambda g:g.withStrategies(*[TraversalStrategy('ProductiveByStrategy',{'productiveKeys':[],'strategy':'org.apache.tinkerpop.gremlin.process.traversal.strategy.optimization.ProductiveByStrategy'}, 'org.apache.tinkerpop.gremlin.process.traversal.strategy.optimization.ProductiveByStrategy')]).V().aggregate('a').by('foo').cap('a').unfold().mean())],
'g_injectXnull_10_20_nullX_mean': [(lambda g, xx1=None,xx2=None:g.inject(None,xx1,xx2,None).mean())],
'g_injectXlistXnull_10_20_nullXX_meanXlocalX': [(lambda g, xx1=None:g.inject(xx1).mean(Scope.local))],
+ 'g_V_mergeEXlabel_self_weight_05X': [(lambda g, xx1=None:g.addV('person').property('name','marko').property('age',29)), (lambda g, xx1=None:g.V().mergeE(xx1)), (lambda g, xx1=None:g.E().hasLabel('self').has('weight',float(0.5)))],
+ 'g_mergeEXlabel_knows_out_marko_in_vadasX': [(lambda g, xx1=None:g.addV('person').property(T.id_,100).property('name','marko').addV('person').property(T.id_,101).property('name','vadas')), (lambda g, xx1=None:g.mergeE(xx1)), (lambda g, xx1=None:g.V().has('person','name','marko').out('knows').has('person','name','vadas'))],
+ 'g_mergeEXlabel_knows_out_marko_in_vadas_weight_05X_exists': [(lambda g, xx1=None:g.addV('person').property(T.id_,100).property('name','marko').as_('a').addV('person').property(T.id_,101).property('name','vadas').as_('b').addE('knows').from_('a').to('b')), (lambda g, xx1=None:g.mergeE(xx1)), (lambda g, xx1=None:g.V().has('person','name','marko').outE('knows').has('weight',float(0.5)).inV().has('person','name','vadas')), (lambda g, xx1=None:g.V().has('person','name','marko').outE('kno [...]
+ 'g_mergeEXlabel_knows_out_marko_in_vadas_weight_05X': [(lambda g, xx1=None:g.mergeE(xx1)), (lambda g, xx1=None:g.V()), (lambda g, xx1=None:g.E().hasLabel('knows').has('weight',float(0.5)))],
+ 'g_mergeEXlabel_knows_out_marko_in_vadasX_optionXonCreate_created_YX_optionXonMatch_created_NX': [(lambda g, xx1=None,xx3=None,xx2=None:g.mergeE(xx1).option(Merge.onCreate,xx2).option(Merge.onMatch,xx3)), (lambda g, xx1=None,xx3=None,xx2=None:g.V()), (lambda g, xx1=None,xx3=None,xx2=None:g.E().hasLabel('knows').has('created','Y')), (lambda g, xx1=None,xx3=None,xx2=None:g.E().hasLabel('knows').has('created','N'))],
+ 'g_mergeEXlabel_knows_out_marko_in_vadasX_optionXonCreate_created_YX_optionXonMatch_created_NX_exists': [(lambda g, xx1=None,xx3=None,xx2=None:g.addV('person').property(T.id_,100).property('name','marko').as_('a').addV('person').property(T.id_,101).property('name','vadas').as_('b').addE('knows').from_('a').to('b')), (lambda g, xx1=None,xx3=None,xx2=None:g.mergeE(xx1).option(Merge.onCreate,xx2).option(Merge.onMatch,xx3)), (lambda g, xx1=None,xx3=None,xx2=None:g.V()), (lambda g, xx1=No [...]
+ 'g_mergeEXlabel_knows_out_marko_in_vadasX_optionXonCreate_created_YX_optionXonMatch_created_NX_exists_updated': [(lambda g, xx1=None,xx3=None,xx2=None:g.addV('person').property(T.id_,100).property('name','marko').as_('a').addV('person').property(T.id_,101).property('name','vadas').as_('b').addE('knows').from_('a').to('b').property('created','Y')), (lambda g, xx1=None,xx3=None,xx2=None:g.mergeE(xx1).option(Merge.onCreate,xx2).option(Merge.onMatch,xx3)), (lambda g, xx1=None,xx3=None,xx [...]
+ 'g_V_hasXperson_name_marko_X_mergeEXlabel_knowsX_optionXonCreate_created_YX_optionXonMatch_created_NX_exists_updated': [(lambda g, xx1=None,xx3=None,xx2=None:g.addV('person').property(T.id_,100).property('name','marko').as_('a').addV('person').property(T.id_,101).property('name','vadas').as_('b').addE('knows').from_('a').to('b').property('created','Y').addE('knows').from_('a').to('b')), (lambda g, xx1=None,xx3=None,xx2=None:g.V().has('person','name','marko').mergeE(xx1).option(Merge. [...]
+ 'g_VX100X_VX101X_mergeEXlabel_knows_out_marko_in_vadasX': [(lambda g, xx1=None:g.addV('person').property(T.id_,100).property('name','marko')), (lambda g, xx1=None:g.V(100).V(101).mergeE(xx1)), (lambda g, xx1=None:g.V()), (lambda g, xx1=None:g.E())],
+ 'g_injectXlabel_knows_out_marko_in_vadasX_mergeE': [(lambda g, xx1=None:g.addV('person').property(T.id_,100).property('name','marko').addV('person').property(T.id_,101).property('name','vadas')), (lambda g, xx1=None:g.inject(xx1).mergeE()), (lambda g, xx1=None:g.V().has('person','name','marko').out('knows').has('person','name','vadas'))],
+ 'g_mergeVXlabel_person_name_stephenX': [(lambda g, xx1=None:g.addV('person').property('name','marko').property('age',29).as_('marko')), (lambda g, xx1=None:g.mergeV(xx1)), (lambda g, xx1=None:g.V().has('person','name','stephen'))],
+ 'g_mergeVXlabel_person_name_markoX': [(lambda g, xx1=None:g.addV('person').property('name','marko').property('age',29).as_('marko')), (lambda g, xx1=None:g.mergeV(xx1)), (lambda g, xx1=None:g.V().has('person','name','marko'))],
+ 'g_mergeVXlabel_person_name_stephenX_optionXonCreate_label_person_name_stephen_age_19X_option': [(lambda g, xx1=None,xx2=None:g.addV('person').property('name','marko').property('age',29).as_('marko')), (lambda g, xx1=None,xx2=None:g.mergeV(xx1).option(Merge.onCreate,xx2)), (lambda g, xx1=None,xx2=None:g.V().has('person','name','stephen').has('age',19))],
+ 'g_mergeVXlabel_person_name_markoX_optionXonMatch_age_19X_option': [(lambda g, xx1=None,xx2=None:g.addV('person').property('name','marko').property('age',29).as_('marko')), (lambda g, xx1=None,xx2=None:g.mergeV(xx1).option(Merge.onMatch,xx2)), (lambda g, xx1=None,xx2=None:g.V().has('person','name','marko').has('age',19))],
+ 'g_withSideEffectXc_label_person_name_stephenX_withSideEffectXm_label_person_name_stephen_age_19X_mergeVXselectXcXX_optionXonCreate_selectXmXX_option': [(lambda g, xx1=None,xx2=None:g.addV('person').property('name','marko').property('age',29).as_('marko')), (lambda g, xx1=None,xx2=None:g.withSideEffect('c',xx1).withSideEffect('m',xx2).mergeV(__.select('c')).option(Merge.onCreate,__.select('m'))), (lambda g, xx1=None,xx2=None:g.V().has('person','name','stephen').has('age',19))],
+ 'g_withSideEffectXc_label_person_name_markoX_withSideEffectXm_age_19X_mergeVXselectXcXX_optionXonMatch_selectXmXX_option': [(lambda g, xx1=None,xx2=None:g.addV('person').property('name','marko').property('age',29).as_('marko')), (lambda g, xx1=None,xx2=None:g.withSideEffect('c',xx1).withSideEffect('m',xx2).mergeV(__.select('c')).option(Merge.onMatch,__.select('m'))), (lambda g, xx1=None,xx2=None:g.V().has('person','name','marko').has('age',19))],
+ 'g_mergeVXlabel_person_name_markoX_propertyXname_vadas_acl_publicX': [(lambda g, xx1=None:g.addV('person').property('name','marko').property('age',29).as_('marko')), (lambda g, xx1=None:g.mergeV(xx1).property('name','vadas','acl','public')), (lambda g, xx1=None:g.V().properties('name').hasValue('vadas').has('acl','public'))],
+ 'g_injectX0X_mergeVXlabel_person_name_stephenX': [(lambda g, xx1=None:g.addV('person').property('name','marko').property('age',29).as_('marko')), (lambda g, xx1=None:g.inject(0).mergeV(xx1)), (lambda g, xx1=None:g.V().has('person','name','stephen'))],
+ 'g_injectX0X_mergeVXlabel_person_name_markoX': [(lambda g, xx1=None:g.addV('person').property('name','marko').property('age',29).as_('marko')), (lambda g, xx1=None:g.inject(0).mergeV(xx1)), (lambda g, xx1=None:g.V().has('person','name','marko'))],
+ 'g_injectX0X_mergeVXlabel_person_name_stephenX_optionXonCreate_label_person_name_stephen_age_19X_option': [(lambda g, xx1=None,xx2=None:g.addV('person').property('name','marko').property('age',29).as_('marko')), (lambda g, xx1=None,xx2=None:g.inject(0).mergeV(xx1).option(Merge.onCreate,xx2)), (lambda g, xx1=None,xx2=None:g.V().has('person','name','stephen').has('age',19))],
+ 'g_injectX0X_mergeVXlabel_person_name_markoX_optionXonMatch_age_19X_option': [(lambda g, xx1=None,xx2=None:g.addV('person').property('name','marko').property('age',29).as_('marko')), (lambda g, xx1=None,xx2=None:g.inject(0).mergeV(xx1).option(Merge.onMatch,xx2)), (lambda g, xx1=None,xx2=None:g.V().has('person','name','marko').has('age',19))],
+ 'g_withSideEffectXc_label_person_name_stephenX_withSideEffectXm_label_person_name_stephen_age_19X_injectX0X_mergeVXselectXcXX_optionXonCreate_selectXmXX_option': [(lambda g, xx1=None,xx2=None:g.addV('person').property('name','marko').property('age',29).as_('marko')), (lambda g, xx1=None,xx2=None:g.withSideEffect('c',xx1).withSideEffect('m',xx2).inject(0).mergeV(__.select('c')).option(Merge.onCreate,__.select('m'))), (lambda g, xx1=None,xx2=None:g.V().has('person','name','stephen').ha [...]
+ 'g_withSideEffectXc_label_person_name_markoX_withSideEffectXm_age_19X_injectX0X_mergeVXselectXcXX_optionXonMatch_selectXmXX_option': [(lambda g, xx1=None,xx2=None:g.addV('person').property('name','marko').property('age',29).as_('marko')), (lambda g, xx1=None,xx2=None:g.withSideEffect('c',xx1).withSideEffect('m',xx2).inject(0).mergeV(__.select('c')).option(Merge.onMatch,__.select('m'))), (lambda g, xx1=None,xx2=None:g.V().has('person','name','marko').has('age',19))],
+ 'g_injectX0X_mergeVXlabel_person_name_markoX_propertyXname_vadas_acl_publicX': [(lambda g, xx1=None:g.addV('person').property('name','marko').property('age',29).as_('marko')), (lambda g, xx1=None:g.inject(0).mergeV(xx1).property('name','vadas','acl','public')), (lambda g, xx1=None:g.V().properties('name').hasValue('vadas').has('acl','public'))],
+ 'g_injectXlabel_person_name_marko_label_person_name_stephenX_mergeVXidentityX': [(lambda g, xx1=None,xx2=None:g.addV('person').property('name','marko').property('age',29).as_('marko')), (lambda g, xx1=None,xx2=None:g.inject(xx1,xx2).mergeV(__.identity())), (lambda g, xx1=None,xx2=None:g.V().has('person','name','stephen')), (lambda g, xx1=None,xx2=None:g.V().has('person','name','marko')), (lambda g, xx1=None,xx2=None:g.V())],
+ 'g_injectXlabel_person_name_marko_label_person_name_stephenX_mergeV': [(lambda g, xx1=None,xx2=None:g.addV('person').property('name','marko').property('age',29).as_('marko')), (lambda g, xx1=None,xx2=None:g.inject(xx1,xx2).mergeV()), (lambda g, xx1=None,xx2=None:g.V().has('person','name','stephen')), (lambda g, xx1=None,xx2=None:g.V().has('person','name','marko')), (lambda g, xx1=None,xx2=None:g.V())],
'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_())],
@@ -780,7 +806,6 @@ world.gremlins = {
'g_V_outXfollowedByX_group_byXsongTypeX_byXbothE_group_byXlabelX_byXweight_sumXX': [(lambda g:g.V().out('followedBy').group().by('songType').by(__.bothE().group().by(T.label).by(__.weight.sum_())))],
'g_V_groupXmX_byXnameX_byXinXknowsX_nameX_capXmX': [(lambda g:g.V().group('m').by('name').by(__.in_('knows').name).cap('m'))],
'g_V_group_byXlabelX_byXbothE_groupXaX_byXlabelX_byXweight_sumX_weight_sumX': [(lambda g:g.V().group().by(T.label).by(__.bothE().group('a').by(T.label).by(__.weight.sum_()).weight.sum_()))],
- 'g_withSideEffectXa__marko_666_noone_blahX_V_groupXaX_byXnameX_byXoutE_label_foldX_capXaX': [(lambda g, xx1=None:g.withSideEffect('a',xx1).V().group('a').by('name').by(__.outE().label().fold()).cap('a').unfold().group().by(Column.keys).by(__.select(Column.values).order(Scope.local).by(Order.asc)))],
'g_V_hasLabelXpersonX_asXpX_outXcreatedX_group_byXnameX_byXselectXpX_valuesXageX_sumX': [(lambda g:g.V().hasLabel('person').as_('p').out('created').group().by('name').by(__.select('p').age.sum_()))],
'g_V_hasLabelXpersonX_asXpX_outXcreatedX_groupXaX_byXnameX_byXselectXpX_valuesXageX_sumX_capXaX': [(lambda g:g.V().hasLabel('person').as_('p').out('created').group('a').by('name').by(__.select('p').age.sum_()).cap('a'))],
'g_V_group_byXlabelX_byXlabel_countX': [(lambda g:g.V().group().by(__.label()).by(__.label().count()))],
@@ -803,7 +828,7 @@ world.gremlins = {
'g_V_hasXperson_name_markoX_bothXknowsX_groupCount_byXvaluesXnameX_foldX': [(lambda g:g.V().has('person','name','marko').both('knows').groupCount().by(__.name.fold()))],
'g_VX1X_out_injectXv2X_name': [(lambda g, vid1=None,v2=None:g.V(vid1).out().inject(v2).name)],
'g_VX1X_out_name_injectXdanielX_asXaX_mapXlengthX_path': [(lambda g, l1=None,vid1=None:g.V(vid1).out().name.inject('daniel').as_('a').map(l1).path())],
- 'g_VX1X_injectXg_VX4XX_out_name': [(lambda g, vid1=None,v4=None:g.V(vid1).inject(v4).out().name)],
+ 'g_VX1X_injectXg_VX4XX_out_name': [(lambda g, vid1=None,v2=None:g.V(vid1).inject(v2).out().name)],
'g_injectXnull_1_3_nullX': [(lambda g:g.inject(None,1,3,None))],
'g_injectX10_20_null_20_10_10X_groupCountXxX_dedup_asXyX_projectXa_bX_by_byXselectXxX_selectXselectXyXXX': [(lambda g:g.inject(10,20,None,20,10,10).groupCount('x').dedup().as_('y').project('a','b').by().by(__.select('x').select(__.select('y'))))],
'g_injectXname_marko_age_nullX_selectXname_ageX': [(lambda g, xx1=None:g.inject(xx1).select('name','age'))],
diff --git a/gremlin-python/src/main/python/tests/driver/test_driver_remote_connection.py b/gremlin-python/src/main/python/tests/driver/test_driver_remote_connection.py
index c931bc9..14b9577 100644
--- a/gremlin-python/src/main/python/tests/driver/test_driver_remote_connection.py
+++ b/gremlin-python/src/main/python/tests/driver/test_driver_remote_connection.py
@@ -44,6 +44,7 @@ class TestDriverRemoteConnection(object):
assert long(6) == g.V().count().toList()[0]
# #
assert Vertex(1) == g.V(1).next()
+ assert Vertex(1) == g.V(Vertex(1)).next()
assert 1 == g.V(1).id_().next()
assert Traverser(Vertex(1)) == g.V(1).nextTraverser()
assert 1 == len(g.V(1).toList())
diff --git a/gremlin-python/src/main/python/tests/structure/io/test_graphbinaryV1.py b/gremlin-python/src/main/python/tests/structure/io/test_graphbinaryV1.py
index 2f2b211..b12c48a 100644
--- a/gremlin-python/src/main/python/tests/structure/io/test_graphbinaryV1.py
+++ b/gremlin-python/src/main/python/tests/structure/io/test_graphbinaryV1.py
@@ -24,7 +24,7 @@ import math
from gremlin_python.statics import timestamp, long, SingleByte, SingleChar, ByteBufferType
from gremlin_python.structure.graph import Vertex, Edge, Property, VertexProperty, Path
from gremlin_python.structure.io.graphbinaryV1 import GraphBinaryWriter, GraphBinaryReader
-from gremlin_python.process.traversal import Barrier, Binding, Bytecode
+from gremlin_python.process.traversal import Barrier, Binding, Bytecode, Merge
class TestGraphBinaryReader(object):
@@ -166,6 +166,11 @@ class TestGraphSONWriter(object):
output = self.graphbinary_reader.readObject(self.graphbinary_writer.writeObject(x))
assert x == output
+ def test_merge(self):
+ x = Merge.onMatch
+ output = self.graphbinary_reader.readObject(self.graphbinary_writer.writeObject(x))
+ assert x == output
+
def test_binding(self):
x = Binding("name", "marko")
output = self.graphbinary_reader.readObject(self.graphbinary_writer.writeObject(x))
diff --git a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/handler/WsGremlinBinaryRequestDecoder.java b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/handler/WsGremlinBinaryRequestDecoder.java
index b05ae92..3af49f4 100644
--- a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/handler/WsGremlinBinaryRequestDecoder.java
+++ b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/handler/WsGremlinBinaryRequestDecoder.java
@@ -76,6 +76,7 @@ public class WsGremlinBinaryRequestDecoder extends MessageToMessageDecoder<Binar
try {
objects.add(serializer.deserializeRequest(messageBytes.discardReadBytes()));
} catch (SerializationException se) {
+ logger.warn(se.getMessage());
objects.add(RequestMessage.INVALID);
}
} finally {
diff --git a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/handler/WsGremlinTextRequestDecoder.java b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/handler/WsGremlinTextRequestDecoder.java
index 50dc6f3..84b0888 100644
--- a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/handler/WsGremlinTextRequestDecoder.java
+++ b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/handler/WsGremlinTextRequestDecoder.java
@@ -63,6 +63,7 @@ public class WsGremlinTextRequestDecoder extends MessageToMessageDecoder<TextWeb
objects.add(serializer.deserializeRequest(frame.text()));
} catch (SerializationException se) {
+ logger.warn(se.getMessage());
objects.add(RequestMessage.INVALID);
}
}
diff --git a/gremlin-test/features/map/MergeEdge.feature b/gremlin-test/features/map/MergeEdge.feature
new file mode 100644
index 0000000..a04ca40
--- /dev/null
+++ b/gremlin-test/features/map/MergeEdge.feature
@@ -0,0 +1,201 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+@StepClassMap @StepMergeE
+Feature: Step - mergeE()
+
+ Scenario: g_V_mergeEXlabel_self_weight_05X
+ Given the empty graph
+ And the graph initializer of
+ """
+ g.addV("person").property("name", "marko").property("age", 29)
+ """
+ And using the parameter xx1 defined as "m[{\"t[label]\": \"self\", \"weight\":\"d[0.5].d\"}]"
+ And the traversal of
+ """
+ g.V().mergeE(xx1)
+ """
+ When iterated to list
+ Then the result should have a count of 1
+ And the graph should return 1 for count of "g.E().hasLabel(\"self\").has(\"weight\",0.5)"
+
+ @AllowUserSuppliedIds
+ Scenario: g_mergeEXlabel_knows_out_marko_in_vadasX
+ Given the empty graph
+ And the graph initializer of
+ """
+ g.addV("person").property(T.id, 100).property("name", "marko").
+ addV("person").property(T.id, 101).property("name", "vadas")
+ """
+ And using the parameter xx1 defined as "m[{\"t[label]\": \"knows\", \"D[OUT]\":\"v[100]\", \"D[IN]\":\"v[101]\"}]"
+ And the traversal of
+ """
+ g.mergeE(xx1)
+ """
+ When iterated to list
+ Then the result should have a count of 1
+ And the graph should return 1 for count of "g.V().has(\"person\",\"name\",\"marko\").out(\"knows\").has(\"person\",\"name\",\"vadas\")"
+
+ @AllowUserSuppliedIds
+ Scenario: g_mergeEXlabel_knows_out_marko_in_vadas_weight_05X_exists
+ Given the empty graph
+ And the graph initializer of
+ """
+ g.addV("person").property(T.id, 100).property("name", "marko").as("a").
+ addV("person").property(T.id, 101).property("name", "vadas").as("b").
+ addE("knows").from("a").to("b")
+ """
+ And using the parameter xx1 defined as "m[{\"t[label]\": \"knows\", \"D[OUT]\":\"v[100]\", \"D[IN]\":\"v[101]\", \"weight\":\"d[0.5].d\"}]"
+ And the traversal of
+ """
+ g.mergeE(xx1)
+ """
+ When iterated to list
+ Then the result should have a count of 1
+ And the graph should return 1 for count of "g.V().has(\"person\",\"name\",\"marko\").outE(\"knows\").has(\"weight\",0.5).inV().has(\"person\",\"name\",\"vadas\")"
+ And the graph should return 2 for count of "g.V().has(\"person\",\"name\",\"marko\").outE(\"knows\").inV().has(\"person\",\"name\",\"vadas\")"
+
+ @AllowUserSuppliedIds
+ Scenario: g_mergeEXlabel_knows_out_marko_in_vadas_weight_05X
+ Given the empty graph
+ And using the parameter xx1 defined as "m[{\"t[label]\": \"knows\", \"D[OUT]\":\"v[100]\", \"D[IN]\":\"v[101]\", \"weight\":\"d[0.5].d\"}]"
+ And the traversal of
+ """
+ g.mergeE(xx1)
+ """
+ When iterated to list
+ 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\").has(\"weight\",0.5)"
+
+ @AllowUserSuppliedIds
+ Scenario: g_mergeEXlabel_knows_out_marko_in_vadasX_optionXonCreate_created_YX_optionXonMatch_created_NX
+ Given the empty graph
+ And using the parameter xx1 defined as "m[{\"t[label]\": \"knows\", \"D[OUT]\":\"v[100]\", \"D[IN]\":\"v[101]\"}]"
+ And using the parameter xx2 defined as "m[{\"t[label]\": \"knows\", \"D[OUT]\":\"v[100]\", \"D[IN]\":\"v[101]\", \"created\":\"Y\"}]"
+ And using the parameter xx3 defined as "m[{\"t[label]\": \"knows\", \"created\":\"N\"}]"
+ And the traversal of
+ """
+ g.mergeE(xx1).option(Merge.onCreate,xx2).option(Merge.onMatch,xx3)
+ """
+ When iterated to list
+ 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\").has(\"created\",\"Y\")"
+ And the graph should return 0 for count of "g.E().hasLabel(\"knows\").has(\"created\",\"N\")"
+
+ @AllowUserSuppliedIds
+ Scenario: g_mergeEXlabel_knows_out_marko_in_vadasX_optionXonCreate_created_YX_optionXonMatch_created_NX_exists
+ Given the empty graph
+ And the graph initializer of
+ """
+ g.addV("person").property(T.id, 100).property("name", "marko").as("a").
+ addV("person").property(T.id, 101).property("name", "vadas").as("b").
+ addE("knows").from("a").to("b")
+ """
+ And using the parameter xx1 defined as "m[{\"t[label]\": \"knows\", \"D[OUT]\":\"v[100]\", \"D[IN]\":\"v[101]\"}]"
+ And using the parameter xx2 defined as "m[{\"t[label]\": \"knows\", \"D[OUT]\":\"v[100]\", \"D[IN]\":\"v[101]\",\"created\":\"Y\"}]"
+ And using the parameter xx3 defined as "m[{\"created\":\"N\"}]"
+ And the traversal of
+ """
+ g.mergeE(xx1).option(Merge.onCreate,xx2).option(Merge.onMatch,xx3)
+ """
+ When iterated to list
+ 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 0 for count of "g.E().hasLabel(\"knows\").has(\"created\",\"Y\")"
+ And the graph should return 1 for count of "g.E().hasLabel(\"knows\").has(\"created\",\"N\")"
+
+ @AllowUserSuppliedIds
+ Scenario: g_mergeEXlabel_knows_out_marko_in_vadasX_optionXonCreate_created_YX_optionXonMatch_created_NX_exists_updated
+ Given the empty graph
+ And the graph initializer of
+ """
+ g.addV("person").property(T.id, 100).property("name", "marko").as("a").
+ addV("person").property(T.id, 101).property("name", "vadas").as("b").
+ addE("knows").from("a").to("b").property("created","Y")
+ """
+ And using the parameter xx1 defined as "m[{\"t[label]\": \"knows\", \"D[OUT]\":\"v[100]\", \"D[IN]\":\"v[101]\"}]"
+ And using the parameter xx2 defined as "m[{\"t[label]\": \"knows\", \"D[OUT]\":\"v[100]\", \"D[IN]\":\"v[101]\",\"created\":\"Y\"}]"
+ And using the parameter xx3 defined as "m[{\"created\":\"N\"}]"
+ And the traversal of
+ """
+ g.mergeE(xx1).option(Merge.onCreate,xx2).option(Merge.onMatch,xx3)
+ """
+ When iterated to list
+ 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 0 for count of "g.E().hasLabel(\"knows\").has(\"created\",\"Y\")"
+ And the graph should return 1 for count of "g.E().hasLabel(\"knows\").has(\"created\",\"N\")"
+
+ @AllowUserSuppliedIds
+ Scenario: g_V_hasXperson_name_marko_X_mergeEXlabel_knowsX_optionXonCreate_created_YX_optionXonMatch_created_NX_exists_updated
+ Given the empty graph
+ And the graph initializer of
+ """
+ g.addV("person").property(T.id, 100).property("name", "marko").as("a").
+ addV("person").property(T.id, 101).property("name", "vadas").as("b").
+ addE("knows").from("a").to("b").property("created","Y").
+ addE("knows").from("a").to("b")
+ """
+ And using the parameter xx1 defined as "m[{\"t[label]\": \"knows\"}]"
+ And using the parameter xx2 defined as "m[{\"t[label]\": \"knows\", \"D[OUT]\":\"v[100]\", \"D[IN]\":\"v[101]\",\"created\":\"Y\"}]"
+ And using the parameter xx3 defined as "m[{\"created\":\"N\"}]"
+ And the traversal of
+ """
+ g.V().has("person","name","marko").mergeE(xx1).option(Merge.onCreate,xx2).option(Merge.onMatch,xx3)
+ """
+ When iterated to list
+ Then the result should have a count of 2
+ And the graph should return 2 for count of "g.V()"
+ And the graph should return 2 for count of "g.E()"
+ And the graph should return 0 for count of "g.E().hasLabel(\"knows\").has(\"created\",\"Y\")"
+ And the graph should return 2 for count of "g.E().hasLabel(\"knows\").has(\"created\",\"N\")"
+
+ @AllowUserSuppliedIds
+ Scenario: g_VX100X_VX101X_mergeEXlabel_knows_out_marko_in_vadasX
+ Given the empty graph
+ And the graph initializer of
+ """
+ g.addV("person").property(T.id, 100).property("name", "marko")
+ """
+ And using the parameter xx1 defined as "m[{\"t[label]\": \"knows\", \"D[OUT]\":\"v[100]\", \"D[IN]\":\"v[101]\"}]"
+ And the traversal of
+ """
+ g.V(100).V(101).mergeE(xx1)
+ """
+ When iterated to list
+ Then the result should have a count of 0
+ And the graph should return 1 for count of "g.V()"
+ And the graph should return 0 for count of "g.E()"
+
+ @AllowUserSuppliedIds
+ Scenario: g_injectXlabel_knows_out_marko_in_vadasX_mergeE
+ Given the empty graph
+ And the graph initializer of
+ """
+ g.addV("person").property(T.id, 100).property("name", "marko").
+ addV("person").property(T.id, 101).property("name", "vadas")
+ """
+ And using the parameter xx1 defined as "m[{\"t[label]\": \"knows\", \"D[OUT]\":\"v[100]\", \"D[IN]\":\"v[101]\"}]"
+ And the traversal of
+ """
+ g.inject(xx1).mergeE()
+ """
+ When iterated to list
+ Then the result should have a count of 1
+ And the graph should return 1 for count of "g.V().has(\"person\",\"name\",\"marko\").out(\"knows\").has(\"person\",\"name\",\"vadas\")"
\ No newline at end of file
diff --git a/gremlin-test/features/map/MergeVertex.feature b/gremlin-test/features/map/MergeVertex.feature
new file mode 100644
index 0000000..cda2e42
--- /dev/null
+++ b/gremlin-test/features/map/MergeVertex.feature
@@ -0,0 +1,283 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+@StepClassMap @StepMergeV
+Feature: Step - mergeV()
+
+ Scenario: g_mergeVXlabel_person_name_stephenX
+ Given the empty graph
+ And the graph initializer of
+ """
+ g.addV("person").property("name", "marko").property("age", 29).as("marko")
+ """
+ And using the parameter xx1 defined as "m[{\"t[label]\": \"person\", \"name\":\"stephen\"}]"
+ And the traversal of
+ """
+ g.mergeV(xx1)
+ """
+ When iterated to list
+ Then the result should have a count of 1
+ And the graph should return 1 for count of "g.V().has(\"person\",\"name\",\"stephen\")"
+
+ Scenario: g_mergeVXlabel_person_name_markoX
+ Given the empty graph
+ And the graph initializer of
+ """
+ g.addV("person").property("name", "marko").property("age", 29).as("marko")
+ """
+ And using the parameter xx1 defined as "m[{\"t[label]\": \"person\", \"name\":\"marko\"}]"
+ And the traversal of
+ """
+ g.mergeV(xx1)
+ """
+ When iterated to list
+ Then the result should have a count of 1
+ And the graph should return 1 for count of "g.V().has(\"person\",\"name\",\"marko\")"
+
+ Scenario: g_mergeVXlabel_person_name_stephenX_optionXonCreate_label_person_name_stephen_age_19X_option
+ Given the empty graph
+ And the graph initializer of
+ """
+ g.addV("person").property("name", "marko").property("age", 29).as("marko")
+ """
+ And using the parameter xx1 defined as "m[{\"t[label]\": \"person\", \"name\":\"stephen\"}]"
+ And using the parameter xx2 defined as "m[{\"t[label]\": \"person\", \"name\":\"stephen\", \"age\": \"d[19].i\"}]"
+ And the traversal of
+ """
+ g.mergeV(xx1).option(Merge.onCreate,xx2)
+ """
+ When iterated to list
+ Then the result should have a count of 1
+ And the graph should return 1 for count of "g.V().has(\"person\",\"name\",\"stephen\").has(\"age\", 19)"
+
+ Scenario: g_mergeVXlabel_person_name_markoX_optionXonMatch_age_19X_option
+ Given the empty graph
+ And the graph initializer of
+ """
+ g.addV("person").property("name", "marko").property("age", 29).as("marko")
+ """
+ And using the parameter xx1 defined as "m[{\"t[label]\": \"person\", \"name\":\"marko\"}]"
+ And using the parameter xx2 defined as "m[{\"age\": \"d[19].i\"}]"
+ And the traversal of
+ """
+ g.mergeV(xx1).option(Merge.onMatch,xx2)
+ """
+ When iterated to list
+ Then the result should have a count of 1
+ And the graph should return 1 for count of "g.V().has(\"person\",\"name\",\"marko\").has(\"age\", 19)"
+
+ Scenario: g_withSideEffectXc_label_person_name_stephenX_withSideEffectXm_label_person_name_stephen_age_19X_mergeVXselectXcXX_optionXonCreate_selectXmXX_option
+ Given the empty graph
+ And the graph initializer of
+ """
+ g.addV("person").property("name", "marko").property("age", 29).as("marko")
+ """
+ And using the parameter xx1 defined as "m[{\"t[label]\": \"person\", \"name\":\"stephen\"}]"
+ And using the parameter xx2 defined as "m[{\"t[label]\": \"person\", \"name\":\"stephen\", \"age\": \"d[19].i\"}]"
+ And the traversal of
+ """
+ g.withSideEffect("c", xx1).
+ withSideEffect("m", xx2).
+ mergeV(__.select("c")).option(Merge.onCreate, __.select("m"))
+ """
+ When iterated to list
+ Then the result should have a count of 1
+ And the graph should return 1 for count of "g.V().has(\"person\",\"name\",\"stephen\").has(\"age\", 19)"
+
+ Scenario: g_withSideEffectXc_label_person_name_markoX_withSideEffectXm_age_19X_mergeVXselectXcXX_optionXonMatch_selectXmXX_option
+ Given the empty graph
+ And the graph initializer of
+ """
+ g.addV("person").property("name", "marko").property("age", 29).as("marko")
+ """
+ And using the parameter xx1 defined as "m[{\"t[label]\": \"person\", \"name\":\"marko\"}]"
+ And using the parameter xx2 defined as "m[{\"age\": \"d[19].i\"}]"
+ And the traversal of
+ """
+ g.withSideEffect("c", xx1).
+ withSideEffect("m", xx2).
+ mergeV(__.select("c")).option(Merge.onMatch, __.select("m"))
+ """
+ When iterated to list
+ Then the result should have a count of 1
+ And the graph should return 1 for count of "g.V().has(\"person\",\"name\",\"marko\").has(\"age\", 19)"
+
+ @MultiMetaProperties
+ Scenario: g_mergeVXlabel_person_name_markoX_propertyXname_vadas_acl_publicX
+ Given the empty graph
+ And the graph initializer of
+ """
+ g.addV("person").property("name", "marko").property("age", 29).as("marko")
+ """
+ And using the parameter xx1 defined as "m[{\"t[label]\": \"person\", \"name\":\"marko\"}]"
+ And the traversal of
+ """
+ g.mergeV(xx1).property("name","vadas","acl","public")
+ """
+ When iterated to list
+ Then the result should have a count of 1
+ And the graph should return 1 for count of "g.V().properties(\"name\").hasValue(\"vadas\").has(\"acl\",\"public\")"
+
+ Scenario: g_injectX0X_mergeVXlabel_person_name_stephenX
+ Given the empty graph
+ And the graph initializer of
+ """
+ g.addV("person").property("name", "marko").property("age", 29).as("marko")
+ """
+ And using the parameter xx1 defined as "m[{\"t[label]\": \"person\", \"name\":\"stephen\"}]"
+ And the traversal of
+ """
+ g.inject(0).mergeV(xx1)
+ """
+ When iterated to list
+ Then the result should have a count of 1
+ And the graph should return 1 for count of "g.V().has(\"person\",\"name\",\"stephen\")"
+
+ Scenario: g_injectX0X_mergeVXlabel_person_name_markoX
+ Given the empty graph
+ And the graph initializer of
+ """
+ g.addV("person").property("name", "marko").property("age", 29).as("marko")
+ """
+ And using the parameter xx1 defined as "m[{\"t[label]\": \"person\", \"name\":\"marko\"}]"
+ And the traversal of
+ """
+ g.inject(0).mergeV(xx1)
+ """
+ When iterated to list
+ Then the result should have a count of 1
+ And the graph should return 1 for count of "g.V().has(\"person\",\"name\",\"marko\")"
+
+ Scenario: g_injectX0X_mergeVXlabel_person_name_stephenX_optionXonCreate_label_person_name_stephen_age_19X_option
+ Given the empty graph
+ And the graph initializer of
+ """
+ g.addV("person").property("name", "marko").property("age", 29).as("marko")
+ """
+ And using the parameter xx1 defined as "m[{\"t[label]\": \"person\", \"name\":\"stephen\"}]"
+ And using the parameter xx2 defined as "m[{\"t[label]\": \"person\", \"name\":\"stephen\", \"age\": \"d[19].i\"}]"
+ And the traversal of
+ """
+ g.inject(0).mergeV(xx1).option(Merge.onCreate,xx2)
+ """
+ When iterated to list
+ Then the result should have a count of 1
+ And the graph should return 1 for count of "g.V().has(\"person\",\"name\",\"stephen\").has(\"age\", 19)"
+
+ Scenario: g_injectX0X_mergeVXlabel_person_name_markoX_optionXonMatch_age_19X_option
+ Given the empty graph
+ And the graph initializer of
+ """
+ g.addV("person").property("name", "marko").property("age", 29).as("marko")
+ """
+ And using the parameter xx1 defined as "m[{\"t[label]\": \"person\", \"name\":\"marko\"}]"
+ And using the parameter xx2 defined as "m[{\"age\": \"d[19].i\"}]"
+ And the traversal of
+ """
+ g.inject(0).mergeV(xx1).option(Merge.onMatch,xx2)
+ """
+ When iterated to list
+ Then the result should have a count of 1
+ And the graph should return 1 for count of "g.V().has(\"person\",\"name\",\"marko\").has(\"age\", 19)"
+
+ Scenario: g_withSideEffectXc_label_person_name_stephenX_withSideEffectXm_label_person_name_stephen_age_19X_injectX0X_mergeVXselectXcXX_optionXonCreate_selectXmXX_option
+ Given the empty graph
+ And the graph initializer of
+ """
+ g.addV("person").property("name", "marko").property("age", 29).as("marko")
+ """
+ And using the parameter xx1 defined as "m[{\"t[label]\": \"person\", \"name\":\"stephen\"}]"
+ And using the parameter xx2 defined as "m[{\"t[label]\": \"person\", \"name\":\"stephen\", \"age\": \"d[19].i\"}]"
+ And the traversal of
+ """
+ g.withSideEffect("c", xx1).
+ withSideEffect("m", xx2).
+ inject(0).mergeV(__.select("c")).option(Merge.onCreate, __.select("m"))
+ """
+ When iterated to list
+ Then the result should have a count of 1
+ And the graph should return 1 for count of "g.V().has(\"person\",\"name\",\"stephen\").has(\"age\", 19)"
+
+ Scenario: g_withSideEffectXc_label_person_name_markoX_withSideEffectXm_age_19X_injectX0X_mergeVXselectXcXX_optionXonMatch_selectXmXX_option
+ Given the empty graph
+ And the graph initializer of
+ """
+ g.addV("person").property("name", "marko").property("age", 29).as("marko")
+ """
+ And using the parameter xx1 defined as "m[{\"t[label]\": \"person\", \"name\":\"marko\"}]"
+ And using the parameter xx2 defined as "m[{\"age\": \"d[19].i\"}]"
+ And the traversal of
+ """
+ g.withSideEffect("c", xx1).
+ withSideEffect("m", xx2).
+ inject(0).mergeV(__.select("c")).option(Merge.onMatch, __.select("m"))
+ """
+ When iterated to list
+ Then the result should have a count of 1
+ And the graph should return 1 for count of "g.V().has(\"person\",\"name\",\"marko\").has(\"age\", 19)"
+
+ @MultiMetaProperties
+ Scenario: g_injectX0X_mergeVXlabel_person_name_markoX_propertyXname_vadas_acl_publicX
+ Given the empty graph
+ And the graph initializer of
+ """
+ g.addV("person").property("name", "marko").property("age", 29).as("marko")
+ """
+ And using the parameter xx1 defined as "m[{\"t[label]\": \"person\", \"name\":\"marko\"}]"
+ And the traversal of
+ """
+ g.inject(0).mergeV(xx1).property("name","vadas","acl","public")
+ """
+ When iterated to list
+ Then the result should have a count of 1
+ And the graph should return 1 for count of "g.V().properties(\"name\").hasValue(\"vadas\").has(\"acl\",\"public\")"
+
+ Scenario: g_injectXlabel_person_name_marko_label_person_name_stephenX_mergeVXidentityX
+ Given the empty graph
+ And the graph initializer of
+ """
+ g.addV("person").property("name", "marko").property("age", 29).as("marko")
+ """
+ And using the parameter xx1 defined as "m[{\"t[label]\": \"person\", \"name\":\"marko\"}]"
+ And using the parameter xx2 defined as "m[{\"t[label]\": \"person\", \"name\":\"stephen\"}]"
+ And the traversal of
+ """
+ g.inject(xx1, xx2).mergeV(__.identity())
+ """
+ When iterated to list
+ Then the result should have a count of 2
+ And the graph should return 1 for count of "g.V().has(\"person\",\"name\",\"stephen\")"
+ And the graph should return 1 for count of "g.V().has(\"person\",\"name\",\"marko\")"
+ And the graph should return 2 for count of "g.V()"
+
+ Scenario: g_injectXlabel_person_name_marko_label_person_name_stephenX_mergeV
+ Given the empty graph
+ And the graph initializer of
+ """
+ g.addV("person").property("name", "marko").property("age", 29).as("marko")
+ """
+ And using the parameter xx1 defined as "m[{\"t[label]\": \"person\", \"name\":\"marko\"}]"
+ And using the parameter xx2 defined as "m[{\"t[label]\": \"person\", \"name\":\"stephen\"}]"
+ And the traversal of
+ """
+ g.inject(xx1, xx2).mergeV()
+ """
+ When iterated to list
+ Then the result should have a count of 2
+ And the graph should return 1 for count of "g.V().has(\"person\",\"name\",\"stephen\")"
+ And the graph should return 1 for count of "g.V().has(\"person\",\"name\",\"marko\")"
+ And the graph should return 2 for count of "g.V()"
\ No newline at end of file
diff --git a/gremlin-test/features/sideEffect/Group.feature b/gremlin-test/features/sideEffect/Group.feature
index d4d3867..01a5a21 100644
--- a/gremlin-test/features/sideEffect/Group.feature
+++ b/gremlin-test/features/sideEffect/Group.feature
@@ -264,17 +264,17 @@ Feature: Step - group()
| m[{"software":"d[2.0].d", "person":"d[5.0].d"}] |
# The post-ordering really isn't really right but works around TINKERPOP-2600
- Scenario: g_withSideEffectXa__marko_666_noone_blahX_V_groupXaX_byXnameX_byXoutE_label_foldX_capXaX
- Given the modern graph
- And using the parameter xx1 defined as "m[{\"marko\":[\"666\"], \"noone\":[\"blah\"]}]"
- And the traversal of
- """
- g.withSideEffect("a", xx1).V().group("a").by("name").by(__.outE().label().fold()).cap("a").unfold().group().by(keys).by(select(values).order(Scope.local).by(Order.asc))
- """
- When iterated to list
- Then the result should be unordered
- | result |
- | m[{"ripple":[], "peter":["created"], "noone":["blah"], "vadas":[], "josh":["created", "created"], "lop":[], "marko":["666", "created", "knows", "knows"]}] |
+# Scenario: g_withSideEffectXa__marko_666_noone_blahX_V_groupXaX_byXnameX_byXoutE_label_foldX_capXaX
+# Given the modern graph
+# And using the parameter xx1 defined as "m[{\"marko\":\"l[\"666\"]\", \"noone\":\"l[\"blah\"]\"}]"
+# And the traversal of
+# """
+# g.withSideEffect("a", xx1).V().group("a").by("name").by(__.outE().label().fold()).cap("a").unfold().group().by(Column.keys).by(select(Column.values).order(Scope.local).by(Order.asc))
+# """
+# When iterated to list
+# Then the result should be unordered
+# | result |
+# | m[{"ripple":[], "peter":["created"], "noone":["blah"], "vadas":[], "josh":["created", "created"], "lop":[], "marko":["666", "created", "knows", "knows"]}] |
@GraphComputerVerificationStarGraphExceeded
Scenario: g_V_hasLabelXpersonX_asXpX_outXcreatedX_group_byXnameX_byXselectXpX_valuesXageX_sumX
diff --git a/gremlin-test/features/sideEffect/Inject.feature b/gremlin-test/features/sideEffect/Inject.feature
index a186095..75edc15 100644
--- a/gremlin-test/features/sideEffect/Inject.feature
+++ b/gremlin-test/features/sideEffect/Inject.feature
@@ -51,13 +51,14 @@ Feature: Step - inject()
| p[v[marko],v[vadas],vadas,d[5].i] |
| p[v[marko],v[josh],josh,d[4].i] |
+ @GraphComputerVerificationInjectionNotSupported
Scenario: g_VX1X_injectXg_VX4XX_out_name
Given the modern graph
And using the parameter vid1 defined as "v[marko].id"
- And using the parameter v4 defined as "v[josh]"
+ And using the parameter v2 defined as "v[josh]"
And the traversal of
"""
- g.V(vid1).inject(v4).out().values("name")
+ g.V(vid1).inject(v2).out().values("name")
"""
When iterated to list
Then the result should be unordered
@@ -130,7 +131,6 @@ Feature: Step - inject()
| result |
| null |
-
Scenario: g_inject
Given the empty graph
And the traversal of
@@ -140,7 +140,6 @@ Feature: Step - inject()
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"
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 b8021c1..d896228 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
@@ -38,6 +38,7 @@ import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSo
import org.apache.tinkerpop.gremlin.structure.Direction;
import org.apache.tinkerpop.gremlin.structure.Edge;
import org.apache.tinkerpop.gremlin.structure.T;
+import org.apache.tinkerpop.gremlin.structure.Vertex;
import org.apache.tinkerpop.gremlin.structure.VertexProperty;
import org.apache.tinkerpop.gremlin.util.iterator.IteratorUtils;
import org.apache.tinkerpop.shaded.jackson.databind.JsonNode;
@@ -64,10 +65,12 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
+import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.Optional;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -88,10 +91,21 @@ public final class StepDefinition {
private static final Pattern edgeTripletPattern = Pattern.compile("(.+)-(.+)->(.+)");
private static final Pattern ioPattern = Pattern.compile("g\\.io\\(\"(.*)\"\\).*");
private List<Pair<Pattern, Function<String,String>>> stringMatcherConverters = new ArrayList<Pair<Pattern, Function<String,String>>>() {{
- // expects json so that should port to the Gremlin script form - replace curly json braces with square ones
- // for Gremlin sake.
- add(Pair.with(Pattern.compile("m\\[(.*)\\]"), s -> s.replace('{','[').replace('}', ']')));
-
+ // expects json so that should port to the Gremlin script form
+ add(Pair.with(Pattern.compile("m\\[(.*)\\]"), s -> {
+ // can't handled embedded maps because of the string replace below on the curly braces
+ final String[] items = s.replace("{", "").replace("}", "").split(",");
+ final String listItems = Stream.of(items).map(String::trim).map(x -> {
+ final String[] pair = x.split(":");
+
+ // if wrapping double quotes aren't removed they end up re-wrapping again for pure string values
+ // on conversion
+ final String convertedKey = convertToString(pair[0].trim().replace("\"", ""));
+ final String convertedValue = convertToString(pair[1].trim().replace("\"", ""));
+ return String.format("%s:%s", convertedKey, convertedValue);
+ }).collect(Collectors.joining(","));
+ return String.format("[%s]", listItems);
+ }));
add(Pair.with(Pattern.compile("l\\[\\]"), s -> "[]"));
add(Pair.with(Pattern.compile("l\\[(.*)\\]"), s -> {
final String[] items = s.split(",");
@@ -109,9 +123,12 @@ public final class StepDefinition {
add(Pair.with(Pattern.compile("v\\[(.+)\\]\\.id"), s -> g.V().has("name", s).id().next().toString()));
add(Pair.with(Pattern.compile("v\\[(.+)\\]\\.sid"), s -> g.V().has("name", s).id().next().toString()));
+ add(Pair.with(Pattern.compile("v\\[(.+)\\]"), s -> {
+ final Iterator<Object> itty = g.V().has("name", s).id();
+ return String.format("new Vertex(%s,\"%s\")", itty.hasNext() ? itty.next() : s, Vertex.DEFAULT_LABEL);
+ }));
add(Pair.with(Pattern.compile("e\\[(.+)\\]\\.id"), s -> getEdgeIdString(g, s)));
add(Pair.with(Pattern.compile("e\\[(.+)\\]\\.sid"), s -> getEdgeIdString(g, s)));
-
add(Pair.with(Pattern.compile("t\\[(.*)\\]"), s -> String.format("T.%s", s)));
add(Pair.with(Pattern.compile("D\\[(.*)\\]"), s -> String.format("Direction.%s", s)));
@@ -121,9 +138,6 @@ public final class StepDefinition {
add(Pair.with(Pattern.compile("c\\[(.*)\\]"), s -> {
throw new AssumptionViolatedException("This test uses a lambda as a parameter which is not supported by gremlin-language");
}));
- add(Pair.with(Pattern.compile("v\\[(.+)\\]"), s -> {
- throw new AssumptionViolatedException("This test uses a Vertex as a parameter which is not supported by gremlin-language");
- }));
add(Pair.with(Pattern.compile("e\\[(.+)\\]"), s -> {
throw new AssumptionViolatedException("This test uses a Edge as a parameter which is not supported by gremlin-language");
}));
diff --git a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/ProcessLimitedStandardSuite.java b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/ProcessLimitedStandardSuite.java
index e9c4602..a785724 100644
--- a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/ProcessLimitedStandardSuite.java
+++ b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/ProcessLimitedStandardSuite.java
@@ -24,64 +24,11 @@ import org.apache.tinkerpop.gremlin.process.traversal.TraversalEngine;
import org.apache.tinkerpop.gremlin.process.traversal.TraversalInterruptionTest;
import org.apache.tinkerpop.gremlin.process.traversal.step.ComplexTest;
import org.apache.tinkerpop.gremlin.process.traversal.step.OrderabilityTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.branch.BranchTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.branch.ChooseTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.branch.LocalTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.branch.OptionalTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.branch.RepeatTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.branch.UnionTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.filter.AndTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.filter.CoinTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.filter.CyclicPathTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.filter.DedupTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.filter.DropTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.filter.FilterTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.filter.HasTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.filter.IsTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.filter.OrTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.filter.RangeTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.filter.SampleTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.filter.SimplePathTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.filter.TailTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.filter.WhereTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.map.AddEdgeTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.map.AddVertexTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.map.CoalesceTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.map.ConstantTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.map.CountTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.map.ElementMapTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.map.FlatMapTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.map.FoldTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.map.GraphTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.map.IndexTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.map.LoopsTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.map.MapTest;
import org.apache.tinkerpop.gremlin.process.traversal.step.map.MatchTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.map.MathTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.map.MaxTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.map.MeanTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.map.MinTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.map.OrderTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.map.PathTest;
import org.apache.tinkerpop.gremlin.process.traversal.step.map.ProfileTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.map.ProjectTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.map.PropertiesTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.map.ReadTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.map.SelectTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.map.SumTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.map.UnfoldTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.map.ValueMapTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.map.VertexTest;
import org.apache.tinkerpop.gremlin.process.traversal.step.map.WriteTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.sideEffect.AggregateTest;
import org.apache.tinkerpop.gremlin.process.traversal.step.sideEffect.ExplainTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.sideEffect.GroupCountTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.sideEffect.GroupTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.sideEffect.InjectTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.sideEffect.SackTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.sideEffect.SideEffectCapTest;
import org.apache.tinkerpop.gremlin.process.traversal.step.sideEffect.SideEffectTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.sideEffect.StoreTest;
import org.apache.tinkerpop.gremlin.process.traversal.step.sideEffect.SubgraphTest;
import org.apache.tinkerpop.gremlin.process.traversal.step.sideEffect.TreeTest;
import org.apache.tinkerpop.gremlin.process.traversal.strategy.decoration.ElementIdStrategyProcessTest;
@@ -93,8 +40,6 @@ import org.apache.tinkerpop.gremlin.process.traversal.strategy.decoration.Transl
import org.apache.tinkerpop.gremlin.process.traversal.strategy.optimization.EarlyLimitStrategyProcessTest;
import org.apache.tinkerpop.gremlin.process.traversal.strategy.optimization.IncidentToAdjacentStrategyProcessTest;
import org.apache.tinkerpop.gremlin.process.traversal.strategy.verification.ReadOnlyStrategyProcessTest;
-import org.apache.tinkerpop.gremlin.structure.Graph;
-import org.apache.tinkerpop.gremlin.structure.StructureStandardSuite;
import org.junit.runners.model.InitializationError;
import org.junit.runners.model.RunnerBuilder;
diff --git a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/branch/BranchTest.java b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/branch/BranchTest.java
index ed4b2e7..642cda4 100644
--- a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/branch/BranchTest.java
+++ b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/branch/BranchTest.java
@@ -40,8 +40,8 @@ import static org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__.identi
import static org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__.in;
import static org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__.label;
import static org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__.values;
-import static org.apache.tinkerpop.gremlin.process.traversal.step.TraversalOptionParent.Pick.any;
-import static org.apache.tinkerpop.gremlin.process.traversal.step.TraversalOptionParent.Pick.none;
+import static org.apache.tinkerpop.gremlin.process.traversal.Pick.any;
+import static org.apache.tinkerpop.gremlin.process.traversal.Pick.none;
/**
* @author Marko A. Rodriguez (http://markorodriguez.com)
diff --git a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/branch/ChooseTest.java b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/branch/ChooseTest.java
index 28f3abe..d1f4933 100644
--- a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/branch/ChooseTest.java
+++ b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/branch/ChooseTest.java
@@ -22,9 +22,9 @@ import org.apache.tinkerpop.gremlin.LoadGraphWith;
import org.apache.tinkerpop.gremlin.process.AbstractGremlinProcessTest;
import org.apache.tinkerpop.gremlin.process.GremlinProcessRunner;
import org.apache.tinkerpop.gremlin.process.traversal.P;
+import org.apache.tinkerpop.gremlin.process.traversal.Pick;
import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__;
-import org.apache.tinkerpop.gremlin.process.traversal.step.TraversalOptionParent;
import org.apache.tinkerpop.gremlin.process.traversal.step.util.MapHelper;
import org.apache.tinkerpop.gremlin.structure.Vertex;
import org.junit.Test;
@@ -195,7 +195,7 @@ public abstract class ChooseTest extends AbstractGremlinProcessTest {
return g.V().choose(label())
.option("blah", out("knows"))
.option("bleep", out("created"))
- .option(TraversalOptionParent.Pick.none, identity()).values("name");
+ .option(Pick.none, identity()).values("name");
}
@Override
@@ -212,7 +212,7 @@ public abstract class ChooseTest extends AbstractGremlinProcessTest {
public Traversal<Vertex, Map<String, Long>> get_g_V_hasLabelXpersonX_chooseXageX__optionX27L__constantXyoungXX_optionXnone__constantXoldXX_groupCount() {
return g.V().hasLabel("person").choose(values("age"))
.option(27L, __.constant("young"))
- .option(TraversalOptionParent.Pick.none, __.constant("old"))
+ .option(Pick.none, __.constant("old"))
.groupCount();
}
diff --git a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/strategy/decoration/EventStrategyProcessTest.java b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/strategy/decoration/EventStrategyProcessTest.java
index 5aa9823..e6c6ebc 100644
--- a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/strategy/decoration/EventStrategyProcessTest.java
+++ b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/strategy/decoration/EventStrategyProcessTest.java
@@ -21,11 +21,13 @@ package org.apache.tinkerpop.gremlin.process.traversal.strategy.decoration;
import org.apache.tinkerpop.gremlin.FeatureRequirement;
import org.apache.tinkerpop.gremlin.FeatureRequirementSet;
import org.apache.tinkerpop.gremlin.process.AbstractGremlinProcessTest;
+import org.apache.tinkerpop.gremlin.process.traversal.Merge;
import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource;
import org.apache.tinkerpop.gremlin.process.traversal.step.util.event.MutationListener;
import org.apache.tinkerpop.gremlin.structure.Edge;
import org.apache.tinkerpop.gremlin.structure.Graph;
import org.apache.tinkerpop.gremlin.structure.Property;
+import org.apache.tinkerpop.gremlin.structure.T;
import org.apache.tinkerpop.gremlin.structure.Vertex;
import org.apache.tinkerpop.gremlin.structure.VertexProperty;
import org.apache.tinkerpop.gremlin.structure.util.detached.DetachedEdge;
@@ -40,7 +42,9 @@ import org.apache.tinkerpop.gremlin.util.iterator.IteratorUtils;
import org.junit.Test;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
@@ -83,6 +87,31 @@ public class EventStrategyProcessTest extends AbstractGremlinProcessTest {
@Test
@FeatureRequirementSet(FeatureRequirementSet.Package.VERTICES_ONLY)
+ public void shouldTriggerAddVertexViaMergeV() {
+ final StubMutationListener listener1 = new StubMutationListener();
+ final StubMutationListener listener2 = new StubMutationListener();
+ final EventStrategy.Builder builder = EventStrategy.build()
+ .addListener(listener1)
+ .addListener(listener2);
+
+ if (graph.features().graph().supportsTransactions())
+ builder.eventQueue(new EventStrategy.TransactionalEventQueue(graph));
+
+ final EventStrategy eventStrategy = builder.create();
+
+ graph.addVertex("some", "thing");
+ final GraphTraversalSource gts = create(eventStrategy);
+ final Map<Object,Object> m = new HashMap<>();
+ m.put("any", "thing");
+ gts.V().mergeV(m).property("any", "thing").next();
+
+ tryCommit(graph, g -> assertEquals(1, IteratorUtils.count(gts.V().has("any", "thing"))));
+ assertEquals(1, listener1.addVertexEventRecorded());
+ assertEquals(1, listener2.addVertexEventRecorded());
+ }
+
+ @Test
+ @FeatureRequirementSet(FeatureRequirementSet.Package.VERTICES_ONLY)
public void shouldTriggerAddVertexFromStart() {
final StubMutationListener listener1 = new StubMutationListener();
final StubMutationListener listener2 = new StubMutationListener();
@@ -135,6 +164,37 @@ public class EventStrategyProcessTest extends AbstractGremlinProcessTest {
@Test
@FeatureRequirementSet(FeatureRequirementSet.Package.SIMPLE)
+ public void shouldTriggerAddEdgeViaMergeE() {
+ final StubMutationListener listener1 = new StubMutationListener();
+ final StubMutationListener listener2 = new StubMutationListener();
+ final EventStrategy.Builder builder = EventStrategy.build()
+ .addListener(listener1)
+ .addListener(listener2);
+
+ if (graph.features().graph().supportsTransactions())
+ builder.eventQueue(new EventStrategy.TransactionalEventQueue(graph));
+
+ final EventStrategy eventStrategy = builder.create();
+
+ final Vertex v = graph.addVertex();
+ v.addEdge("self", v);
+
+ final GraphTraversalSource gts = create(eventStrategy);
+ final Map<Object,Object> m = new HashMap<>();
+ m.put(T.label, "self-but-different");
+ gts.V(v).mergeE(m).next();
+
+ tryCommit(graph, g -> assertEquals(2, IteratorUtils.count(gts.E())));
+
+ assertEquals(0, listener1.addVertexEventRecorded());
+ assertEquals(0, listener2.addVertexEventRecorded());
+
+ assertEquals(1, listener1.addEdgeEventRecorded());
+ assertEquals(1, listener2.addEdgeEventRecorded());
+ }
+
+ @Test
+ @FeatureRequirementSet(FeatureRequirementSet.Package.SIMPLE)
public void shouldTriggerAddEdgeByPath() {
final StubMutationListener listener1 = new StubMutationListener();
final StubMutationListener listener2 = new StubMutationListener();
@@ -246,6 +306,40 @@ public class EventStrategyProcessTest extends AbstractGremlinProcessTest {
@Test
@FeatureRequirementSet(FeatureRequirementSet.Package.VERTICES_ONLY)
+ public void shouldTriggerAddVertexPropertyChangedViaMergeV() {
+ final StubMutationListener listener1 = new StubMutationListener();
+ final StubMutationListener listener2 = new StubMutationListener();
+ final EventStrategy.Builder builder = EventStrategy.build()
+ .addListener(listener1)
+ .addListener(listener2);
+
+ if (graph.features().graph().supportsTransactions())
+ builder.eventQueue(new EventStrategy.TransactionalEventQueue(graph));
+
+ final EventStrategy eventStrategy = builder.create();
+
+ final Vertex vSome = graph.addVertex("some", "thing");
+ vSome.property(VertexProperty.Cardinality.single, "that", "thing");
+ final GraphTraversalSource gts = create(eventStrategy);
+
+ final Map<Object,Object> m1 = new HashMap<>();
+ m1.put("any", "thing");
+ gts.mergeV(m1).iterate();
+
+ final Map<Object,Object> m2 = new HashMap<>();
+ m2.put("any", "thing else");
+ gts.mergeV(m1).option(Merge.onMatch, m2).iterate();
+
+ tryCommit(graph, g -> assertEquals(1, IteratorUtils.count(gts.V().has("any", "thing else"))));
+
+ assertEquals(1, listener1.addVertexEventRecorded());
+ assertEquals(1, listener2.addVertexEventRecorded());
+ assertEquals(1, listener2.vertexPropertyChangedEventRecorded());
+ assertEquals(1, listener1.vertexPropertyChangedEventRecorded());
+ }
+
+ @Test
+ @FeatureRequirementSet(FeatureRequirementSet.Package.VERTICES_ONLY)
@FeatureRequirement(featureClass = Graph.Features.VertexFeatures.class, feature = Graph.Features.VertexFeatures.FEATURE_META_PROPERTIES)
public void shouldTriggerAddVertexPropertyPropertyChanged() {
final StubMutationListener listener1 = new StubMutationListener();
@@ -307,6 +401,44 @@ public class EventStrategyProcessTest extends AbstractGremlinProcessTest {
@Test
@FeatureRequirementSet(FeatureRequirementSet.Package.SIMPLE)
+ public void shouldTriggerUpdateEdgePropertyAddedViaMergeE() {
+ final StubMutationListener listener1 = new StubMutationListener();
+ final StubMutationListener listener2 = new StubMutationListener();
+ final EventStrategy.Builder builder = EventStrategy.build()
+ .addListener(listener1)
+ .addListener(listener2);
+
+ if (graph.features().graph().supportsTransactions())
+ builder.eventQueue(new EventStrategy.TransactionalEventQueue(graph));
+
+ final EventStrategy eventStrategy = builder.create();
+
+ final Vertex v = graph.addVertex();
+ v.addEdge("self", v);
+
+ final GraphTraversalSource gts = create(eventStrategy);
+ final Map<Object,Object> m = new HashMap<>();
+ m.put(T.label, "self");
+ final Map<Object,Object> mMatch = new HashMap<>();
+ mMatch.put("some", "thing");
+ gts.V(v).mergeE(m).option(Merge.onMatch, mMatch).next();
+
+ tryCommit(graph, g -> assertEquals(1, IteratorUtils.count(gts.E().has("some", "thing"))));
+
+ assertEquals(1, IteratorUtils.count(gts.E()));
+
+ assertEquals(0, listener1.addVertexEventRecorded());
+ assertEquals(0, listener2.addVertexEventRecorded());
+
+ assertEquals(0, listener1.addEdgeEventRecorded());
+ assertEquals(0, listener2.addEdgeEventRecorded());
+
+ assertEquals(1, listener2.edgePropertyChangedEventRecorded());
+ assertEquals(1, listener1.edgePropertyChangedEventRecorded());
+ }
+
+ @Test
+ @FeatureRequirementSet(FeatureRequirementSet.Package.SIMPLE)
public void shouldTriggerEdgePropertyChanged() {
final StubMutationListener listener1 = new StubMutationListener();
final StubMutationListener listener2 = new StubMutationListener();
diff --git a/gremlin-tools/gremlin-io-test/src/main/java/org/apache/tinkerpop/gremlin/structure/io/Model.java b/gremlin-tools/gremlin-io-test/src/main/java/org/apache/tinkerpop/gremlin/structure/io/Model.java
index 87e5f86..98f3f35 100644
--- a/gremlin-tools/gremlin-io-test/src/main/java/org/apache/tinkerpop/gremlin/structure/io/Model.java
+++ b/gremlin-tools/gremlin-io-test/src/main/java/org/apache/tinkerpop/gremlin/structure/io/Model.java
@@ -26,12 +26,12 @@ import org.apache.tinkerpop.gremlin.process.traversal.Bytecode;
import org.apache.tinkerpop.gremlin.process.traversal.Operator;
import org.apache.tinkerpop.gremlin.process.traversal.Order;
import org.apache.tinkerpop.gremlin.process.traversal.P;
+import org.apache.tinkerpop.gremlin.process.traversal.Pick;
import org.apache.tinkerpop.gremlin.process.traversal.Pop;
import org.apache.tinkerpop.gremlin.process.traversal.SackFunctions;
import org.apache.tinkerpop.gremlin.process.traversal.Scope;
import org.apache.tinkerpop.gremlin.process.traversal.TextP;
import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource;
-import org.apache.tinkerpop.gremlin.process.traversal.step.TraversalOptionParent;
import org.apache.tinkerpop.gremlin.process.traversal.step.util.BulkSet;
import org.apache.tinkerpop.gremlin.process.traversal.util.DefaultTraversalMetrics;
import org.apache.tinkerpop.gremlin.process.traversal.util.MutableMetrics;
@@ -178,7 +178,7 @@ public class Model {
addGraphProcessEntry(Direction.OUT, "Direction", "", Compatibilities.UNTYPED_GRAPHSON.matchToArray());
addGraphProcessEntry(Operator.sum, "Operator", "", Compatibilities.UNTYPED_GRAPHSON.matchToArray());
addGraphProcessEntry(Order.shuffle, "Order", "", before3_5_0);
- addGraphProcessEntry(TraversalOptionParent.Pick.any, "Pick", "", Compatibilities.UNTYPED_GRAPHSON.matchToArray());
+ addGraphProcessEntry(Pick.any, "Pick", "", Compatibilities.UNTYPED_GRAPHSON.matchToArray());
addGraphProcessEntry(Pop.all, "Pop", "", Compatibilities.UNTYPED_GRAPHSON.matchToArray());
addGraphProcessEntry(org.apache.tinkerpop.gremlin.util.function.Lambda.function("{ it.get() }"), "Lambda", "", Compatibilities.UNTYPED_GRAPHSON.matchToArray());
final TraversalMetrics tm = createStaticTraversalMetrics();
diff --git a/gremlin-tools/gremlin-io-test/src/test/resources/org/apache/tinkerpop/gremlin/structure/io/graphson/_3_2_3/manual-graphson-generator.groovy b/gremlin-tools/gremlin-io-test/src/test/resources/org/apache/tinkerpop/gremlin/structure/io/graphson/_3_2_3/manual-graphson-generator.groovy
index 878f6a3..3806884 100644
--- a/gremlin-tools/gremlin-io-test/src/test/resources/org/apache/tinkerpop/gremlin/structure/io/graphson/_3_2_3/manual-graphson-generator.groovy
+++ b/gremlin-tools/gremlin-io-test/src/test/resources/org/apache/tinkerpop/gremlin/structure/io/graphson/_3_2_3/manual-graphson-generator.groovy
@@ -22,16 +22,13 @@ import org.apache.tinkerpop.gremlin.process.traversal.util.DefaultTraversalMetri
import org.apache.tinkerpop.gremlin.process.traversal.util.MutableMetrics
import java.time.*
-import java.nio.file.*
+
import org.apache.tinkerpop.gremlin.driver.ser.*
import org.apache.tinkerpop.gremlin.process.traversal.*
import org.apache.tinkerpop.gremlin.tinkergraph.structure.*
import org.apache.tinkerpop.gremlin.structure.*
import org.apache.tinkerpop.gremlin.structure.io.graphson.*
import org.apache.tinkerpop.gremlin.driver.message.*
-import org.apache.tinkerpop.gremlin.process.traversal.step.*
-import org.apache.tinkerpop.gremlin.process.traversal.step.TraversalOptionParent.Pick
-import org.apache.tinkerpop.gremlin.structure.io.gryo.*
import org.apache.commons.configuration.BaseConfiguration
import java.util.concurrent.TimeUnit
diff --git a/gremlin-tools/gremlin-io-test/src/test/resources/org/apache/tinkerpop/gremlin/structure/io/graphson/_3_2_4/manual-graphson-generator.groovy b/gremlin-tools/gremlin-io-test/src/test/resources/org/apache/tinkerpop/gremlin/structure/io/graphson/_3_2_4/manual-graphson-generator.groovy
index e1ff14d..6b497b7 100644
--- a/gremlin-tools/gremlin-io-test/src/test/resources/org/apache/tinkerpop/gremlin/structure/io/graphson/_3_2_4/manual-graphson-generator.groovy
+++ b/gremlin-tools/gremlin-io-test/src/test/resources/org/apache/tinkerpop/gremlin/structure/io/graphson/_3_2_4/manual-graphson-generator.groovy
@@ -22,16 +22,13 @@ import org.apache.tinkerpop.gremlin.process.traversal.util.DefaultTraversalMetri
import org.apache.tinkerpop.gremlin.process.traversal.util.MutableMetrics
import java.time.*
-import java.nio.file.*
+
import org.apache.tinkerpop.gremlin.driver.ser.*
import org.apache.tinkerpop.gremlin.process.traversal.*
import org.apache.tinkerpop.gremlin.tinkergraph.structure.*
import org.apache.tinkerpop.gremlin.structure.*
import org.apache.tinkerpop.gremlin.structure.io.graphson.*
import org.apache.tinkerpop.gremlin.driver.message.*
-import org.apache.tinkerpop.gremlin.process.traversal.step.*
-import org.apache.tinkerpop.gremlin.process.traversal.step.TraversalOptionParent.Pick
-import org.apache.tinkerpop.gremlin.structure.io.gryo.*
import org.apache.commons.configuration.BaseConfiguration
import java.util.concurrent.TimeUnit
diff --git a/gremlin-tools/gremlin-io-test/src/test/resources/org/apache/tinkerpop/gremlin/structure/io/gryo/_3_2_3/manual-gryo-generator.groovy b/gremlin-tools/gremlin-io-test/src/test/resources/org/apache/tinkerpop/gremlin/structure/io/gryo/_3_2_3/manual-gryo-generator.groovy
index d231aa7..4f0dfac 100644
--- a/gremlin-tools/gremlin-io-test/src/test/resources/org/apache/tinkerpop/gremlin/structure/io/gryo/_3_2_3/manual-gryo-generator.groovy
+++ b/gremlin-tools/gremlin-io-test/src/test/resources/org/apache/tinkerpop/gremlin/structure/io/gryo/_3_2_3/manual-gryo-generator.groovy
@@ -21,19 +21,13 @@
import org.apache.commons.configuration.BaseConfiguration
import org.apache.tinkerpop.gremlin.process.traversal.util.DefaultTraversalMetrics
import org.apache.tinkerpop.gremlin.process.traversal.util.MutableMetrics
-import org.apache.tinkerpop.gremlin.process.traversal.util.TraversalMetrics
import org.apache.tinkerpop.shaded.kryo.io.Output
import java.time.*
-import java.nio.file.*
-import org.apache.tinkerpop.gremlin.driver.ser.*
+
import org.apache.tinkerpop.gremlin.process.traversal.*
import org.apache.tinkerpop.gremlin.tinkergraph.structure.*
import org.apache.tinkerpop.gremlin.structure.*
-import org.apache.tinkerpop.gremlin.structure.io.graphson.*
-import org.apache.tinkerpop.gremlin.driver.message.*
-import org.apache.tinkerpop.gremlin.process.traversal.step.*
-import org.apache.tinkerpop.gremlin.process.traversal.step.TraversalOptionParent.Pick
import org.apache.tinkerpop.gremlin.structure.io.gryo.*
import java.util.concurrent.TimeUnit
diff --git a/gremlin-tools/gremlin-io-test/src/test/resources/org/apache/tinkerpop/gremlin/structure/io/gryo/_3_2_4/manual-gryo-generator.groovy b/gremlin-tools/gremlin-io-test/src/test/resources/org/apache/tinkerpop/gremlin/structure/io/gryo/_3_2_4/manual-gryo-generator.groovy
index 6e66f36..d67eec4 100644
--- a/gremlin-tools/gremlin-io-test/src/test/resources/org/apache/tinkerpop/gremlin/structure/io/gryo/_3_2_4/manual-gryo-generator.groovy
+++ b/gremlin-tools/gremlin-io-test/src/test/resources/org/apache/tinkerpop/gremlin/structure/io/gryo/_3_2_4/manual-gryo-generator.groovy
@@ -21,19 +21,13 @@
import org.apache.commons.configuration.BaseConfiguration
import org.apache.tinkerpop.gremlin.process.traversal.util.DefaultTraversalMetrics
import org.apache.tinkerpop.gremlin.process.traversal.util.MutableMetrics
-import org.apache.tinkerpop.gremlin.process.traversal.util.TraversalMetrics
import org.apache.tinkerpop.shaded.kryo.io.Output
import java.time.*
-import java.nio.file.*
-import org.apache.tinkerpop.gremlin.driver.ser.*
+
import org.apache.tinkerpop.gremlin.process.traversal.*
import org.apache.tinkerpop.gremlin.tinkergraph.structure.*
import org.apache.tinkerpop.gremlin.structure.*
-import org.apache.tinkerpop.gremlin.structure.io.graphson.*
-import org.apache.tinkerpop.gremlin.driver.message.*
-import org.apache.tinkerpop.gremlin.process.traversal.step.*
-import org.apache.tinkerpop.gremlin.process.traversal.step.TraversalOptionParent.Pick
import org.apache.tinkerpop.gremlin.structure.io.gryo.*
import java.util.concurrent.TimeUnit
diff --git a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/process/traversal/step/map/TinkerMergeEdgeStep.java b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/process/traversal/step/map/TinkerMergeEdgeStep.java
new file mode 100644
index 0000000..23b49a8
--- /dev/null
+++ b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/process/traversal/step/map/TinkerMergeEdgeStep.java
@@ -0,0 +1,97 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.tinkerpop.gremlin.tinkergraph.process.traversal.step.map;
+
+import org.apache.tinkerpop.gremlin.process.traversal.Merge;
+import org.apache.tinkerpop.gremlin.process.traversal.step.map.MergeEdgeStep;
+import org.apache.tinkerpop.gremlin.structure.Direction;
+import org.apache.tinkerpop.gremlin.structure.Edge;
+import org.apache.tinkerpop.gremlin.structure.Property;
+import org.apache.tinkerpop.gremlin.structure.T;
+import org.apache.tinkerpop.gremlin.structure.Vertex;
+import org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerGraph;
+import org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerHelper;
+import org.apache.tinkerpop.gremlin.util.iterator.IteratorUtils;
+
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Stream;
+
+/**
+ * Optimizes {@code mergeE()} searches by attempting to use an index where possible.
+ */
+public class TinkerMergeEdgeStep<S> extends MergeEdgeStep<S> {
+
+ public TinkerMergeEdgeStep(final MergeEdgeStep step) {
+ super(step.getTraversal(), step.isStart(), step.getSearchCreateTraversal());
+ if (step.getOnMatchTraversal() != null) this.addChildOption(Merge.onMatch, step.getOnMatchTraversal());
+ if (step.getOnCreateTraversal() != null) this.addChildOption(Merge.onCreate, step.getOnCreateTraversal());
+ if (step.getCallbackRegistry() != null) this.callbackRegistry = step.getCallbackRegistry();
+ }
+
+ @Override
+ protected Stream<Edge> createSearchStream(final Map<Object, Object> search) {
+ final TinkerGraph graph = (TinkerGraph) this.getTraversal().getGraph().get();
+ Optional<String> firstIndex = Optional.empty();
+
+ Stream<Edge> stream;
+ // prioritize lookup by id but otherwise attempt an index lookup
+ if (search.containsKey(T.id)) {
+ stream = IteratorUtils.stream(graph.edges(search.get(T.id)));
+ } else {
+ // look for the first index we can find - that's the lucky winner. may or may not be the most selective
+ final Set<String> indexedKeys = graph.getIndexedKeys(Edge.class);
+ firstIndex = search.keySet().stream().
+ filter(k -> k instanceof String).
+ map(k -> (String) k).
+ filter(indexedKeys::contains).findFirst();
+
+ // use the index if possible otherwise just in memory filter
+ stream = firstIndex.map(s -> TinkerHelper.queryEdgeIndex(graph, s, search.get(s)).stream().map(e -> (Edge) e)).
+ orElseGet(() -> IteratorUtils.stream(graph.edges()));
+ }
+
+ final Optional<String> indexUsed = firstIndex;
+ stream = stream.filter(e -> {
+ // try to match on all search criteria skipping T.id as it was handled above
+ return search.entrySet().stream().filter(kv -> {
+ final Object k = kv.getKey();
+ return k != T.id && !(indexUsed.isPresent() && indexUsed.get().equals(k));
+ }).allMatch(kv -> {
+ if (kv.getKey() == T.label) {
+ return e.label().equals(kv.getValue());
+ } else if (kv.getKey() instanceof Direction) {
+ final Direction direction = (Direction) kv.getKey();
+
+ // try to take advantage of string id conversions of the graph by doing a lookup rather
+ // than direct compare on id
+ final Iterator<Vertex> found = graph.vertices(kv.getValue());
+ return found.hasNext() && e.vertices(direction).next().equals(found.next());
+ } else {
+ final Property<Object> vp = e.property(kv.getKey().toString());
+ return vp.isPresent() && kv.getValue().equals(vp.value());
+ }
+ });
+ });
+
+ return stream;
+ }
+}
diff --git a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/process/traversal/step/map/TinkerMergeVertexStep.java b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/process/traversal/step/map/TinkerMergeVertexStep.java
new file mode 100644
index 0000000..6771ebd
--- /dev/null
+++ b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/process/traversal/step/map/TinkerMergeVertexStep.java
@@ -0,0 +1,88 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.tinkerpop.gremlin.tinkergraph.process.traversal.step.map;
+
+import org.apache.tinkerpop.gremlin.process.traversal.Merge;
+import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
+import org.apache.tinkerpop.gremlin.process.traversal.step.map.MergeVertexStep;
+import org.apache.tinkerpop.gremlin.structure.Graph;
+import org.apache.tinkerpop.gremlin.structure.T;
+import org.apache.tinkerpop.gremlin.structure.Vertex;
+import org.apache.tinkerpop.gremlin.structure.VertexProperty;
+import org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerGraph;
+import org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerHelper;
+import org.apache.tinkerpop.gremlin.util.iterator.IteratorUtils;
+
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Stream;
+
+/**
+ * Optimizes {@code mergeV()} searches by attempting to use an index where possible.
+ */
+public class TinkerMergeVertexStep<S> extends MergeVertexStep<S> {
+ public TinkerMergeVertexStep(final MergeVertexStep step) {
+ super(step.getTraversal(), step.isStart(), step.getSearchCreateTraversal());
+ if (step.getOnMatchTraversal() != null) this.addChildOption(Merge.onMatch, step.getOnMatchTraversal());
+ if (step.getOnCreateTraversal() != null) this.addChildOption(Merge.onCreate, step.getOnCreateTraversal());
+ if (step.getCallbackRegistry() != null) this.callbackRegistry = step.getCallbackRegistry();
+ }
+
+ @Override
+ protected Stream<Vertex> createSearchStream(final Map<Object, Object> search) {
+ final TinkerGraph graph = (TinkerGraph) this.getTraversal().getGraph().get();
+ Optional<String> firstIndex = Optional.empty();
+
+ Stream<Vertex> stream;
+ // prioritize lookup by id but otherwise attempt an index lookup
+ if (search.containsKey(T.id)) {
+ stream = IteratorUtils.stream(graph.vertices(search.get(T.id)));
+ } else {
+ // look for the first index we can find - that's the lucky winner. may or may not be the most selective
+ final Set<String> indexedKeys = graph.getIndexedKeys(Vertex.class);
+ firstIndex = search.keySet().stream().
+ filter(k -> k instanceof String).
+ map(k -> (String) k).
+ filter(indexedKeys::contains).findFirst();
+
+ // use the index if possible otherwise just in memory filter
+ stream = firstIndex.map(s -> TinkerHelper.queryVertexIndex(graph, s, search.get(s)).stream().map(v -> (Vertex) v)).
+ orElseGet(() -> IteratorUtils.stream(graph.vertices()));
+ }
+
+ final Optional<String> indexUsed = firstIndex;
+ stream = stream.filter(v -> {
+ // try to match on all search criteria skipping T.id as it was handled above
+ return search.entrySet().stream().filter(kv -> {
+ final Object k = kv.getKey();
+ return k != T.id && !(indexUsed.isPresent() && indexUsed.get().equals(k));
+ }).allMatch(kv -> {
+ if (kv.getKey() == T.label) {
+ return v.label().equals(kv.getValue());
+ } else {
+ final VertexProperty<Object> vp = v.property(kv.getKey().toString());
+ return vp.isPresent() && kv.getValue().equals(vp.value());
+ }
+ });
+ });
+
+ return stream;
+ }
+}
diff --git a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/process/traversal/strategy/optimization/TinkerMergeEVStepStrategy.java b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/process/traversal/strategy/optimization/TinkerMergeEVStepStrategy.java
new file mode 100644
index 0000000..c4b1ea5
--- /dev/null
+++ b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/process/traversal/strategy/optimization/TinkerMergeEVStepStrategy.java
@@ -0,0 +1,61 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.tinkerpop.gremlin.tinkergraph.process.traversal.strategy.optimization;
+
+import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
+import org.apache.tinkerpop.gremlin.process.traversal.TraversalStrategy;
+import org.apache.tinkerpop.gremlin.process.traversal.step.map.MergeEdgeStep;
+import org.apache.tinkerpop.gremlin.process.traversal.step.map.MergeVertexStep;
+import org.apache.tinkerpop.gremlin.process.traversal.strategy.AbstractTraversalStrategy;
+import org.apache.tinkerpop.gremlin.process.traversal.util.TraversalHelper;
+import org.apache.tinkerpop.gremlin.tinkergraph.process.traversal.step.map.TinkerMergeEdgeStep;
+import org.apache.tinkerpop.gremlin.tinkergraph.process.traversal.step.map.TinkerMergeVertexStep;
+
+/**
+ * Optimizes {@code mergeV()} and {@code mergeE()} search lookups by using {@link TinkerMergeVertexStep} and
+ * {@link TinkerMergeEdgeStep} respectively.
+ */
+public final class TinkerMergeEVStepStrategy extends AbstractTraversalStrategy<TraversalStrategy.ProviderOptimizationStrategy>
+ implements TraversalStrategy.ProviderOptimizationStrategy {
+
+ private static final TinkerMergeEVStepStrategy INSTANCE = new TinkerMergeEVStepStrategy();
+
+ private TinkerMergeEVStepStrategy() {
+ }
+
+ @Override
+ public void apply(final Traversal.Admin<?, ?> traversal) {
+ if (TraversalHelper.onGraphComputer(traversal))
+ return;
+
+ for (final MergeVertexStep originalMergeVertexStep : TraversalHelper.getStepsOfClass(MergeVertexStep.class, traversal)) {
+ final TinkerMergeVertexStep tinkerMergeVertexStep = new TinkerMergeVertexStep(originalMergeVertexStep);
+ TraversalHelper.replaceStep(originalMergeVertexStep, tinkerMergeVertexStep, traversal);
+ }
+
+ for (final MergeEdgeStep originalMergeEdgeStep : TraversalHelper.getStepsOfClass(MergeEdgeStep.class, traversal)) {
+ final TinkerMergeEdgeStep tinkerMergeEdgeStep = new TinkerMergeEdgeStep(originalMergeEdgeStep);
+ TraversalHelper.replaceStep(originalMergeEdgeStep, tinkerMergeEdgeStep, traversal);
+ }
+ }
+
+ public static TinkerMergeEVStepStrategy instance() {
+ return INSTANCE;
+ }
+}
diff --git a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerGraph.java b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerGraph.java
index b9dda16..795b4fa 100644
--- a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerGraph.java
+++ b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerGraph.java
@@ -39,6 +39,7 @@ import org.apache.tinkerpop.gremlin.tinkergraph.process.computer.TinkerGraphComp
import org.apache.tinkerpop.gremlin.tinkergraph.process.computer.TinkerGraphComputerView;
import org.apache.tinkerpop.gremlin.tinkergraph.process.traversal.strategy.optimization.TinkerGraphCountStrategy;
import org.apache.tinkerpop.gremlin.tinkergraph.process.traversal.strategy.optimization.TinkerGraphStepStrategy;
+import org.apache.tinkerpop.gremlin.tinkergraph.process.traversal.strategy.optimization.TinkerMergeEVStepStrategy;
import org.apache.tinkerpop.gremlin.util.iterator.IteratorUtils;
import java.io.File;
@@ -72,7 +73,8 @@ public final class TinkerGraph implements Graph {
static {
TraversalStrategies.GlobalCache.registerStrategies(TinkerGraph.class, TraversalStrategies.GlobalCache.getStrategies(Graph.class).clone().addStrategies(
TinkerGraphStepStrategy.instance(),
- TinkerGraphCountStrategy.instance()));
+ TinkerGraphCountStrategy.instance(),
+ TinkerMergeEVStepStrategy.instance()));
}
private static final Configuration EMPTY_CONFIGURATION = new BaseConfiguration() {{
diff --git a/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/TinkerGraphWorld.java b/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/TinkerGraphWorld.java
index 7d6f820..46856f8 100644
--- a/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/TinkerGraphWorld.java
+++ b/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/TinkerGraphWorld.java
@@ -106,6 +106,7 @@ public class TinkerGraphWorld implements World {
private static final List<String> TAGS_TO_IGNORE = Arrays.asList(
"@StepDrop",
+ "@StepInject",
"@StepV",
"@GraphComputerVerificationOneBulk",
"@GraphComputerVerificationStrategyNotSupported",