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/21 12:59:51 UTC

[tinkerpop] branch TINKERPOP-2681 updated (0e09fbd -> 2942929)

This is an automated email from the ASF dual-hosted git repository.

spmallette pushed a change to branch TINKERPOP-2681
in repository https://gitbox.apache.org/repos/asf/tinkerpop.git.


 discard 0e09fbd  wip
 discard 0b5ee8d  wip
 discard 6b79c8b  wip
     add 728c438  Tweak bio based on recent focus. Small CTR change
     add 04393e3  Added reference doc link to main menu CTR
     add 33b0daa  Squashed all commits and merged from master
     add 4a1d59d  Merge pull request #1546 from bechbd/TINKERPOP-2665-squashed
     add b0edd69  Minor documentation formatting tweaks CTR
     add b74e5f6  when testing strings representing file contents cause the expected value to use system line-separators escape constructed literal strings representing file paths
     add 15728ba  Merge pull request #1548 from phreed/TINKERPOP-2628-SQUASH
     add 6b15623  Updated home page for 3.4.13/3.5.2 release CTR
     add db0d45a  Fix code examples CTR
     add 9249d14  Update gremlint dependencies to get rid of vulnerabilities CTR
     add ec28bf7  Update gremlint website dependencies to get rid of vulnerabilities CTR
     add 9f58ec5  Orderability Semantics
     add fe74928  Merge pull request #1545 from mikepersonick/TINKERPOP-2641
     add 4e648bf  Update gremlint dependencies to get rid of vulnerabilities CTR
     add 134180f  Update gremlint website dependencies to get rid of vulnerabilities CTR
     add 93d7109  gremlin-javascript: Tune Writer/Reader abstraction border
     add ec78520  Merge pull request #1549 from ihoro/gremlin-javascript-tune-writer-reader-abstraction-border
     add 8e4c226  TINKERPOP-2651 Update to .NET 6
     add 71a977f  Merge branch 'TINKERPOP-2651' into 3.5-dev
     add f48fc35  Merge branch '3.5-dev'
     add 4043c0c  Added lyndon bauto as a committer
     add 71fd8fe  Bump to 3.5.3-SNAPSHOT CTR
     add 3173e90  Merge branch '3.5-dev'
     add f581917  Fixed up license headers CTR
     add 19001ef  Minor fix in upgrade docs CTR
     add 62e2f73  Minor doc fixes CTR
     add ba6dd04  Merge branch '3.5-dev'
     add e547e9c  Changing Lyndon's committer website.
     new 7b9ef89  wip
     new 7273b69  f
     new 2942929  wip

This update added new revisions after undoing existing revisions.
That is to say, some revisions that were in the old version of the
branch are not in the new version.  This situation occurs
when a user --force pushes a change and generates a repository
containing something like this:

 * -- * -- B -- O -- O -- O   (0e09fbd)
            \
             N -- N -- N   refs/heads/TINKERPOP-2681 (2942929)

You should already have received notification emails for all of the O
revisions, and so the following emails describe only the N revisions
from the common base, B.

Any revisions marked "omit" are not gone; other references still
refer to them.  Any revisions marked "discard" are gone forever.

