You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tinkerpop.apache.org by sp...@apache.org on 2023/06/21 20:33:10 UTC

[tinkerpop] 01/01: TINKERPOP-2957 allow cardinality to be specified for mergeV()

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

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

commit 11cabf007d7d08dbb56d97117010775adba84e87
Author: Stephen Mallette <st...@amazon.com>
AuthorDate: Mon Jun 19 11:27:12 2023 -0400

    TINKERPOP-2957 allow cardinality to be specified for mergeV()
    
    Provides a nicer way to allow users to control cardinality of property values when using mergeV(). A bit tough to get this in without changes to the serializers but by treating the CardinalityValue as a special form of Bytecode it allowed this change to keep the serializers sacred.
---
 CHANGELOG.asciidoc                                 |   1 +
 docs/src/upgrade/release-3.6.x.asciidoc            |  36 ++++++
 .../tinkerpop/gremlin/jsr223/JavaTranslator.java   |  34 ++++--
 .../grammar/DefaultGremlinBaseVisitor.java         |   5 +
 .../language/grammar/GenericLiteralVisitor.java    |  19 ++-
 .../language/grammar/TraversalMethodVisitor.java   |  10 ++
 .../gremlin/process/traversal/Bytecode.java        |   4 +-
 .../gremlin/process/traversal/Translator.java      |  14 ++-
 .../traversal/dsl/graph/GraphTraversal.java        |  24 ++++
 .../lambda/CardinalityValueTraversal.java          |  82 +++++++++++++
 .../traversal/step/map/MergeVertexStep.java        |  17 ++-
 .../process/traversal/step/util/Parameters.java    |   1 -
 .../traversal/translator/DotNetTranslator.java     |  10 ++
 .../traversal/translator/GolangTranslator.java     |  10 ++
 .../traversal/translator/GroovyTranslator.java     |   9 ++
 .../traversal/translator/JavascriptTranslator.java |   9 ++
 .../traversal/translator/PythonTranslator.java     |   9 ++
 .../gremlin/structure/VertexProperty.java          |  18 ++-
 .../structure/io/binary/GraphBinaryWriter.java     |   4 +-
 .../io/binary/types/TransformSerializer.java       |   2 +-
 .../grammar/GeneralLiteralVisitorTest.java         |  40 +++++++
 .../gremlin/process/traversal/CardinalityTest.java |  68 +++++++++++
 .../traversal/translator/DotNetTranslatorTest.java |   5 +
 .../traversal/translator/GolangTranslatorTest.java |   6 +
 .../traversal/translator/GroovyTranslatorTest.java |   6 +
 .../translator/JavascriptTranslatorTest.java       |   6 +
 .../traversal/translator/PythonTranslatorTest.java |  14 +++
 .../Process/Traversal/CardinalityValue.cs          |  88 ++++++++++++++
 .../Process/Traversal/GraphTraversal.cs            |   9 ++
 .../Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs |   7 ++
 .../Process/Traversal/CardinalityValueTests.cs     |  55 +++++++++
 gremlin-go/driver/cucumber/gremlin.go              |   7 ++
 gremlin-go/driver/traversal.go                     |  34 +++++-
 gremlin-javascript/build/generate.groovy           |   1 +
 .../lib/process/graph-traversal.js                 |  42 ++++++-
 .../gremlin-javascript/test/cucumber/gremlin.js    |   8 ++
 gremlin-language/src/main/antlr4/Gremlin.g4        |   9 +-
 gremlin-python/build/generate.groovy               |   2 +-
 .../python/gremlin_python/process/traversal.py     |  18 +++
 gremlin-python/src/main/python/radish/gremlin.py   |   9 +-
 .../gremlin/test/features/map/MergeVertex.feature  | 129 +++++++++++++++++++++
 41 files changed, 856 insertions(+), 25 deletions(-)

diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc
index 3032e5e4f4..c4bebe0990 100644
--- a/CHANGELOG.asciidoc
+++ b/CHANGELOG.asciidoc
@@ -25,6 +25,7 @@ image::https://raw.githubusercontent.com/apache/tinkerpop/master/docs/static/ima
 
 This release also includes changes from <<release-3-5-7, 3.5.7>>.
 
+* Allowed `mergeV()` to more easily define `Cardinality` values for properties for `onMatch` and `onCreate` options.
 * Added `text/plain` MIME type to the HTTP endpoint to return a Gremlin Console-like representation of the data.
 * Added GraphBinary serialization option to the HTTP endpoint.
 * Fixed bug with `fail` step not working with a `VertexProgram` running on the server.
diff --git a/docs/src/upgrade/release-3.6.x.asciidoc b/docs/src/upgrade/release-3.6.x.asciidoc
index d54ad7a38c..2154a3be6d 100644
--- a/docs/src/upgrade/release-3.6.x.asciidoc
+++ b/docs/src/upgrade/release-3.6.x.asciidoc
@@ -48,6 +48,42 @@ $ curl -H "Accept:text/plain" -X POST -d "{\"gremlin\":\"g.V()\"}" "http://local
 
 See: link:https://issues.apache.org/jira/browse/TINKERPOP-2947[TINKERPOP-2947]
 
+==== mergeV() and Cardinality
+
+The `mergeV()` step makes it much easier to write upsert-like traversals. Of course, if you had a graph that required
+the use of multi-properties, some of the ease of `mergeV()` was lost. It typically meant falling back to traversals
+using `sideEffect()` or similar direct uses of `property()` to allow it to work properly:
+
+[source,groovy]
+----
+g.mergeV([(T.id): '1234']).
+  option(onMatch, sideEffect(property(single,'age', 20).
+                             property(set,'city','miami')).constant([:]))
+----
+
+For this version, `mergeV()` gets two new bits of syntax. First, it is possible to individually define the cardinality
+for each property value in the `Map` for `onCreate` or `onMerge` events. Therefore, the above example could be written
+as:
+
+[source,groovy]
+----
+g.mergeV([(T.id): '1234']).
+  option(onMatch, ['age': single(20), 'city': set('miami')])
+----
+
+The other option available is to provide a default `Cardinality` to the `option()` as follows:
+
+[source,groovy]
+----
+g.mergeV([(T.id): '1234']).
+  option(onMatch, ['age': 20, 'city': set('miami')], single)
+----
+
+In the above example, any property value that does not have its cardinality explicitly defined, will be assumed to be
+the cardinality of the argument specified.
+
+See: link:https://issues.apache.org/jira/browse/TINKERPOP-2957[TINKERPOP-2957]
+
 == TinkerPop 3.6.4
 
 *Release Date: May 12, 2023*
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/JavaTranslator.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/JavaTranslator.java
index 91bc19cc0f..830ca2093e 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/JavaTranslator.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/JavaTranslator.java
@@ -26,6 +26,7 @@ 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.lambda.CardinalityValueTraversal;
 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;