The 3 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .github/workflows/build-test.yml                   |     4 +-
 .mailmap                                           |    21 +-
 CHANGELOG.asciidoc                                 |     6 +
 docker/Dockerfile                                  |     2 +-
 docs/gremlint/package-lock.json                    | 32966 ++++++-------------
 docs/gremlint/package.json                         |    34 +-
 docs/gremlint/src/components/Footer.tsx            |     4 +-
 docs/site/home/benefits-use-cases.html             |     1 +
 docs/site/home/community.html                      |     1 +
 docs/site/home/download.html                       |    84 +-
 docs/site/home/gremlin.html                        |   122 +-
 docs/site/home/index.html                          |   121 +-
 docs/site/home/providers.html                      |     1 +
 .../dev/developer/development-environment.asciidoc |    10 +-
 docs/src/dev/developer/for-committers.asciidoc     |     4 +
 docs/src/dev/developer/release.asciidoc            |     5 +-
 docs/src/dev/io/graphbinary.asciidoc               |     5 +
 docs/src/reference/gremlin-variants.asciidoc       |     2 +-
 docs/src/reference/the-traversal.asciidoc          |    18 +-
 docs/src/upgrade/release-3.5.x.asciidoc            |     2 +-
 docs/src/upgrade/release-3.6.x.asciidoc            |    85 +-
 .../language/grammar/GenericLiteralVisitor.java    |     4 +-
 .../language/grammar/GremlinBaseVisitor.java       |     8 +
 .../language/grammar/TraversalMethodVisitor.java   |    19 +
 .../tinkerpop/gremlin/process/traversal/Order.java |    22 +-
 .../traversal/dsl/graph/GraphTraversal.java        |    50 +-
 .../gremlin/process/traversal/dsl/graph/__.java    |     7 +
 .../traversal/step/map/MergeVertexStep.java        |     2 +-
 .../traversal/translator/GroovyTranslator.java     |     2 +-
 .../traversal/util/DefaultTraversalMetrics.java    |     7 +-
 .../apache/tinkerpop/gremlin/structure/Graph.java  |   342 +-
 .../gremlin/structure/io/binary/DataType.java      |     1 +
 .../structure/io/graphson/GraphSONModule.java      |     7 +
 .../gremlin/util/OrderabilityComparator.java       |   189 +
 .../grammar/TraversalMethodVisitorTest.java        |    28 +
 .../util/DefaultTraversalMetricsTest.java          |    27 +-
 gremlin-dotnet/build/generate.groovy               |    34 +-
 .../Gremlin.Net.Template.csproj                    |     2 +-
 gremlin-dotnet/src/pom.xml                         |    19 +-
 .../Gherkin/CommonSteps.cs                         |     4 +-
 .../Gherkin/GherkinTestRunner.cs                   |     4 +-
 .../Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs |    21 +
 .../Gherkin/IgnoreException.cs                     |     7 +-
 .../Gremlin.Net.IntegrationTest.csproj             |     2 +-
 .../Gremlin.Net.Template.IntegrationTest.csproj    |     2 +-
 .../Gremlin.Net.UnitTest.csproj                    |     2 +-
 gremlin-dotnet/test/pom.xml                        |     2 +-
 .../groovy/jsr223/GroovyTranslatorProvider.java    |     4 +
 .../ParameterizedGroovyTranslatorProvider.java     |     4 +
 gremlin-javascript/pom.xml                         |    13 +-
 .../gremlin-javascript/lib/driver/connection.js    |    66 +-
 .../lib/process/graph-traversal.js                 |    20 +
 .../lib/structure/io/graph-serializer.js           |    46 +
 .../test/cucumber/feature-steps.js                 |    27 +-
 .../gremlin-javascript/test/cucumber/gremlin.js    |    24 +-
 gremlin-language/src/main/antlr4/Gremlin.g4        |     2 +
 .../gremlin/language/corpus/FeatureReader.java     |    12 +-
 .../language/grammar/ReferenceGrammarTest.java     |     6 +-
 gremlin-python/build/generate.groovy               |     2 +-
 .../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        |    15 +-
 gremlin-python/src/main/python/radish/gremlin.py   |    26 +-
 .../driver/remote/AbstractRemoteGraphProvider.java |     4 +
 gremlin-test/features/map/AddVertex.feature        |    22 +
 .../features/semantics/Orderability.feature        |   355 +
 gremlin-test/features/sideEffect/Group.feature     |    22 +-
 gremlin-test/features/sideEffect/Inject.feature    |     7 +-
 .../tinkerpop/gremlin/AbstractGremlinTest.java     |     8 +
 .../tinkerpop/gremlin/features/StepDefinition.java |    41 +-
 .../process/ProcessLimitedStandardSuite.java       |     8 +-
 .../gremlin/process/ProcessStandardSuite.java      |     6 +-
 .../process/traversal/step/OrderabilityTest.java   |   536 +
 gremlin-tools/gremlin-coverage/pom.xml             |    17 +-
 gremlin-tools/gremlin-io-test/pom.xml              |    26 +-
 .../tinkerpop/gremlin/structure/io/Model.java      |     9 +-
 gremlin-tools/pom.xml                              |    17 +-
 gremlint/package-lock.json                         |     2 +
 gremlint/pom.xml                                   |    13 +-
 .../gremlin/neo4j/Neo4jGraphFeatureTest.java       |     2 +-
 pom.xml                                            |     2 +-
 sparql-gremlin/pom.xml                             |    17 +-
 .../traversal/step/map/TinkerMergeVertexStep.java  |     1 +
 .../tinkergraph/structure/TinkerFactory.java       |    25 +-
 .../gremlin/tinkergraph/TinkerGraphWorld.java      |     1 +
 ...tractTinkerGraphGraphSONTranslatorProvider.java |    12 +
 .../io/gryo/TinkerGraphGryoTranslatorProvider.java |     4 +
 88 files changed, 13209 insertions(+), 22580 deletions(-)
 create mode 100644 gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/util/OrderabilityComparator.java
 create mode 100644 gremlin-test/features/semantics/Orderability.feature
 create mode 100644 gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/OrderabilityTest.java

[tinkerpop] 01/03: wip

Posted by sp...@apache.org.
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 7b9ef8983d2ccc0df48a0981cfcb2fa5a8b6855c
Author: Stephen Mallette <st...@amazon.com>
AuthorDate: Wed Jan 12 10:12:16 2022 -0500

    wip
---
 docs/src/dev/developer/for-committers.asciidoc     |   1 +
 .../tinkerpop/gremlin/jsr223/CoreImports.java      |   7 +-
 .../language/grammar/GenericLiteralVisitor.java    |  17 +-
 .../language/grammar/GremlinBaseVisitor.java       |  55 ++++
 .../language/grammar/TraversalMethodVisitor.java   |  39 +++
 .../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        | 102 ++++++-
 .../traversal/dsl/graph/GraphTraversalSource.java  |  82 ++++-
 .../gremlin/process/traversal/dsl/graph/__.java    |  30 +-
 .../traversal/step/TraversalOptionParent.java      |  11 +-
 .../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  | 334 +++++++++++++++++++++
 .../traversal/step/map/MergeVertexStep.java        | 287 ++++++++++++++++++
 .../traversal/translator/DotNetTranslator.java     |   5 +-
 .../traversal/translator/GroovyTranslator.java     |   6 +-
 .../traversal/translator/JavascriptTranslator.java |   4 +-
 .../traversal/translator/PythonTranslator.java     |   4 +-
 .../io/binary/TypeSerializerRegistry.java          |   4 +-
 .../structure/io/binary/types/EnumSerializer.java  |   4 +-
 .../structure/io/graphson/GraphSONModule.java      |  14 +-
 .../io/graphson/GraphSONTypeSerializerV2d0.java    |   6 +-
 .../io/graphson/GraphSONTypeSerializerV3d0.java    |   6 +-
 .../gremlin/structure/io/gryo/GryoVersion.java     |   6 +-
 .../language/grammar/TraversalEnumParserTest.java  |   4 +-
 .../traversal/step/branch/BranchStepTest.java      |   2 +-
 .../traversal/step/branch/ChooseStepTest.java      |   2 +-
 gremlin-dotnet/build/generate.groovy               |  10 +
 .../Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs |  24 ++
 .../GraphBinaryReaderWriterRoundTripTest.java      |   4 +-
 .../ast/VarAsBindingASTTransformation.groovy       |   7 +-
 .../jsr223/GremlinGroovyScriptEngineTest.java      |   9 +-
 gremlin-javascript/build/generate.groovy           |  11 +
 .../lib/process/graph-traversal.js                 |  22 ++
 .../gremlin-javascript/lib/process/traversal.js    |   1 +
 .../gremlin-javascript/test/cucumber/gremlin.js    |  25 ++
 gremlin-language/src/main/antlr4/Gremlin.g4        |  33 ++
 .../language/corpus/DocumentationReader.java       |   2 +
 .../language/grammar/ReferenceGrammarTest.java     |  16 +-
 gremlin-python/build/generate.groovy               |  10 +
 gremlin-python/src/main/python/radish/gremlin.py   |  24 ++
 gremlin-test/features/map/MergeEdge.feature        | 184 ++++++++++++
 gremlin-test/features/map/MergeVertex.feature      | 265 ++++++++++++++++
 gremlin-test/features/sideEffect/Group.feature     |   2 +-
 .../tinkerpop/gremlin/features/StepDefinition.java |  27 +-
 .../process/ProcessLimitedStandardSuite.java       |  56 ----
 .../process/traversal/step/branch/BranchTest.java  |   4 +-
 .../process/traversal/step/branch/ChooseTest.java  |   6 +-
 .../decoration/EventStrategyProcessTest.java       |  62 ++++
 .../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/TinkerMergeVertexStep.java  |  87 ++++++
 .../TinkerMergeVertexStepStrategy.java             |  53 ++++
 .../gremlin/tinkergraph/structure/TinkerGraph.java |   4 +-
 61 files changed, 1953 insertions(+), 169 deletions(-)

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/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..67be548 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);
@@ -349,7 +352,7 @@ public final class CoreImports {
         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..e987683 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,59 @@ 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;
+	}
 }
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..c89ed86 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;
@@ -89,6 +90,26 @@ public class TraversalMethodVisitor extends TraversalRootVisitor<GraphTraversal>
     }
 
     @Override