@@ -111,14 +112,33 @@ public final class JavaTranslator<S extends TraversalSource, T extends Traversal
         if (object instanceof Bytecode.Binding)
             return translateObject(((Bytecode.Binding) object).value());
         else if (object instanceof Bytecode) {
-            try {
-                final Traversal.Admin<?, ?> traversal = (Traversal.Admin) this.anonymousTraversalStart.invoke(null);
-                for (final Bytecode.Instruction instruction : ((Bytecode) object).getStepInstructions()) {
-                    invokeMethod(traversal, Traversal.class, instruction.getOperator(), instruction.getArguments());
+            // source based bytecode at this stage of translation could have special meaning, but generally this is
+            // going to spawn a new anonymous traversal.
+            final Bytecode bc = (Bytecode) object;
+            if (!bc.getSourceInstructions().isEmpty()) {
+                // currently, valid source instructions will be singly defined. would be odd to get this error. could
+                // be just bad construction from a language variant if it appears. maybe better as an assertion but
+                // third-party variants might benefit from this error
+                if (bc.getSourceInstructions().size() != 1) {
+                    throw new IllegalStateException("More than one source instruction defined in bytecode");
+                }
+
+                final Bytecode.Instruction inst = bc.getSourceInstructions().get(0);
+                if (inst.getOperator().equals(CardinalityValueTraversal.class.getSimpleName())) {
+                    return CardinalityValueTraversal.from(inst);
+                } else {
+                    throw new IllegalStateException(String.format("Unknown source instruction for %s", inst.getOperator()));
+                }
+            } else {
+                try {
+                    final Traversal.Admin<?, ?> traversal = (Traversal.Admin) this.anonymousTraversalStart.invoke(null);
+                    for (final Bytecode.Instruction instruction : bc.getStepInstructions()) {
+                        invokeMethod(traversal, Traversal.class, instruction.getOperator(), instruction.getArguments());
+                    }
+                    return traversal;
+                } catch (final Throwable e) {
+                    throw new IllegalStateException(e.getMessage());
                 }
-                return traversal;
-            } catch (final Throwable e) {
-                throw new IllegalStateException(e.getMessage());
             }
         } else if (object instanceof TraversalStrategyProxy) {
             final Map<String, Object> map = new HashMap<>();
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/DefaultGremlinBaseVisitor.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/DefaultGremlinBaseVisitor.java
index 39dac7efa5..bbb79de7cd 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/DefaultGremlinBaseVisitor.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/DefaultGremlinBaseVisitor.java
@@ -1432,4 +1432,9 @@ public class DefaultGremlinBaseVisitor<T> extends AbstractParseTreeVisitor<T> im
 	 * {@inheritDoc}
 	 */
 	@Override public T visitTraversalMethod_mergeE_empty(final GremlinParser.TraversalMethod_mergeE_emptyContext ctx) { notImplemented(ctx); return null; }
+	/**
+	 * {@inheritDoc}
+	 */
+	@Override
+	public T visitTraversalMethod_option_Merge_Map_Cardinality(final GremlinParser.TraversalMethod_option_Merge_Map_CardinalityContext ctx) { notImplemented(ctx); return null; }
 }
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 78d144616f..61863b2ae9 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
@@ -508,7 +508,23 @@ public class GenericLiteralVisitor extends DefaultGremlinBaseVisitor<Object> {
      */
     @Override
     public Object visitTraversalCardinality(final GremlinParser.TraversalCardinalityContext ctx) {
-        return TraversalEnumParser.parseTraversalEnumFromContext(VertexProperty.Cardinality.class, ctx);
+        // could be Cardinality.single() the method or single the enum so grab the right child index based on
+        // number of children
+        if (ctx.getChildCount() == 1) {
+            return TraversalEnumParser.parseTraversalEnumFromContext(VertexProperty.Cardinality.class, ctx);
+        } else {
+            final int idx = ctx.getChildCount() == 5 ? 1 : 0;
+            final String specifiedCard = ctx.children.get(idx).getText();
+            if (specifiedCard.endsWith(VertexProperty.Cardinality.single.name()))
+                return VertexProperty.Cardinality.single(visitGenericLiteral(ctx.genericLiteral()));
+            else if (specifiedCard.endsWith(VertexProperty.Cardinality.list.name()))
+                return VertexProperty.Cardinality.list(visitGenericLiteral(ctx.genericLiteral()));
+            else if (specifiedCard.endsWith(VertexProperty.Cardinality.set.name()))
+                return VertexProperty.Cardinality.set(visitGenericLiteral(ctx.genericLiteral()));
+            else
+                throw new GremlinParserException(String.format(
+                        "A Cardinality value not recognized: %s", specifiedCard));
+        }
     }
 
     /**
@@ -611,5 +627,4 @@ public class GenericLiteralVisitor extends DefaultGremlinBaseVisitor<Object> {
         }
         return result;
     }
-
 }
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 111732cd07..adde508c96 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
@@ -1703,6 +1703,16 @@ public class TraversalMethodVisitor extends TraversalRootVisitor<GraphTraversal>
                 antlr.tvisitor.visitNestedTraversal(ctx.nestedTraversal()));
     }
 
+    @Override
+    public Traversal visitTraversalMethod_option_Merge_Map_Cardinality(final GremlinParser.TraversalMethod_option_Merge_Map_CardinalityContext ctx) {
+        if (ctx.nullLiteral() != null) {
+            return this.graphTraversal.option(TraversalEnumParser.parseTraversalEnumFromContext(Merge.class, ctx.traversalMerge()), (Map) null);
+        }
+
+        return graphTraversal.option(TraversalEnumParser.parseTraversalEnumFromContext(Merge.class, ctx.traversalMerge()),
+                (Map) new GenericLiteralVisitor(antlr).visitGenericLiteralMap(ctx.genericLiteralMap()));
+    }
+
     public GraphTraversal[] getNestedTraversalList(final GremlinParser.NestedTraversalListContext ctx) {
         return ctx.nestedTraversalExpr().nestedTraversal()
                 .stream()
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Bytecode.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Bytecode.java
index dc8eca3add..5b88cea5fc 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Bytecode.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Bytecode.java
@@ -47,7 +47,7 @@ import java.util.Set;
  *
  * @author Marko A. Rodriguez (http://markorodriguez.com)
  */
-public final class Bytecode implements Cloneable, Serializable {
+public class Bytecode implements Cloneable, Serializable {
 
     private static final Object[] EMPTY_ARRAY = new Object[]{};
 
@@ -56,7 +56,7 @@ public final class Bytecode implements Cloneable, Serializable {
 
     public Bytecode() {}
 
-    Bytecode(final String sourceName, final Object... arguments) {
+    public Bytecode(final String sourceName, final Object... arguments) {
         this.sourceInstructions.add(new Instruction(sourceName, flattenArguments(arguments)));
     }
 
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 0be7555797..120176821a 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,6 +19,7 @@
 
 package org.apache.tinkerpop.gremlin.process.traversal;
 
+import org.apache.tinkerpop.gremlin.process.traversal.lambda.CardinalityValueTraversal;
 import org.apache.tinkerpop.gremlin.process.traversal.strategy.TraversalStrategyProxy;
 import org.apache.tinkerpop.gremlin.structure.Edge;
 import org.apache.tinkerpop.gremlin.structure.Vertex;
@@ -231,6 +232,11 @@ public interface Translator<S, T> {
              */
             protected abstract Script produceScript(final P<?> p);
 
+            /**
+             * Take the {@link Bytecode} and write the syntax for it directly to the member {@link #script} variable.
+             */
+            protected abstract Script produceCardinalityValue(final Bytecode o);
+
             /**
              *  For each operator argument, if withParameters set true, try parametrization as follows:
              *
@@ -272,7 +278,13 @@ public interface Translator<S, T> {
                 if (object instanceof Bytecode.Binding) {
                     return script.getBoundKeyOrAssign(withParameters, ((Bytecode.Binding) object).variable());
                 } else if (object instanceof Bytecode) {
-                    return produceScript(getAnonymousTraversalPrefix(), (Bytecode) object);
+                    final Bytecode bc = (Bytecode) object;
+                    if (bc.getSourceInstructions().size() == 1 &&
+                            bc.getSourceInstructions().get(0).getOperator().equals(CardinalityValueTraversal.class.getSimpleName())) {
+                        return produceCardinalityValue(bc);
+                    } else {
+                        return produceScript(getAnonymousTraversalPrefix(), bc);
+                    }
                 } else if (object instanceof Traversal) {
                     return convertToScript(((Traversal) object).asAdmin().getBytecode());
                 } else if (object instanceof String) {
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 7fc1b3995a..c6bd6ea811 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
@@ -36,6 +36,7 @@ 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.CardinalityValueTraversal;
 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;
@@ -3271,6 +3272,29 @@ public interface GraphTraversal<S, E> extends Traversal<S, E> {
         return this;
     }
 
+    /**
+     * This is a step modulator to a {@link TraversalOptionParent} like {@code choose()} or {@code mergeV()} where the
+     * provided argument associated to the {@code token} is applied according to the semantics of the step. Please see
+     * the documentation of such steps to understand the usage context.
+     *
+     * @param m Provides a {@code Map} as the option which is the same as doing {@code constant(m)}.
+     * @return the traversal with the modulated step
+     * @see <a href="http://tinkerpop.apache.org/docs/${project.version}/reference/#mergev-step" target="_blank">Reference Documentation - MergeV Step</a>
+     * @see <a href="http://tinkerpop.apache.org/docs/${project.version}/reference/#mergee-step" target="_blank">Reference Documentation - MergeE Step</a>
+     * @since 3.6.0
+     */
+    public default <M, E2> GraphTraversal<S, E> option(final Merge merge, final Map<Object, Object> m, final VertexProperty.Cardinality cardinality) {
+        this.asAdmin().getBytecode().addStep(Symbols.option, merge, m, cardinality);
+        // do explicit cardinality for every single pair in the map
+        for (Object k : m.keySet()) {
+            final Object o = m.get(k);
+            if (!(o instanceof CardinalityValueTraversal))
+                m.put(k, new CardinalityValueTraversal(cardinality, o));
+        }
+        ((TraversalOptionParent<M, E, E2>) this.asAdmin().getEndStep()).addChildOption((M) merge, (Traversal.Admin<E, E2>) new ConstantTraversal<>(m).asAdmin());
+        return this;
+    }
+
     /**
      * This step modifies {@link #choose(Function)} to specifies the available choices that might be executed.
      *
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/lambda/CardinalityValueTraversal.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/lambda/CardinalityValueTraversal.java
new file mode 100644
index 0000000000..8c13e0649b
--- /dev/null
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/lambda/CardinalityValueTraversal.java
@@ -0,0 +1,82 @@
+/*
+ * 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.lambda;
+
+import org.apache.tinkerpop.gremlin.process.traversal.Bytecode;
+import org.apache.tinkerpop.gremlin.structure.VertexProperty;
+
+import java.util.Objects;
+
+public final class CardinalityValueTraversal extends AbstractLambdaTraversal {
+
+    private final VertexProperty.Cardinality cardinality;
+
+    private final Object value;
+
+    private final Bytecode bytecode;
+
+    public CardinalityValueTraversal(final VertexProperty.Cardinality cardinality, final Object value) {
+        this.cardinality = cardinality;
+        this.value = value;
+        this.bytecode = new Bytecode(CardinalityValueTraversal.class.getSimpleName(), cardinality.name(), value);
+    }
+
+    public static CardinalityValueTraversal from(final Bytecode.Instruction inst) {
+        return new CardinalityValueTraversal(VertexProperty.Cardinality.valueOf(inst.getArguments()[0].toString()),
+                inst.getArguments()[1]);
+    }
+
+    @Override
+    public Bytecode getBytecode() {
+        return this.bytecode;
+    }
+
+    public VertexProperty.Cardinality getCardinality() {
+        return cardinality;
+    }
+
+    public Object getValue() {
+        return value;
+    }
+
+    @Override
+    public String toString() {
+        return "[" + cardinality + ", " + value + "]";
+    }
+
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o) return true;
+        if (!(o instanceof CardinalityValueTraversal)) return false;
+        if (!super.equals(o)) return false;
+
+        final CardinalityValueTraversal that = (CardinalityValueTraversal) o;
+
+        if (cardinality != that.cardinality) return false;
+        return Objects.equals(value, that.value);
+    }
+
+    @Override
+    public int hashCode() {
+        int result = super.hashCode();
+        result = 31 * result + cardinality.hashCode();
+        result = 31 * result + (value != null ? value.hashCode() : 0);
+        return result;
+    }
+}
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 1408cfbfec..ed1e26a34a 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
@@ -29,12 +29,15 @@ import java.util.stream.Stream;
 import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
 import org.apache.tinkerpop.gremlin.process.traversal.Traverser;
 import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal;
+import org.apache.tinkerpop.gremlin.process.traversal.lambda.CardinalityValueTraversal;
+import org.apache.tinkerpop.gremlin.process.traversal.step.sideEffect.AddPropertyStep;
 import org.apache.tinkerpop.gremlin.process.traversal.step.util.event.Event;
 import org.apache.tinkerpop.gremlin.process.traversal.strategy.decoration.EventStrategy;
 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.CloseableIterator;
 import org.apache.tinkerpop.gremlin.util.iterator.IteratorUtils;
 
@@ -96,18 +99,28 @@ public class MergeVertexStep<S> extends MergeStep<S, Vertex, Map> {
                 validateMapInput(onMatchMap, true);
 
                 onMatchMap.forEach((key, value) -> {
+                    Object val = value;
+                    VertexProperty.Cardinality card = graph.features().vertex().getCardinality(key);
+
+                    // a value can be a traversal in the case where the user specifies the cardinality for the value.
+                    if (value instanceof CardinalityValueTraversal) {
+                        final CardinalityValueTraversal cardinalityValueTraversal =  (CardinalityValueTraversal) value;
+                        card = cardinalityValueTraversal.getCardinality();
+                        val = cardinalityValueTraversal.getValue();
+                    }
+
                     // trigger callbacks for eventing - in this case, it's a VertexPropertyChangedEvent. if there's no
                     // registry/callbacks then just set the property
                     if (this.callbackRegistry != null && !callbackRegistry.getCallbacks().isEmpty()) {
                         final EventStrategy eventStrategy = getTraversal().getStrategies().getStrategy(EventStrategy.class).get();
                         final Property<?> p = v.property(key);
                         final Property<Object> oldValue = p.isPresent() ? eventStrategy.detach(v.property(key)) : null;
-                        final Event.VertexPropertyChangedEvent vpce = new Event.VertexPropertyChangedEvent(eventStrategy.detach(v), oldValue, value);
+                        final Event.VertexPropertyChangedEvent vpce = new Event.VertexPropertyChangedEvent(eventStrategy.detach(v), oldValue, val);
                         this.callbackRegistry.getCallbacks().forEach(c -> c.accept(vpce));
                     }
 
                     // try to detect proper cardinality for the key according to the graph
-                    v.property(graph.features().vertex().getCardinality(key), key, value);
+                    v.property(card, key, val);
                 });
             });
         }
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/util/Parameters.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/util/Parameters.java
index 55d69367df..d1654a344e 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/util/Parameters.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/util/Parameters.java
@@ -135,7 +135,6 @@ public class Parameters implements Cloneable, Serializable {
     public <E> List<E> get(final Object key, final Supplier<E> defaultValue) {
         final List<E> list = (List<E>) this.parameters.get(key);
         return (null == list) ? (null == defaultValue ? Collections.emptyList() : Collections.singletonList(defaultValue.get())) : list;
-
     }
 
     /**
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 20ee8ec65c..5e37147b25 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
@@ -192,6 +192,16 @@ public final class DotNetTranslator implements Translator.ScriptTranslator {
             return o.toString();
         }
 
+        @Override
+        protected Script produceCardinalityValue(final Bytecode o) {
+            final Bytecode.Instruction inst = o.getSourceInstructions().get(0);
+            final String card = inst.getArguments()[0].toString();
+            script.append("CardinalityValue." + card.substring(0, 1).toUpperCase() + card.substring(1) + "(");
+            convertToScript(inst.getArguments()[1]);
+            script.append(")");
+            return script;
+        }
+
         @Override
         protected Script produceScript(final Set<?> o) {
             final Iterator<?> iterator = o.iterator();
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/translator/GolangTranslator.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/translator/GolangTranslator.java
index ee1c38cbc0..dece576e72 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/translator/GolangTranslator.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/translator/GolangTranslator.java
@@ -181,6 +181,16 @@ public final class GolangTranslator implements Translator.ScriptTranslator {
             return GO_PACKAGE_NAME + "Pick." + resolveSymbol(o.toString());
         }
 
+        @Override
+        protected Script produceCardinalityValue(final Bytecode o) {
+            final Bytecode.Instruction inst = o.getSourceInstructions().get(0);
+            final String card = inst.getArguments()[0].toString();
+            script.append(GO_PACKAGE_NAME + "CardinalityValue." + card.substring(0, 1).toUpperCase() + card.substring(1) + "(");
+            convertToScript(inst.getArguments()[1]);
+            script.append(")");
+            return script;
+        }
+
         @Override
         protected Script produceScript(final Set<?> o) {
             final Iterator<?> iterator = o.iterator();
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 7fea7eb829..ad5498b003 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
@@ -206,6 +206,15 @@ public final class GroovyTranslator implements Translator.ScriptTranslator {
             return o.toString();
         }
 
+        @Override
+        protected Script produceCardinalityValue(final Bytecode o) {
+            final Bytecode.Instruction inst = o.getSourceInstructions().get(0);
+            script.append("VertexProperty.Cardinality." + inst.getArguments()[0] + "(");
+            convertToScript(inst.getArguments()[1]);
+            script.append(")");
+            return script;
+        }
+
         @Override
         protected Script produceScript(final Set<?> o) {
             return produceScript(new ArrayList<>(o)).append(" as Set");
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 c03fd01ebb..1ebf25a34c 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
@@ -187,6 +187,15 @@ public final class JavascriptTranslator implements Translator.ScriptTranslator {
             return o.toString();
         }
 
+        @Override
+        protected Script produceCardinalityValue(final Bytecode o) {
+            final Bytecode.Instruction inst = o.getSourceInstructions().get(0);
+            script.append("CardinalityValue." + inst.getArguments()[0] + "(");
+            convertToScript(inst.getArguments()[1]);
+            script.append(")");
+            return script;
+        }
+
         @Override
         protected Script produceScript(final Set<?> o) {
             return produceScript(new ArrayList<>(o));
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 41f8382643..e26b02f8ad 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
@@ -362,6 +362,15 @@ public final class PythonTranslator implements Translator.ScriptTranslator {
             return script;
         }
 
+        @Override
+        protected Script produceCardinalityValue(final Bytecode o) {
+            final Bytecode.Instruction inst = o.getSourceInstructions().get(0);
+            script.append("CardinalityValue." + resolveSymbol(inst.getArguments()[0].toString()) + "(");
+            convertToScript(inst.getArguments()[1]);
+            script.append(")");
+            return script;
+        }
+
         protected String resolveSymbol(final String methodName) {
             return SymbolHelper.toPython(methodName);
         }
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/VertexProperty.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/VertexProperty.java
index 4460f7d050..a1f45c145d 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/VertexProperty.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/VertexProperty.java
@@ -18,6 +18,10 @@
  */
 package org.apache.tinkerpop.gremlin.structure;
 
+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.lambda.CardinalityValueTraversal;
 import org.apache.tinkerpop.gremlin.structure.util.empty.EmptyVertexProperty;
 
 import java.util.Iterator;
@@ -40,7 +44,19 @@ public interface VertexProperty<V> extends Property<V>, Element {
     public static final String DEFAULT_LABEL = "vertexProperty";
 
     public enum Cardinality {
-        single, list, set
+        single, list, set;
+
+        public static CardinalityValueTraversal single(final Object value) {
+            return new CardinalityValueTraversal(single, value);
+        }
+
+        public static CardinalityValueTraversal list(final Object value) {
+            return new CardinalityValueTraversal(list, value);
+        }
+
+        public static CardinalityValueTraversal set(final Object value) {
+            return new CardinalityValueTraversal(set, value);
+        }
     }
 
     /**
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/binary/GraphBinaryWriter.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/binary/GraphBinaryWriter.java
index ab0b094329..80b1df0b1b 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/binary/GraphBinaryWriter.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/binary/GraphBinaryWriter.java
@@ -102,7 +102,7 @@ public class GraphBinaryWriter {
         if (serializer instanceof TransformSerializer) {
             // For historical reasons, there are types that need to be transformed into another type
             // before serialization, e.g., Map.Entry
-            TransformSerializer<T> transformSerializer = (TransformSerializer<T>) serializer;
+            final TransformSerializer<T> transformSerializer = (TransformSerializer<T>) serializer;
             write(transformSerializer.transform(value), buffer);
             return;
         }
@@ -118,7 +118,7 @@ public class GraphBinaryWriter {
      * <p>Note that for simple types, the provided information will be <code>null</code>.</p>
      */
     public <T> void writeFullyQualifiedNull(final Class<T> objectClass, Buffer buffer, final Object information) throws IOException {
-        TypeSerializer<T> serializer = registry.getSerializer(objectClass);
+        final TypeSerializer<T> serializer = registry.getSerializer(objectClass);
         serializer.write(null, buffer, this);
     }
 
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/binary/types/TransformSerializer.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/binary/types/TransformSerializer.java
index 97ccbbaea3..7275b1cce6 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/binary/types/TransformSerializer.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/binary/types/TransformSerializer.java
@@ -24,5 +24,5 @@ import org.apache.tinkerpop.gremlin.structure.io.binary.TypeSerializer;
  * Represents a special TypeSerializer placeholder that transforms the value into another before serializing it.
  */
 public interface TransformSerializer<T> extends TypeSerializer<T> {
-    Object transform(T value);
+    Object transform(final T value);
 }
diff --git a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/language/grammar/GeneralLiteralVisitorTest.java b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/language/grammar/GeneralLiteralVisitorTest.java
index 1350b03202..89b6fca15a 100644
--- a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/language/grammar/GeneralLiteralVisitorTest.java
+++ b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/language/grammar/GeneralLiteralVisitorTest.java
@@ -20,6 +20,8 @@ package org.apache.tinkerpop.gremlin.language.grammar;
 
 import org.antlr.v4.runtime.CharStreams;
 import org.antlr.v4.runtime.CommonTokenStream;
+import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal;
+import org.apache.tinkerpop.gremlin.structure.VertexProperty;
 import org.junit.Assert;
 import org.junit.Test;
 import org.junit.experimental.runners.Enclosed;
@@ -673,4 +675,42 @@ public class GeneralLiteralVisitorTest {
             assertEquals(Double.NEGATIVE_INFINITY, GenericLiteralVisitor.instance().visitInfLiteral(ctx));
         }
     }
+
+    @RunWith(Parameterized.class)
+    public static class CardinalityTest {
+        @Parameterized.Parameter(value = 0)
+        public String script;
+
+        @Parameterized.Parameter(value = 1)
+        public Object expected;
+
+        @Parameterized.Parameters()
+        public static Iterable<Object[]> generateTestParameters() {
+            return Arrays.asList(new Object[][]{
+                    {"single(\"test\")", VertexProperty.Cardinality.single("test")},
+                    {"list(\"test\")", VertexProperty.Cardinality.list("test")},
+                    {"set(\"test\")", VertexProperty.Cardinality.set("test")},
+                    {"Cardinality.single(\"test\")", VertexProperty.Cardinality.single("test")},
+                    {"Cardinality.list(\"test\")", VertexProperty.Cardinality.list("test")},
+                    {"Cardinality.set(\"test\")", VertexProperty.Cardinality.set("test")},
+                    {"single(1l)", VertexProperty.Cardinality.single(1L)},
+                    {"list(1l)", VertexProperty.Cardinality.list(1L)},
+                    {"set(1l)", VertexProperty.Cardinality.set(1L)},
+                    {"Cardinality.single", VertexProperty.Cardinality.single},
+                    {"Cardinality.list", VertexProperty.Cardinality.list},
+                    {"Cardinality.set", VertexProperty.Cardinality.set},
+                    {"single", VertexProperty.Cardinality.single},
+                    {"list", VertexProperty.Cardinality.list},
+                    {"set", VertexProperty.Cardinality.set},
+            });
+        }
+
+        @Test
+        public void shouldParse() {
+            final GremlinLexer lexer = new GremlinLexer(CharStreams.fromString(script));
+            final GremlinParser parser = new GremlinParser(new CommonTokenStream(lexer));
+            final GremlinParser.TraversalCardinalityContext ctx = parser.traversalCardinality();
+            assertEquals(expected, GenericLiteralVisitor.instance().visitTraversalCardinality(ctx));
+        }
+    }
 }
diff --git a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/CardinalityTest.java b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/CardinalityTest.java
new file mode 100644
index 0000000000..b5c578e2f4
--- /dev/null
+++ b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/CardinalityTest.java
@@ -0,0 +1,68 @@
+/*
+ * 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.GraphTraversal;
+import org.apache.tinkerpop.gremlin.process.traversal.lambda.CardinalityValueTraversal;
+import org.apache.tinkerpop.gremlin.process.traversal.step.sideEffect.AddPropertyStep;
+import org.apache.tinkerpop.gremlin.structure.T;
+import org.apache.tinkerpop.gremlin.structure.VertexProperty;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+public class CardinalityTest {
+
+    @Test
+    public void shouldCreateSingle() {
+        final CardinalityValueTraversal t = VertexProperty.Cardinality.single("test");
+        assertEquals("test", t.getValue());
+        assertEquals(VertexProperty.Cardinality.single, t.getCardinality());
+    }
+
+    @Test
+    public void shouldCreateSet() {
+        final CardinalityValueTraversal t = VertexProperty.Cardinality.set("test");
+        assertEquals("test", t.getValue());
+        assertEquals(VertexProperty.Cardinality.set, t.getCardinality());
+    }
+
+    @Test
+    public void shouldCreateList() {
+        final CardinalityValueTraversal t = VertexProperty.Cardinality.list("test");
+        assertEquals("test", t.getValue());
+        assertEquals(VertexProperty.Cardinality.list, t.getCardinality());
+    }
+
+    @Test
+    public void shouldBeEqual() {
+        assertEquals(VertexProperty.Cardinality.single("test"), VertexProperty.Cardinality.single("test"));
+        assertEquals(VertexProperty.Cardinality.single(1), VertexProperty.Cardinality.single(1));
+        assertEquals(VertexProperty.Cardinality.single(null), VertexProperty.Cardinality.single(null));
+    }
+
+    @Test
+    public void shouldNotBeEqual() {
+        assertNotEquals(VertexProperty.Cardinality.single(100), VertexProperty.Cardinality.single("testing"));
+        assertNotEquals(VertexProperty.Cardinality.single("test"), VertexProperty.Cardinality.single("testing"));
+        assertNotEquals(VertexProperty.Cardinality.single(100), VertexProperty.Cardinality.single(1));
+        assertNotEquals(VertexProperty.Cardinality.single("null"), VertexProperty.Cardinality.single(null));
+    }
+}
diff --git a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/translator/DotNetTranslatorTest.java b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/translator/DotNetTranslatorTest.java
index 28256f79fe..b3b7f1a855 100644
--- a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/translator/DotNetTranslatorTest.java
+++ b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/translator/DotNetTranslatorTest.java
@@ -199,6 +199,11 @@ public class DotNetTranslatorTest {
                 g.V().hasLabel("person").property(VertexProperty.Cardinality.single, "name", null)).getScript());
     }
 
+    @Test
+    public void shouldTranslateCardinalityValue() {
+        assertTranslation("CardinalityValue.Set(\"test\")", VertexProperty.Cardinality.set("test"));
+    }
+
     @Test
     public void shouldTranslateHasNull() {
         String script = translator.translate(g.V().has("k", (Object) null).asAdmin().getBytecode()).getScript();
diff --git a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/translator/GolangTranslatorTest.java b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/translator/GolangTranslatorTest.java
index 19b37b79eb..6ad0a95671 100644
--- a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/translator/GolangTranslatorTest.java
+++ b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/translator/GolangTranslatorTest.java
@@ -63,6 +63,12 @@ public class GolangTranslatorTest {
         assertEquals("g.AddV(\"person\").Property(gremlingo.Cardinality.List, \"name\", \"marko\")", gremlinAsGo);
     }
 
+    @Test
+    public void shouldTranslateCardinalityValue() {
+        assertEquals("g.Inject(gremlingo.CardinalityValue.Set(\"test\"))", translator.translate(
+                g.inject(VertexProperty.Cardinality.set("test")).asAdmin().getBytecode()).getScript());
+    }
+
     @Test
     public void shouldTranslateMultilineStrings() {
         final String gremlinAsGo = translator.translate(
diff --git a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/translator/GroovyTranslatorTest.java b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/translator/GroovyTranslatorTest.java
index b9910fbbc8..499e76b4b4 100644
--- a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/translator/GroovyTranslatorTest.java
+++ b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/translator/GroovyTranslatorTest.java
@@ -34,6 +34,7 @@ import org.apache.tinkerpop.gremlin.structure.Column;
 import org.apache.tinkerpop.gremlin.structure.Direction;
 import org.apache.tinkerpop.gremlin.structure.Edge;
 import org.apache.tinkerpop.gremlin.structure.Vertex;
+import org.apache.tinkerpop.gremlin.structure.VertexProperty;
 import org.apache.tinkerpop.gremlin.structure.util.detached.DetachedEdge;
 import org.apache.tinkerpop.gremlin.structure.util.detached.DetachedVertex;
 import org.apache.tinkerpop.gremlin.structure.util.empty.EmptyGraph;
@@ -160,6 +161,11 @@ public class GroovyTranslatorTest {
         assertTranslation("Column.keys", Column.keys);
     }
 
+    @Test
+    public void shouldTranslateCardinalityValue() {
+        assertTranslation("VertexProperty.Cardinality.set(\"test\")", VertexProperty.Cardinality.set("test"));
+    }
+
     @Test
     public void shouldTranslateDateUsingLanguageTypeTranslator() {
         final Translator.ScriptTranslator t = GroovyTranslator.of("g",
diff --git a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/translator/JavascriptTranslatorTest.java b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/translator/JavascriptTranslatorTest.java
index 98de2f988c..ba99d44969 100644
--- a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/translator/JavascriptTranslatorTest.java
+++ b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/translator/JavascriptTranslatorTest.java
@@ -32,6 +32,7 @@ import org.apache.tinkerpop.gremlin.structure.Column;
 import org.apache.tinkerpop.gremlin.structure.Direction;
 import org.apache.tinkerpop.gremlin.structure.Edge;
 import org.apache.tinkerpop.gremlin.structure.Vertex;
+import org.apache.tinkerpop.gremlin.structure.VertexProperty;
 import org.apache.tinkerpop.gremlin.structure.util.detached.DetachedEdge;
 import org.apache.tinkerpop.gremlin.structure.util.detached.DetachedVertex;
 import org.apache.tinkerpop.gremlin.structure.util.empty.EmptyGraph;
@@ -120,6 +121,11 @@ public class JavascriptTranslatorTest {
         assertTranslation("Scope.local", Scope.local);
     }
 
+    @Test
+    public void shouldTranslateCardinalityValue() {
+        assertTranslation("CardinalityValue.set(\"test\")", VertexProperty.Cardinality.set("test"));
+    }
+
     @Test
     public void shouldHaveValidToString() {
         assertEquals("translator[h:gremlin-javascript]", JavascriptTranslator.of("h").toString());
diff --git a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/translator/PythonTranslatorTest.java b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/translator/PythonTranslatorTest.java
index 9fee3ed4a9..ebbca186f8 100644
--- a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/translator/PythonTranslatorTest.java
+++ b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/translator/PythonTranslatorTest.java
@@ -19,11 +19,13 @@
 package org.apache.tinkerpop.gremlin.process.traversal.translator;
 
 import org.apache.tinkerpop.gremlin.process.traversal.Bytecode;
+import org.apache.tinkerpop.gremlin.process.traversal.Merge;
 import org.apache.tinkerpop.gremlin.process.traversal.TextP;
 import org.apache.tinkerpop.gremlin.process.traversal.Translator;
 import org.apache.tinkerpop.gremlin.process.traversal.Traverser;
 import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource;
 import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__;
+import org.apache.tinkerpop.gremlin.process.traversal.lambda.CardinalityValueTraversal;
 import org.apache.tinkerpop.gremlin.process.traversal.strategy.decoration.SeedStrategy;
 import org.apache.tinkerpop.gremlin.process.traversal.strategy.decoration.SubgraphStrategy;
 import org.apache.tinkerpop.gremlin.process.traversal.strategy.verification.ReadOnlyStrategy;
@@ -32,6 +34,9 @@ import org.apache.tinkerpop.gremlin.structure.util.empty.EmptyGraph;
 import org.apache.tinkerpop.gremlin.util.function.Lambda;
 import org.junit.Test;
 
+import java.util.HashMap;
+import java.util.Map;
+
 import static org.apache.tinkerpop.gremlin.process.traversal.AnonymousTraversalSource.traversal;
 import static org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__.hasLabel;
 import static org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__.inE;
@@ -62,6 +67,15 @@ public class PythonTranslatorTest {
         assertEquals("g.addV('person').property(Cardinality.list_,'name','marko')", gremlinAsPython);
     }
 
+    @Test
+    public void shouldTranslateCardinalityValue() {
+        final Map<Object, Object> m = new HashMap<>();
+        m.put("name", VertexProperty.Cardinality.set("marko"));
+        final String gremlinAsPython = translator.translate(
+                g.mergeV(new HashMap<>()).option(Merge.onMatch, m).asAdmin().getBytecode()).getScript();
+        assertEquals("g.merge_v({}).option(Merge.on_match,{'name':CardinalityValue.set_('marko')})", gremlinAsPython);
+    }
+
     @Test
     public void shouldTranslateMultilineStrings() {
         final String gremlinAsPython = translator.translate(
diff --git a/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/CardinalityValue.cs b/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/CardinalityValue.cs
new file mode 100644
index 0000000000..efecabddae
--- /dev/null
+++ b/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/CardinalityValue.cs
@@ -0,0 +1,88 @@
+#region License
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#endregion
+
+using System.Collections.Generic;
+using System.Threading;
+
+namespace Gremlin.Net.Process.Traversal
+{
+    /// <summary>
+    ///     Holds a property value with the associated <see cref="Traversal.Cardinality" />.
+    /// </summary>
+    public class CardinalityValue : Bytecode
+    {
+        /// <summary>
+        ///     Gets the <see cref="Traversal.Cardinality" /> for the value.
+        /// </summary>
+        public Cardinality Cardinality
+        {
+            get
+            {
+                return this.SourceInstructions[0].Arguments[0];
+            }
+        }
+
+        /// <summary>
+        ///     Gets the value.
+        /// </summary>
+        public object Value
+        {
+            get
+            {
+                return this.SourceInstructions[0].Arguments[1];
+            }
+        }
+
+        /// <summary>
+        ///     Initializes a new instance of the <see cref="CardinalityValue" /> class.
+        /// </summary>
+        public CardinalityValue(Cardinality card, object val)
+        {
+            this.AddSource("CardinalityValueTraversal", card, val);
+        }
+
+        /// <summary>
+        ///     Creates a new <see cref="CardinalityValue" /> with a particular value and Single <see cref="Cardinality" />.
+        /// </summary>
+        public static CardinalityValue Single(object value)
+        {
+            return new CardinalityValue(Cardinality.Single, value);
+        }
+
+        /// <summary>
+        ///     Creates a new <see cref="CardinalityValue" /> with a particular value and Set <see cref="Cardinality" />.
+        /// </summary>
+        public static CardinalityValue Set(object value)
+        {
+            return new CardinalityValue(Cardinality.Set, value);
+        }
+
+        /// <summary>
+        ///     Creates a new <see cref="CardinalityValue" /> with a particular value and List <see cref="Cardinality" />.
+        /// </summary>
+        public static CardinalityValue List(object value)
+        {
+            return new CardinalityValue(Cardinality.List, value);
+        }
+    }
+}
\ No newline at end of file
diff --git a/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/GraphTraversal.cs b/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/GraphTraversal.cs
index c6cf798f9a..82aa656ced 100644
--- a/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/GraphTraversal.cs
+++ b/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/GraphTraversal.cs
@@ -1301,6 +1301,15 @@ namespace Gremlin.Net.Process.Traversal
             return Wrap<S, E>(this);
         }
 
+        /// <summary>
+        ///     Adds the option step to this <see cref="GraphTraversal{SType, EType}" />.
+        /// </summary>
+        public GraphTraversal<S, E> Option (object pickToken, IDictionary<object,object> traversalOption, Cardinality cardinality)
+        {
+            Bytecode.AddStep("option", pickToken, traversalOption, cardinality);
+            return Wrap<S, E>(this);
+        }
+
         /// <summary>
         ///     Adds the option step to this <see cref="GraphTraversal{SType, EType}" />.
         /// </summary>
diff --git a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs
index 6580b1908c..0748899b6c 100644
--- a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs
+++ b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs
@@ -707,6 +707,13 @@ namespace Gremlin.Net.IntegrationTest.Gherkin
                {"g_mergeV_hidden_id_key_onMatch_matched_prohibited", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.AddV("vertex"), (g,p) =>g.MergeV((IDictionary<object,object>) new Dictionary<object,object> {}).Option(Merge.OnMatch, (IDictionary<object,object>) p["xx1"])}}, 
                {"g_mergeV_hidden_label_key_matched_onMatch_matched_prohibited", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.AddV("vertex"), (g,p) =>g.MergeV((IDictionary<object,object>) new Dictionary<object,object> {}).Option(Merge.OnMatch, (IDictionary<object,object>) p["xx1"])}}, 
                {"g_mergeV_hidden_label_value_onMatch_matched_prohibited", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.AddV("vertex"), (g,p) =>g.MergeV((IDictionary<object,object>) new Dictionary<object,object> {}).Option(Merge.OnMatch, (IDictionary<object,object>) p["xx1"])}}, 
+               {"g_mergeVXname_markoX_optionXonMatch_age_listX33XX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.AddV("person").Property("name","marko").Property(Cardinality.List,"age",29).Property(Cardinality.List,"age",31).Property(Cardinality.List,"age",32), (g,p) =>g.MergeV((IDictionary<object,object>) new Dictionary<object,object> {{"name", "marko"}}).Option(Merge.OnMatch, (IDictionary<object,object>) new Dictionary<object,object> {{"age" [...]
+               {"g_mergeVXname_markoX_optionXonMatch_age_setX33XX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.AddV("person").Property("name","marko").Property(Cardinality.List,"age",29).Property(Cardinality.List,"age",31).Property(Cardinality.List,"age",32), (g,p) =>g.MergeV((IDictionary<object,object>) new Dictionary<object,object> {{"name", "marko"}}).Option(Merge.OnMatch, (IDictionary<object,object>) new Dictionary<object,object> {{"age", [...]
+               {"g_mergeVXname_markoX_optionXonMatch_age_setX31XX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.AddV("person").Property("name","marko").Property(Cardinality.List,"age",29).Property(Cardinality.List,"age",31).Property(Cardinality.List,"age",32), (g,p) =>g.MergeV((IDictionary<object,object>) new Dictionary<object,object> {{"name", "marko"}}).Option(Merge.OnMatch, (IDictionary<object,object>) new Dictionary<object,object> {{"age", [...]
+               {"g_mergeVXname_markoX_optionXonMatch_age_singleX33XX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.AddV("person").Property("name","marko").Property(Cardinality.List,"age",29).Property(Cardinality.List,"age",31).Property(Cardinality.List,"age",32), (g,p) =>g.MergeV((IDictionary<object,object>) new Dictionary<object,object> {{"name", "marko"}}).Option(Merge.OnMatch, (IDictionary<object,object>) new Dictionary<object,object> {{"ag [...]
+               {"g_mergeVXname_markoX_optionXonMatch_age_33_singleX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.AddV("person").Property("name","marko").Property(Cardinality.List,"age",29).Property(Cardinality.List,"age",31).Property(Cardinality.List,"age",32), (g,p) =>g.MergeV((IDictionary<object,object>) new Dictionary<object,object> {{"name", "marko"}}).Option(Merge.OnMatch,new Dictionary<object,object> {{"age", 33}},Cardinality.Single), ( [...]
+               {"g_mergeVXname_markoX_optionXonMatch_name_allen_age_setX31X_singleX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.AddV("person").Property("name","marko").Property(Cardinality.List,"age",29).Property(Cardinality.List,"age",31).Property(Cardinality.List,"age",32), (g,p) =>g.MergeV((IDictionary<object,object>) new Dictionary<object,object> {{"name", "marko"}}).Option(Merge.OnMatch,new Dictionary<object,object> {{"name", "allen"},  [...]
+               {"g_mergeVXname_markoX_optionXonMatch_name_allen_age_singleX31X_singleX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.AddV("person").Property("name","marko").Property(Cardinality.List,"age",29).Property(Cardinality.List,"age",31).Property(Cardinality.List,"age",32), (g,p) =>g.MergeV((IDictionary<object,object>) new Dictionary<object,object> {{"name", "marko"}}).Option(Merge.OnMatch,new Dictionary<object,object> {{"name", "allen" [...]
                {"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-dotnet/test/Gremlin.Net.UnitTest/Process/Traversal/CardinalityValueTests.cs b/gremlin-dotnet/test/Gremlin.Net.UnitTest/Process/Traversal/CardinalityValueTests.cs
new file mode 100644
index 0000000000..c30aaf3950
--- /dev/null
+++ b/gremlin-dotnet/test/Gremlin.Net.UnitTest/Process/Traversal/CardinalityValueTests.cs
@@ -0,0 +1,55 @@
+#region License
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#endregion
+
+using Gremlin.Net.Process.Traversal;
+using Xunit;
+
+namespace Gremlin.Net.UnitTest.Process.Traversal
+{
+    public class CardinalityValueTests
+    {
+        [Fact]
+        public void ShouldProduceSingleValue()
+        {
+            CardinalityValue val = CardinalityValue.Single("test");
+            Assert.Equal("test", val.Value);
+            Assert.Equal(Cardinality.Single, val.Cardinality);
+        }
+
+        [Fact]
+        public void ShouldProduceListValue()
+        {
+            CardinalityValue val = CardinalityValue.List("test");
+            Assert.Equal("test", val.Value);
+            Assert.Equal(Cardinality.List, val.Cardinality);
+        }
+
+        [Fact]
+        public void ShouldProduceSetValue()
+        {
+            CardinalityValue val = CardinalityValue.Set("test");
+            Assert.Equal("test", val.Value);
+            Assert.Equal(Cardinality.Set, val.Cardinality);
+        }
+    }
+}
\ No newline at end of file
diff --git a/gremlin-go/driver/cucumber/gremlin.go b/gremlin-go/driver/cucumber/gremlin.go
index 1444206a4b..6ea3697903 100644
--- a/gremlin-go/driver/cucumber/gremlin.go
+++ b/gremlin-go/driver/cucumber/gremlin.go
@@ -678,6 +678,13 @@ var translationMap = map[string][]func(g *gremlingo.GraphTraversalSource, p map[
     "g_mergeV_hidden_id_key_onMatch_matched_prohibited": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.AddV("vertex")}, func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.MergeV(map[interface{}]interface{}{}).Option(gremlingo.Merge.OnMatch, p["xx1"])}}, 
     "g_mergeV_hidden_label_key_matched_onMatch_matched_prohibited": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.AddV("vertex")}, func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.MergeV(map[interface{}]interface{}{}).Option(gremlingo.Merge.OnMatch, p["xx1"])}}, 
     "g_mergeV_hidden_label_value_onMatch_matched_prohibited": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.AddV("vertex")}, func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.MergeV(map[interface{}]interface{}{}).Option(gremlingo.Merge.OnMatch, p["xx1"])}}, 
+    "g_mergeVXname_markoX_optionXonMatch_age_listX33XX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.AddV("person").Property("name", "marko").Property(gremlingo.Cardinality.List, "age", 29).Property(gremlingo.Cardinality.List, "age", 31).Property(gremlingo.Cardinality.List, "age", 32)}, func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.MergeV(map[interface{}]interface{}{"name":  [...]
+    "g_mergeVXname_markoX_optionXonMatch_age_setX33XX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.AddV("person").Property("name", "marko").Property(gremlingo.Cardinality.List, "age", 29).Property(gremlingo.Cardinality.List, "age", 31).Property(gremlingo.Cardinality.List, "age", 32)}, func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.MergeV(map[interface{}]interface{}{"name": " [...]
+    "g_mergeVXname_markoX_optionXonMatch_age_setX31XX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.AddV("person").Property("name", "marko").Property(gremlingo.Cardinality.List, "age", 29).Property(gremlingo.Cardinality.List, "age", 31).Property(gremlingo.Cardinality.List, "age", 32)}, func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.MergeV(map[interface{}]interface{}{"name": " [...]
+    "g_mergeVXname_markoX_optionXonMatch_age_singleX33XX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.AddV("person").Property("name", "marko").Property(gremlingo.Cardinality.List, "age", 29).Property(gremlingo.Cardinality.List, "age", 31).Property(gremlingo.Cardinality.List, "age", 32)}, func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.MergeV(map[interface{}]interface{}{"name" [...]
+    "g_mergeVXname_markoX_optionXonMatch_age_33_singleX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.AddV("person").Property("name", "marko").Property(gremlingo.Cardinality.List, "age", 29).Property(gremlingo.Cardinality.List, "age", 31).Property(gremlingo.Cardinality.List, "age", 32)}, func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.MergeV(map[interface{}]interface{}{"name": [...]
+    "g_mergeVXname_markoX_optionXonMatch_name_allen_age_setX31X_singleX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.AddV("person").Property("name", "marko").Property(gremlingo.Cardinality.List, "age", 29).Property(gremlingo.Cardinality.List, "age", 31).Property(gremlingo.Cardinality.List, "age", 32)}, func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.MergeV(map[interface{}]int [...]
+    "g_mergeVXname_markoX_optionXonMatch_name_allen_age_singleX31X_singleX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.AddV("person").Property("name", "marko").Property(gremlingo.Cardinality.List, "age", 29).Property(gremlingo.Cardinality.List, "age", 31).Property(gremlingo.Cardinality.List, "age", 32)}, func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.MergeV(map[interface{}] [...]
     "g_V_age_min": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Values("age").Min()}}, 
     "g_V_foo_min": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Values("foo").Min()}}, 
     "g_V_name_min": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Values("name").Min()}}, 
diff --git a/gremlin-go/driver/traversal.go b/gremlin-go/driver/traversal.go
index c6a72a6f43..1529fbacb0 100644
--- a/gremlin-go/driver/traversal.go
+++ b/gremlin-go/driver/traversal.go
@@ -153,6 +153,36 @@ var Cardinality = cardinalities{
 	Set:    "set",
 }
 
+type cv struct {
+	Bytecode *Bytecode
+}
+
+type CardValue interface {
+	Single(val interface{}) Bytecode
+	Set(val interface{}) Bytecode
+	List(val interface{}) Bytecode
+}
+
+var CardinalityValue CardValue = &cv{}
+
+func (*cv) Single(val interface{}) Bytecode {
+	bc := Bytecode{}
+	bc.AddSource("CardinalityValueTraversal", Cardinality.Single, val)
+	return bc
+}
+
+func (*cv) Set(val interface{}) Bytecode {
+	bc := Bytecode{}
+	bc.AddSource("CardinalityValueTraversal", Cardinality.Set, val)
+	return bc
+}
+
+func (*cv) List(val interface{}) Bytecode {
+	bc := Bytecode{}
+	bc.AddSource("CardinalityValueTraversal", Cardinality.List, val)
+	return bc
+}
+
 type column string
 
 type columns struct {
@@ -286,8 +316,8 @@ type merges struct {
 var Merge = merges{
 	OnCreate: "onCreate",
 	OnMatch:  "onMatch",
-	OutV:  "outV",
-	InV:  "inV",
+	OutV:     "outV",
+	InV:      "inV",
 }
 
 type operator string
diff --git a/gremlin-javascript/build/generate.groovy b/gremlin-javascript/build/generate.groovy
index c726d0e2ff..c7d92e0b29 100644
--- a/gremlin-javascript/build/generate.groovy
+++ b/gremlin-javascript/build/generate.groovy
@@ -95,6 +95,7 @@ radishGremlinFile.withWriter('UTF-8') { Writer writer ->
                     'const __ = graphTraversalModule.statics;\n' +
                     'const Barrier = traversalModule.barrier\n' +
                     'const Cardinality = traversalModule.cardinality\n' +
+                    'const CardinalityValue = graphTraversalModule.CardinalityValue;\n' +
                     'const Column = traversalModule.column\n' +
                     'const Direction = {\n' +
                     '    BOTH: traversalModule.direction.both,\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 292439ee6c..3884639d55 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
@@ -22,7 +22,7 @@
  */
 'use strict';
 
-const { Traversal } = require('./traversal');
+const { Traversal, cardinality } = require('./traversal');
 const { Transaction } = require('./transaction');
 const remote = require('../driver/remote-connection');
 const Bytecode = require('./bytecode');
@@ -1433,6 +1433,45 @@ class GraphTraversal extends Traversal {
   }
 }
 
+class CardinalityValue extends Bytecode {
+  /**
+   * Creates a new instance of {@link CardinalityValue}.
+   * @param {String} card
+   * @param {Object} value
+   */
+  constructor(card, value) {
+    super();
+    this.addSource('CardinalityValueTraversal', [card, value]);
+  }
+
+  /**
+   * Create a value with single cardinality.
+   * @param {Array} value
+   * @returns {CardinalityValue}
+   */
+  static single(value) {
+    return new CardinalityValue(cardinality.single, value);
+  }
+
+  /**
+   * Create a value with list cardinality.
+   * @param {Array} value
+   * @returns {CardinalityValue}
+   */
+  static list(value) {
+    return new CardinalityValue(cardinality.list, value);
+  }
+
+  /**
+   * Create a value with set cardinality.
+   * @param {Array} value
+   * @returns {CardinalityValue}
+   */
+  static set(value) {
+    return new CardinalityValue(cardinality.set, value);
+  }
+}
+
 function callOnEmptyTraversal(fnName, args) {
   const g = new GraphTraversal(null, null, new Bytecode());
   return g[fnName].apply(g, args);
@@ -1543,5 +1582,6 @@ const statics = {
 module.exports = {
   GraphTraversal,
   GraphTraversalSource,
+  CardinalityValue,
   statics,
 };
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 513042b2cb..9031bd61c6 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
@@ -30,6 +30,7 @@ const { TraversalStrategies, VertexProgramStrategy, OptionsStrategy, ReadOnlyStr
 const __ = graphTraversalModule.statics;
 const Barrier = traversalModule.barrier
 const Cardinality = traversalModule.cardinality
+const CardinalityValue = graphTraversalModule.CardinalityValue;
 const Column = traversalModule.column
 const Direction = {
     BOTH: traversalModule.direction.both,
@@ -697,6 +698,13 @@ const gremlins = {
     g_mergeV_hidden_id_key_onMatch_matched_prohibited: [function({g, xx1}) { return g.addV("vertex") }, function({g, xx1}) { return g.mergeV(new Map([])).option(Merge.onMatch,xx1) }], 
     g_mergeV_hidden_label_key_matched_onMatch_matched_prohibited: [function({g, xx1}) { return g.addV("vertex") }, function({g, xx1}) { return g.mergeV(new Map([])).option(Merge.onMatch,xx1) }], 
     g_mergeV_hidden_label_value_onMatch_matched_prohibited: [function({g, xx1}) { return g.addV("vertex") }, function({g, xx1}) { return g.mergeV(new Map([])).option(Merge.onMatch,xx1) }], 
+    g_mergeVXname_markoX_optionXonMatch_age_listX33XX: [function({g}) { return g.addV("person").property("name","marko").property(Cardinality.list,"age",29).property(Cardinality.list,"age",31).property(Cardinality.list,"age",32) }, function({g}) { return g.mergeV(new Map([["name","marko"]])).option(Merge.onMatch,new Map([["age",CardinalityValue.list(33)]])) }, function({g}) { return g.V().has("person","name","marko").has("age",33) }, function({g}) { return g.V().has("person","name","mark [...]
+    g_mergeVXname_markoX_optionXonMatch_age_setX33XX: [function({g}) { return g.addV("person").property("name","marko").property(Cardinality.list,"age",29).property(Cardinality.list,"age",31).property(Cardinality.list,"age",32) }, function({g}) { return g.mergeV(new Map([["name","marko"]])).option(Merge.onMatch,new Map([["age",CardinalityValue.set(33)]])) }, function({g}) { return g.V().has("person","name","marko").has("age",33) }, function({g}) { return g.V().has("person","name","marko" [...]
+    g_mergeVXname_markoX_optionXonMatch_age_setX31XX: [function({g}) { return g.addV("person").property("name","marko").property(Cardinality.list,"age",29).property(Cardinality.list,"age",31).property(Cardinality.list,"age",32) }, function({g}) { return g.mergeV(new Map([["name","marko"]])).option(Merge.onMatch,new Map([["age",CardinalityValue.set(31)]])) }, function({g}) { return g.V().has("person","name","marko").has("age",31) }, function({g}) { return g.V().has("person","name","marko" [...]
+    g_mergeVXname_markoX_optionXonMatch_age_singleX33XX: [function({g}) { return g.addV("person").property("name","marko").property(Cardinality.list,"age",29).property(Cardinality.list,"age",31).property(Cardinality.list,"age",32) }, function({g}) { return g.mergeV(new Map([["name","marko"]])).option(Merge.onMatch,new Map([["age",CardinalityValue.single(33)]])) }, function({g}) { return g.V().has("person","name","marko").has("age",33) }, function({g}) { return g.V().has("person","name"," [...]
+    g_mergeVXname_markoX_optionXonMatch_age_33_singleX: [function({g}) { return g.addV("person").property("name","marko").property(Cardinality.list,"age",29).property(Cardinality.list,"age",31).property(Cardinality.list,"age",32) }, function({g}) { return g.mergeV(new Map([["name","marko"]])).option(Merge.onMatch,new Map([["age",33]]),Cardinality.single) }, function({g}) { return g.V().has("person","name","marko").has("age",33) }, function({g}) { return g.V().has("person","name","marko") [...]
+    g_mergeVXname_markoX_optionXonMatch_name_allen_age_setX31X_singleX: [function({g}) { return g.addV("person").property("name","marko").property(Cardinality.list,"age",29).property(Cardinality.list,"age",31).property(Cardinality.list,"age",32) }, function({g}) { return g.mergeV(new Map([["name","marko"]])).option(Merge.onMatch,new Map([["name","allen"],["age",CardinalityValue.set(31)]]),Cardinality.single) }, function({g}) { return g.V().has("person","name","marko") }, function({g}) {  [...]
+    g_mergeVXname_markoX_optionXonMatch_name_allen_age_singleX31X_singleX: [function({g}) { return g.addV("person").property("name","marko").property(Cardinality.list,"age",29).property(Cardinality.list,"age",31).property(Cardinality.list,"age",32) }, function({g}) { return g.mergeV(new Map([["name","marko"]])).option(Merge.onMatch,new Map([["name","allen"],["age",CardinalityValue.single(31)]]),Cardinality.single) }, function({g}) { return g.V().has("person","name","marko") }, function({ [...]
     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 2e0ce504f5..5f37c5013f 100644
--- a/gremlin-language/src/main/antlr4/Gremlin.g4
+++ b/gremlin-language/src/main/antlr4/Gremlin.g4
@@ -569,6 +569,7 @@ traversalMethod_not
 traversalMethod_option
 	: 'option' LPAREN traversalPredicate COMMA nestedTraversal RPAREN #traversalMethod_option_Predicate_Traversal
 	| 'option' LPAREN traversalMerge COMMA (genericLiteralMap | nullLiteral) RPAREN #traversalMethod_option_Merge_Map
+	| 'option' LPAREN traversalMerge COMMA (genericLiteralMap | nullLiteral) COMMA traversalCardinality RPAREN #traversalMethod_option_Merge_Map_Cardinality
 	| '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
@@ -910,7 +911,13 @@ traversalDirection
     ;
 
 traversalCardinality
-    : 'single' | 'Cardinality.single'
+    : 'Cardinality.single' LPAREN genericLiteral RPAREN
+    | 'Cardinality.set' LPAREN genericLiteral RPAREN
+    | 'Cardinality.list' LPAREN genericLiteral RPAREN
+    | 'single' LPAREN genericLiteral RPAREN
+    | 'set' LPAREN genericLiteral RPAREN
+    | 'list' LPAREN genericLiteral RPAREN
+    | 'single' | 'Cardinality.single'
     | 'set' | 'Cardinality.set'
     | 'list' | 'Cardinality.list'
     ;
diff --git a/gremlin-python/build/generate.groovy b/gremlin-python/build/generate.groovy
index 6ccfa9ee86..78a065499b 100644
--- a/gremlin-python/build/generate.groovy
+++ b/gremlin-python/build/generate.groovy
@@ -93,7 +93,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, Merge, T, Pick, Operator, IO, WithOptions\n')
+                    'from gremlin_python.process.traversal import Barrier, Cardinality, CardinalityValue, 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/traversal.py b/gremlin-python/src/main/python/gremlin_python/process/traversal.py
index dc259d3d6f..a83048e3b0 100644
--- a/gremlin-python/src/main/python/gremlin_python/process/traversal.py
+++ b/gremlin-python/src/main/python/gremlin_python/process/traversal.py
@@ -817,6 +817,24 @@ class Bytecode(object):
             return Bytecode._create_graph_op("tx", "rollback")
 
 
+class CardinalityValue(Bytecode):
+    def __init__(self, cardinality, val):
+        super().__init__()
+        self.add_source("CardinalityValueTraversal", cardinality, val)
+
+    @classmethod
+    def single(cls, val):
+        return CardinalityValue(Cardinality.single, val)
+
+    @classmethod
+    def list_(cls, val):
+        return CardinalityValue(Cardinality.list_, val)
+
+    @classmethod
+    def set_(cls, val):
+        return CardinalityValue(Cardinality.set_, val)
+
+
 '''
 BINDINGS
 '''
diff --git a/gremlin-python/src/main/python/radish/gremlin.py b/gremlin-python/src/main/python/radish/gremlin.py
index 998289bbc9..5bbe59478e 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, Merge, T, Pick, Operator, IO, WithOptions
+from gremlin_python.process.traversal import Barrier, Cardinality, CardinalityValue, 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))], 
@@ -679,6 +679,13 @@ world.gremlins = {
     'g_mergeV_hidden_id_key_onMatch_matched_prohibited': [(lambda g, xx1=None:g.addV('vertex')), (lambda g, xx1=None:g.merge_v({}).option(Merge.on_match,xx1))], 
     'g_mergeV_hidden_label_key_matched_onMatch_matched_prohibited': [(lambda g, xx1=None:g.addV('vertex')), (lambda g, xx1=None:g.merge_v({}).option(Merge.on_match,xx1))], 
     'g_mergeV_hidden_label_value_onMatch_matched_prohibited': [(lambda g, xx1=None:g.addV('vertex')), (lambda g, xx1=None:g.merge_v({}).option(Merge.on_match,xx1))], 
+    'g_mergeVXname_markoX_optionXonMatch_age_listX33XX': [(lambda g:g.addV('person').property('name','marko').property(Cardinality.list_,'age',29).property(Cardinality.list_,'age',31).property(Cardinality.list_,'age',32)), (lambda g:g.merge_v({'name':'marko'}).option(Merge.on_match,{'age':CardinalityValue.list_(33)})), (lambda g:g.V().has('person','name','marko').has('age',33)), (lambda g:g.V().has('person','name','marko').has('age')), (lambda g:g.V().has('person','name','marko').propert [...]
+    'g_mergeVXname_markoX_optionXonMatch_age_setX33XX': [(lambda g:g.addV('person').property('name','marko').property(Cardinality.list_,'age',29).property(Cardinality.list_,'age',31).property(Cardinality.list_,'age',32)), (lambda g:g.merge_v({'name':'marko'}).option(Merge.on_match,{'age':CardinalityValue.set_(33)})), (lambda g:g.V().has('person','name','marko').has('age',33)), (lambda g:g.V().has('person','name','marko').has('age')), (lambda g:g.V().has('person','name','marko').propertie [...]
+    'g_mergeVXname_markoX_optionXonMatch_age_setX31XX': [(lambda g:g.addV('person').property('name','marko').property(Cardinality.list_,'age',29).property(Cardinality.list_,'age',31).property(Cardinality.list_,'age',32)), (lambda g:g.merge_v({'name':'marko'}).option(Merge.on_match,{'age':CardinalityValue.set_(31)})), (lambda g:g.V().has('person','name','marko').has('age',31)), (lambda g:g.V().has('person','name','marko').has('age')), (lambda g:g.V().has('person','name','marko').propertie [...]
+    'g_mergeVXname_markoX_optionXonMatch_age_singleX33XX': [(lambda g:g.addV('person').property('name','marko').property(Cardinality.list_,'age',29).property(Cardinality.list_,'age',31).property(Cardinality.list_,'age',32)), (lambda g:g.merge_v({'name':'marko'}).option(Merge.on_match,{'age':CardinalityValue.single(33)})), (lambda g:g.V().has('person','name','marko').has('age',33)), (lambda g:g.V().has('person','name','marko').has('age')), (lambda g:g.V().has('person','name','marko').prop [...]
+    'g_mergeVXname_markoX_optionXonMatch_age_33_singleX': [(lambda g:g.addV('person').property('name','marko').property(Cardinality.list_,'age',29).property(Cardinality.list_,'age',31).property(Cardinality.list_,'age',32)), (lambda g:g.merge_v({'name':'marko'}).option(Merge.on_match,{'age':33},Cardinality.single)), (lambda g:g.V().has('person','name','marko').has('age',33)), (lambda g:g.V().has('person','name','marko').has('age')), (lambda g:g.V().has('person','name','marko').properties( [...]
+    'g_mergeVXname_markoX_optionXonMatch_name_allen_age_setX31X_singleX': [(lambda g:g.addV('person').property('name','marko').property(Cardinality.list_,'age',29).property(Cardinality.list_,'age',31).property(Cardinality.list_,'age',32)), (lambda g:g.merge_v({'name':'marko'}).option(Merge.on_match,{'name':'allen','age':CardinalityValue.set_(31)},Cardinality.single)), (lambda g:g.V().has('person','name','marko')), (lambda g:g.V().has('person','name','allen').has('age',31)), (lambda g:g.V [...]
+    'g_mergeVXname_markoX_optionXonMatch_name_allen_age_singleX31X_singleX': [(lambda g:g.addV('person').property('name','marko').property(Cardinality.list_,'age',29).property(Cardinality.list_,'age',31).property(Cardinality.list_,'age',32)), (lambda g:g.merge_v({'name':'marko'}).option(Merge.on_match,{'name':'allen','age':CardinalityValue.single(31)},Cardinality.single)), (lambda g:g.V().has('person','name','marko')), (lambda g:g.V().has('person','name','allen').has('age',33)), (lambda  [...]
     'g_V_age_min': [(lambda g:g.V().age.min_())], 
     'g_V_foo_min': [(lambda g:g.V().foo.min_())], 
     'g_V_name_min': [(lambda g:g.V().name.min_())], 
diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/MergeVertex.feature b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/MergeVertex.feature
index 91de838781..a71737e4fb 100644
--- a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/MergeVertex.feature
+++ b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/MergeVertex.feature
@@ -842,3 +842,132 @@ Feature: Step - mergeV()
       """
     When iterated to list
     Then the traversal will raise an error
+
+  @MultiMetaProperties
+  Scenario: g_mergeVXname_markoX_optionXonMatch_age_listX33XX
+    Given the empty graph
+    And the graph initializer of
+      """
+      g.addV("person").property("name", "marko").property(Cardinality.list, "age", 29).property(Cardinality.list, "age", 31).property(Cardinality.list, "age", 32)
+      """
+    And the traversal of
+      """
+      g.mergeV([name: "marko"]).
+          option(Merge.onMatch, [age: Cardinality.list(33)])
+      """
+    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\", 33)"
+    And the graph should return 1 for count of "g.V().has(\"person\",\"name\",\"marko\").has(\"age\")"
+    And the graph should return 4 for count of "g.V().has(\"person\",\"name\",\"marko\").properties(\"age\")"
+
+  @MultiMetaProperties
+  Scenario: g_mergeVXname_markoX_optionXonMatch_age_setX33XX
+    Given the empty graph
+    And the graph initializer of
+      """
+      g.addV("person").property("name", "marko").property(Cardinality.list, "age", 29).property(Cardinality.list, "age", 31).property(Cardinality.list, "age", 32)
+      """
+    And the traversal of
+      """
+      g.mergeV([name: "marko"]).
+          option(Merge.onMatch, [age: Cardinality.set(33)])
+      """
+    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\", 33)"
+    And the graph should return 1 for count of "g.V().has(\"person\",\"name\",\"marko\").has(\"age\")"
+    And the graph should return 4 for count of "g.V().has(\"person\",\"name\",\"marko\").properties(\"age\")"
+
+  @MultiMetaProperties
+  Scenario: g_mergeVXname_markoX_optionXonMatch_age_setX31XX
+    Given the empty graph
+    And the graph initializer of
+      """
+      g.addV("person").property("name", "marko").property(Cardinality.list, "age", 29).property(Cardinality.list, "age", 31).property(Cardinality.list, "age", 32)
+      """
+    And the traversal of
+      """
+      g.mergeV([name: "marko"]).
+          option(Merge.onMatch, [age: Cardinality.set(31)])
+      """
+    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\", 31)"
+    And the graph should return 1 for count of "g.V().has(\"person\",\"name\",\"marko\").has(\"age\")"
+    And the graph should return 3 for count of "g.V().has(\"person\",\"name\",\"marko\").properties(\"age\")"
+
+  @MultiMetaProperties
+  Scenario: g_mergeVXname_markoX_optionXonMatch_age_singleX33XX
+    Given the empty graph
+    And the graph initializer of
+      """
+      g.addV("person").property("name", "marko").property(Cardinality.list, "age", 29).property(Cardinality.list, "age", 31).property(Cardinality.list, "age", 32)
+      """
+    And the traversal of
+      """
+      g.mergeV([name: "marko"]).
+          option(Merge.onMatch, [age: Cardinality.single(33)])
+      """
+    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\", 33)"
+    And the graph should return 1 for count of "g.V().has(\"person\",\"name\",\"marko\").has(\"age\")"
+    And the graph should return 1 for count of "g.V().has(\"person\",\"name\",\"marko\").properties(\"age\")"
+
+  @MultiMetaProperties
+  Scenario: g_mergeVXname_markoX_optionXonMatch_age_33_singleX
+    Given the empty graph
+    And the graph initializer of
+      """
+      g.addV("person").property("name", "marko").property(Cardinality.list, "age", 29).property(Cardinality.list, "age", 31).property(Cardinality.list, "age", 32)
+      """
+    And the traversal of
+      """
+      g.mergeV([name: "marko"]).
+          option(Merge.onMatch, [age: 33], Cardinality.single)
+      """
+    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\", 33)"
+    And the graph should return 1 for count of "g.V().has(\"person\",\"name\",\"marko\").has(\"age\")"
+    And the graph should return 1 for count of "g.V().has(\"person\",\"name\",\"marko\").properties(\"age\")"
+
+  @MultiMetaProperties
+  Scenario: g_mergeVXname_markoX_optionXonMatch_name_allen_age_setX31X_singleX
+    Given the empty graph
+    And the graph initializer of
+      """
+      g.addV("person").property("name", "marko").property(Cardinality.list, "age", 29).property(Cardinality.list, "age", 31).property(Cardinality.list, "age", 32)
+      """
+    And the traversal of
+      """
+      g.mergeV([name: "marko"]).
+          option(Merge.onMatch, [name: "allen", age: Cardinality.set(31)], single)
+      """
+    When iterated to list
+    Then the result should have a count of 1
+    And the graph should return 0 for count of "g.V().has(\"person\",\"name\",\"marko\")"
+    And the graph should return 1 for count of "g.V().has(\"person\",\"name\",\"allen\").has(\"age\", 31)"
+    And the graph should return 1 for count of "g.V().has(\"person\",\"name\",\"allen\").has(\"age\")"
+    And the graph should return 3 for count of "g.V().has(\"person\",\"name\",\"allen\").properties(\"age\")"
+
+  @MultiMetaProperties
+  Scenario: g_mergeVXname_markoX_optionXonMatch_name_allen_age_singleX31X_singleX
+    Given the empty graph
+    And the graph initializer of
+      """
+      g.addV("person").property("name", "marko").property(Cardinality.list, "age", 29).property(Cardinality.list, "age", 31).property(Cardinality.list, "age", 32)
+      """
+    And the traversal of
+      """
+      g.mergeV([name: "marko"]).
+          option(Merge.onMatch, [name: "allen", age: Cardinality.single(31)], single)
+      """
+    When iterated to list
+    Then the result should have a count of 1
+    And the graph should return 0 for count of "g.V().has(\"person\",\"name\",\"marko\")"
+    And the graph should return 0 for count of "g.V().has(\"person\",\"name\",\"allen\").has(\"age\", 33)"
+    And the graph should return 1 for count of "g.V().has(\"person\",\"name\",\"allen\").has(\"age\", 31)"
+    And the graph should return 1 for count of "g.V().has(\"person\",\"name\",\"allen\").has(\"age\")"
+    And the graph should return 1 for count of "g.V().has(\"person\",\"name\",\"allen\").properties(\"age\")"
\ No newline at end of file