+    public GraphTraversal visitTraversalMethod_mergeV_Map(final GremlinParser.TraversalMethod_mergeV_MapContext ctx) {
+        return this.graphTraversal.mergeV(GenericLiteralVisitor.getMapLiteral(ctx.genericLiteralMap()));
+    }
+
+    @Override
+    public GraphTraversal visitTraversalMethod_mergeV_Traversal(final GremlinParser.TraversalMethod_mergeV_TraversalContext ctx) {
+        return this.graphTraversal.mergeV(antlr.tvisitor.visitNestedTraversal(ctx.nestedTraversal()));
+    }
+
+    @Override
+    public GraphTraversal visitTraversalMethod_mergeE_Map(final GremlinParser.TraversalMethod_mergeE_MapContext ctx) {
+        return this.graphTraversal.mergeE(GenericLiteralVisitor.getMapLiteral(ctx.genericLiteralMap()));
+    }
+
+    @Override
+    public GraphTraversal visitTraversalMethod_mergeE_Traversal(final GremlinParser.TraversalMethod_mergeE_TraversalContext ctx) {
+        return this.graphTraversal.mergeE(antlr.tvisitor.visitNestedTraversal(ctx.nestedTraversal()));
+    }
+
+    @Override
     public GraphTraversal visitTraversalMethod_addE_Traversal(final GremlinParser.TraversalMethod_addE_TraversalContext ctx) {
         return this.graphTraversal.addE(antlr.tvisitor.visitNestedTraversal(ctx.nestedTraversal()));
     }
@@ -976,6 +997,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..9b0bdc5 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,63 @@ public interface GraphTraversal<S, E> extends Traversal<S, E> {
     }
 
     /**
+     * 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 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
@@ -3060,7 +3122,7 @@ public interface GraphTraversal<S, E> extends Traversal<S, E> {
     /**
      * This step modifies {@link #choose(Function)} to specifies the available choices that might be executed.
      *
-     * @param pick       the token that would trigger this option which may be a {@link TraversalOptionParent.Pick},
+     * @param pick       the token that would trigger this option which may be a {@link Pick},
      *                   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
@@ -3069,7 +3131,7 @@ public interface GraphTraversal<S, E> extends Traversal<S, E> {
      */
     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());
+        ((TraversalOptionParent<M, E, E2>) this.asAdmin().getEndStep()).addChildOption(pick, (Traversal.Admin<E, E2>) traversalOption.asAdmin());
         return this;
     }
 
@@ -3083,7 +3145,39 @@ 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;
+    }
+
+    /**
+     * This step modifies merge related steps to specify the available choices that might be executed on the available
+     * {@link Merge} options.
+     *
+     * @param traversal the option as a traversal
+     * @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 <E2> GraphTraversal<S, E> option(final Merge merge, final Traversal<?, E2> traversal) {
+        this.asAdmin().getBytecode().addStep(Symbols.option, merge, traversal);
+        ((TraversalOptionParent<Merge, E, E2>) this.asAdmin().getEndStep()).addChildOption(merge, (Traversal.Admin<E, E2>) traversal.asAdmin());
+        return this;
+    }
+
+    /**
+     * This step modifies merge related steps to specify the available choices that might be executed on the available
+     * {@link Merge} options.
+     *
+     * @param 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 <E2> GraphTraversal<S, E> option(final Merge merge, final Map<Object, Object> m) {
+        this.asAdmin().getBytecode().addStep(Symbols.option, merge, m);
+        ((TraversalOptionParent<Merge, E, E2>) this.asAdmin().getEndStep()).addChildOption(merge, (Traversal.Admin<E, E2>) new ConstantTraversal<>(m).asAdmin());
         return this;
     }
 
@@ -3186,6 +3280,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..01e5c0f 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,20 @@ public class __ {
     }
 
     /**
+     * @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 +537,27 @@ 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(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..ef9b92c 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,19 @@
  */
 package org.apache.tinkerpop.gremlin.process.traversal.step;
 
+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 {@code option()}.
+ *
  * @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}. 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..e5e4a19
--- /dev/null
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MergeEdgeStep.java
@@ -0,0 +1,334 @@
+/*
+ * 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.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;
+
+    private CallbackRegistry<Event> callbackRegistry;
+
+    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 vertex 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 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<Object> oldValue = eventStrategy.detach(e.property(key));
+                    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..319db5d
--- /dev/null
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MergeVertexStep.java
@@ -0,0 +1,287 @@
+/*
+ * 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.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;
+
+    private CallbackRegistry<Event> callbackRegistry;
+
+    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<Object> oldValue = eventStrategy.detach(v.property(key));
+                    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/translator/DotNetTranslator.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/translator/DotNetTranslator.java
index 4f375e5..5f3ae25 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,10 +19,10 @@
 
 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.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 +30,6 @@ 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.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 +170,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());
         }
 
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..bfa3065 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,7 +173,7 @@ public final class GroovyTranslator implements Translator.ScriptTranslator {
         }
 
         @Override
-        protected String getSyntax(final TraversalOptionParent.Pick o) {
+        protected String getSyntax(final Pick o) {
             return "TraversalOptionParent.Pick." + o.toString();
         }
 
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/TypeSerializerRegistry.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/binary/TypeSerializerRegistry.java
index ced0088..0c4b913 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,7 @@
  */
 package org.apache.tinkerpop.gremlin.structure.io.binary;
 
+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 +31,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;
@@ -119,7 +119,7 @@ public class TypeSerializerRegistry {
             new RegistryEntry<>(Direction.class, EnumSerializer.DirectionSerializer),
             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..52b4c08 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,7 @@
  */
 package org.apache.tinkerpop.gremlin.structure.io.binary.types;
 
+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 +27,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;
@@ -49,7 +49,7 @@ public class EnumSerializer<E extends Enum> extends SimpleTypeSerializer<E> {
     public static final EnumSerializer<Direction> DirectionSerializer = new EnumSerializer<>(DataType.DIRECTION, Direction::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..fccab3f 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
@@ -25,6 +25,7 @@ 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 +33,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;
@@ -178,7 +178,7 @@ abstract class GraphSONModule extends TinkerPopJacksonModule {
                             Order.class,
                             Pop.class,
                             SackFunctions.Barrier.class,
-                            TraversalOptionParent.Pick.class,
+                            Pick.class,
                             Scope.class,
                             T.class).forEach(e -> put(e, e.getSimpleName()));
                     Arrays.asList(
@@ -262,7 +262,7 @@ abstract class GraphSONModule extends TinkerPopJacksonModule {
                     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());
@@ -304,7 +304,7 @@ abstract class GraphSONModule extends TinkerPopJacksonModule {
                     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());
@@ -416,7 +416,7 @@ abstract class GraphSONModule extends TinkerPopJacksonModule {
                             Order.class,
                             Pop.class,
                             SackFunctions.Barrier.class,
-                            TraversalOptionParent.Pick.class,
+                            Pick.class,
                             Scope.class,
                             T.class).forEach(e -> put(e, e.getSimpleName()));
                     Arrays.asList(
@@ -496,7 +496,7 @@ abstract class GraphSONModule extends TinkerPopJacksonModule {
                     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());
@@ -532,7 +532,7 @@ abstract class GraphSONModule extends TinkerPopJacksonModule {
                     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..eda92fe 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
@@ -30,11 +30,11 @@ 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 +329,7 @@ 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(HashSetSupplier.class, 136, new UtilSerializers.HashSetSupplierSerializer()));
             add(GryoTypeReg.of(MultiComparator.class, 165));
 
@@ -529,7 +529,7 @@ 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(HashSetSupplier.class, 136, new UtilSerializers.HashSetSupplierSerializer()));
             add(GryoTypeReg.of(MultiComparator.class, 165));
 
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-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/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs
index 021f484..f89e2a5 100644
--- a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs
+++ b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs
@@ -500,6 +500,30 @@ 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(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(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(p["xx1"]), (g,p) =>g.V().Has("person","name","marko").OutE("knows").Has("weight",0.5).InV().Has("person","name","vadas"), (g,p) =>g.V().Has("p [...]
+               {"g_mergeEXlabel_knows_out_marko_in_vadas_weight_05X", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.MergeE(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(p["xx1"]).Option(Merge.OnCreate,p["xx2"]).Option(Merge.OnMatch,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(p["xx1"]).Option(Merge.OnCreate,p["xx2"]).Option(Merge.OnMatch,p["xx3"]), (g,p) =>g.V(), (g,p) =>g. [...]
+               {"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(p["xx1"]).Option(Merge.OnCreate,p["xx2"]).Option(Merge.OnMatch,p["x [...]
+               {"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(p["xx1"]), (g,p) =>g.V(), (g,p) =>g.E()}}, 
+               {"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(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(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(p["xx1"]).Option(Merge.OnCreate,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(p["xx1"]).Option(Merge.OnMatch,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(__.Select<object>("c")).Option(Merge.OnCreate,__.Select<object>("m")), (g,p) =>g.V().Has [...]
+               {"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(__.Select<object>("c")).Option(Merge.OnMatch,__.Select<object>("m")), (g,p) =>g.V().Has("person","name","marko").Has( [...]
+               {"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(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(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(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(p["xx1"]).Option(Merge.OnCreate,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(p["xx1"]).Option(Merge.OnMatch,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(__.Select<object>("c")).Option(Merge.OnCreate,__.Select<object>("m") [...]
+               {"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(__.Select<object>("c")).Option(Merge.OnMatch,__.Select<object>("m")), (g,p) =>g.V().Has("person", [...]
+               {"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(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(__.Identity()), (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>()}}, 
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..c609ba0 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
@@ -810,6 +810,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 +1385,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/gremlin.js b/gremlin-javascript/src/main/javascript/gremlin-javascript/test/cucumber/gremlin.js
index 0a452a6..6079579 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,30 @@ 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_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_V_age_min: [function({g}) { return g.V().values("age").min() }], 
     g_V_foo_min: [function({g}) { return g.V().values("foo").min() }], 
     g_V_name_min: [function({g}) { return g.V().values("name").min() }], 
diff --git a/gremlin-language/src/main/antlr4/Gremlin.g4 b/gremlin-language/src/main/antlr4/Gremlin.g4
index 78d8928..242b79c 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,17 @@ traversalMethod_addV
 	| 'addV' LPAREN nestedTraversal RPAREN #traversalMethod_addV_Traversal
 	;
 
+traversalMethod_mergeV
+    : 'mergeV' LPAREN genericLiteralMap RPAREN #traversalMethod_mergeV_Map
+    | 'mergeV' LPAREN nestedTraversal RPAREN #traversalMethod_mergeV_Traversal
+    ;
+
+traversalMethod_mergeE
+    : '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 +556,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 +864,11 @@ traversalToken
     | 'value' | 'T.value'
     ;
 
+traversalMerge
+    : 'onCreate' | 'Merge.onCreate'
+    | 'onMatch' | 'Merge.onMatch'
+    ;
+
 traversalOrder
     : 'incr' | 'Order.incr'
     | 'decr' | 'Order.decr'
@@ -1310,6 +1342,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..a0e91fe 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)", 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..a633cd7 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")
 
diff --git a/gremlin-python/src/main/python/radish/gremlin.py b/gremlin-python/src/main/python/radish/gremlin.py
index 5ec3509..7f4a084 100644
--- a/gremlin-python/src/main/python/radish/gremlin.py
+++ b/gremlin-python/src/main/python/radish/gremlin.py
@@ -473,6 +473,30 @@ 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_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_V_age_min': [(lambda g:g.V().age.min_())], 
     'g_V_foo_min': [(lambda g:g.V().foo.min_())], 
     'g_V_name_min': [(lambda g:g.V().name.min_())], 
diff --git a/gremlin-test/features/map/MergeEdge.feature b/gremlin-test/features/map/MergeEdge.feature
new file mode 100644
index 0000000..65173ce
--- /dev/null
+++ b/gremlin-test/features/map/MergeEdge.feature
@@ -0,0 +1,184 @@
+# 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()"
\ 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..385f2f8
--- /dev/null
+++ b/gremlin-test/features/map/MergeVertex.feature
@@ -0,0 +1,265 @@
+# 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()"
diff --git a/gremlin-test/features/sideEffect/Group.feature b/gremlin-test/features/sideEffect/Group.feature
index d4d3867..b8e7b5c 100644
--- a/gremlin-test/features/sideEffect/Group.feature
+++ b/gremlin-test/features/sideEffect/Group.feature
@@ -266,7 +266,7 @@ Feature: Step - group()
   # 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 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(keys).by(select(values).order(Scope.local).by(Order.asc))
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..ea5eed3 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)));
 
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..e55d7bb 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
@@ -23,65 +23,11 @@ import org.apache.tinkerpop.gremlin.process.traversal.CoreTraversalTest;
 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 +39,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..ed1e6a2 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,6 +21,7 @@ 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;
@@ -40,7 +41,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 +86,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();
@@ -246,6 +274,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();
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/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..5876879
--- /dev/null
+++ b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/process/traversal/step/map/TinkerMergeVertexStep.java
@@ -0,0 +1,87 @@
+/*
+ * 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());
+    }
+
+    @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/TinkerMergeVertexStepStrategy.java b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/process/traversal/strategy/optimization/TinkerMergeVertexStepStrategy.java
new file mode 100644
index 0000000..e5806ea
--- /dev/null
+++ b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/process/traversal/strategy/optimization/TinkerMergeVertexStepStrategy.java
@@ -0,0 +1,53 @@
+/*
+ * 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.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.TinkerMergeVertexStep;
+
+/**
+ * Optimizes {@code mergeV()} search lookups by using {@link TinkerMergeVertexStep}.
+ */
+public final class TinkerMergeVertexStepStrategy extends AbstractTraversalStrategy<TraversalStrategy.ProviderOptimizationStrategy>
+        implements TraversalStrategy.ProviderOptimizationStrategy {
+
+    private static final TinkerMergeVertexStepStrategy INSTANCE = new TinkerMergeVertexStepStrategy();
+
+    private TinkerMergeVertexStepStrategy() {
+    }
+
+    @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);
+        }
+    }
+
+    public static TinkerMergeVertexStepStrategy 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..514d008 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.TinkerMergeVertexStepStrategy;
 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(),
+                TinkerMergeVertexStepStrategy.instance()));
     }
 
     private static final Configuration EMPTY_CONFIGURATION = new BaseConfiguration() {{

[tinkerpop] 03/03: wip

Posted by sp...@apache.org.
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 294292958d69e50533b1527e791188b4ec8d566a
Author: Stephen Mallette <st...@amazon.com>
AuthorDate: Thu Jan 20 06:48:19 2022 -0500

    wip
---
 docs/src/dev/io/graphbinary.asciidoc               |  5 +++
 .../traversal/step/map/MergeVertexStep.java        |  2 +-
 .../traversal/translator/GroovyTranslator.java     |  2 +-
 .../gremlin/structure/io/binary/DataType.java      |  1 +
 .../structure/io/graphson/GraphSONModule.java      |  7 ++++
 .../lib/process/graph-traversal.js                 | 20 ++++++++++++
 .../test/cucumber/feature-steps.js                 |  8 ++++-
 .../gremlin-javascript/test/cucumber/gremlin.js    |  3 +-
 .../language/grammar/ReferenceGrammarTest.java     |  2 +-
 gremlin-python/build/generate.groovy               |  2 +-
 .../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        |  9 +++--
 gremlin-python/src/main/python/radish/gremlin.py   |  5 ++-
 gremlin-test/features/sideEffect/Group.feature     | 22 ++++++-------
 gremlin-test/features/sideEffect/Inject.feature    |  7 ++--
 .../tinkerpop/gremlin/features/StepDefinition.java |  5 +--
 .../traversal/step/map/TinkerMergeVertexStep.java  |  1 +
 .../gremlin/tinkergraph/TinkerGraphWorld.java      |  1 +
 20 files changed, 122 insertions(+), 33 deletions(-)

diff --git a/docs/src/dev/io/graphbinary.asciidoc b/docs/src/dev/io/graphbinary.asciidoc
index 065370a..f3b0a82 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/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
index 319db5d..365d83a 100644
--- 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
@@ -60,7 +60,7 @@ public class MergeVertexStep<S> extends FlatMapStep<S, Vertex> implements Mutati
     private Traversal.Admin<S, Map<Object, Object>> onCreateTraversal = null;
     private Traversal.Admin<S, Map<String, Object>> onMatchTraversal = null;
 
-    private CallbackRegistry<Event> callbackRegistry;
+    protected CallbackRegistry<Event> callbackRegistry;
 
     public MergeVertexStep(final Traversal.Admin traversal, final boolean isStart, final Map<Object, Object> searchCreate) {
         this(traversal, isStart, new ConstantTraversal<>(searchCreate));
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 bfa3065..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
@@ -174,7 +174,7 @@ public final class GroovyTranslator implements Translator.ScriptTranslator {
 
         @Override
         protected String getSyntax(final Pick o) {
-            return "TraversalOptionParent.Pick." + o.toString();
+            return "Pick." + o.toString();
         }
 
         @Override
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/graphson/GraphSONModule.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/GraphSONModule.java
index fccab3f..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,6 +21,7 @@ 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;
@@ -174,6 +175,7 @@ abstract class GraphSONModule extends TinkerPopJacksonModule {
                             VertexProperty.Cardinality.class,
                             Column.class,
                             Direction.class,
+                            Merge.class,
                             Operator.class,
                             Order.class,
                             Pop.class,
@@ -257,6 +259,7 @@ abstract class GraphSONModule extends TinkerPopJacksonModule {
             Stream.of(VertexProperty.Cardinality.class,
                     Column.class,
                     Direction.class,
+                    Merge.class,
                     Operator.class,
                     Order.class,
                     Pop.class,
@@ -299,6 +302,7 @@ abstract class GraphSONModule extends TinkerPopJacksonModule {
             Stream.of(VertexProperty.Cardinality.values(),
                     Column.values(),
                     Direction.values(),
+                    Merge.values(),
                     Operator.values(),
                     Order.values(),
                     Pop.values(),
@@ -412,6 +416,7 @@ abstract class GraphSONModule extends TinkerPopJacksonModule {
                             VertexProperty.Cardinality.class,
                             Column.class,
                             Direction.class,
+                            Merge.class,
                             Operator.class,
                             Order.class,
                             Pop.class,
@@ -491,6 +496,7 @@ abstract class GraphSONModule extends TinkerPopJacksonModule {
             Stream.of(VertexProperty.Cardinality.class,
                     Column.class,
                     Direction.class,
+                    Merge.class,
                     Operator.class,
                     Order.class,
                     Pop.class,
@@ -527,6 +533,7 @@ abstract class GraphSONModule extends TinkerPopJacksonModule {
             Stream.of(VertexProperty.Cardinality.values(),
                     Column.values(),
                     Direction.values(),
+                    Merge.values(),
                     Operator.values(),
                     Order.values(),
                     Pop.values(),
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 c609ba0..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.
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 6079579..6af6338 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
@@ -820,7 +820,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()) }], 
@@ -843,7 +842,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/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 a0e91fe..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
@@ -70,7 +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)", v)));
+        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('}', ']')));
diff --git a/gremlin-python/build/generate.groovy b/gremlin-python/build/generate.groovy
index a633cd7..0c6dd42 100644
--- a/gremlin-python/build/generate.groovy
+++ b/gremlin-python/build/generate.groovy
@@ -87,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..560bc30 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
@@ -73,6 +73,7 @@ def add_parameter(step, param_name, param):
         step.context.traversal_params = {}
 
     step.context.traversal_params[param_name] = _convert(param.replace('\\"', '"'), step.context)
+    print(step.context.traversal_params[param_name])
 
 
 @given("the traversal of")
@@ -221,7 +222,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 cache.__contains__(identifier):
+        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 7f4a084..8f9368d 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))], 
@@ -804,7 +804,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()))], 
@@ -827,7 +826,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-test/features/sideEffect/Group.feature b/gremlin-test/features/sideEffect/Group.feature
index b8e7b5c..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\":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(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 ea5eed3..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
@@ -125,7 +125,7 @@ public final class StepDefinition {
         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);
+            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)));
@@ -138,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/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
index 5876879..6771ebd 100644
--- 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
@@ -42,6 +42,7 @@ public class TinkerMergeVertexStep<S> extends MergeVertexStep<S> {
         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
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",

[tinkerpop] 02/03: f

Posted by sp...@apache.org.
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 7273b69d8ebc253c3a6264061df80c766d5e5c43
Author: Stephen Mallette <st...@amazon.com>
AuthorDate: Tue Jan 18 16:22:39 2022 -0500

    f
---
 .../apache/tinkerpop/gremlin/process/ProcessLimitedStandardSuite.java    | 1 +
 1 file changed, 1 insertion(+)

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 e55d7bb..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
@@ -23,6 +23,7 @@ import org.apache.tinkerpop.gremlin.process.traversal.CoreTraversalTest;
 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.map.MatchTest;
 import org.apache.tinkerpop.gremlin.process.traversal.step.map.ProfileTest;
 import org.apache.tinkerpop.gremlin.process.traversal.step.map.WriteTest;