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 c4e32b786b6556275d409b7cee6a014b372d828e
Author: Stephen Mallette <[email protected]>
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() and property(Map). 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/reference/gremlin-variants.asciidoc       |  24 +++-
 docs/src/reference/the-traversal.asciidoc          |  52 ++++++---
 docs/src/upgrade/release-3.7.x.asciidoc            |  71 ++++++++++++
 .../tinkerpop/gremlin/jsr223/JavaTranslator.java   |  34 ++++--
 .../grammar/DefaultGremlinBaseVisitor.java         |   5 +
 .../language/grammar/GenericLiteralVisitor.java    |  18 ++-
 .../language/grammar/TraversalMethodVisitor.java   |  15 +++
 .../gremlin/process/traversal/Bytecode.java        |   4 +-
 .../gremlin/process/traversal/Translator.java      |  14 ++-
 .../traversal/dsl/graph/GraphTraversal.java        |  56 ++++++++-
 .../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          |  92 +++++++++++++++
 .../Process/Traversal/GraphTraversal.cs            |   9 ++
 .../Docs/Reference/GremlinVariantsTests.cs         |   2 +
 .../Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs |   9 ++
 .../Process/Traversal/CardinalityValueTests.cs     |  55 +++++++++
 gremlin-go/driver/cucumber/gremlin.go              |   9 ++
 gremlin-go/driver/traversal.go                     |  34 +++++-
 gremlin-javascript/build/generate.groovy           |   1 +
 .../lib/process/graph-traversal.js                 |  42 ++++++-
 .../gremlin-javascript/test/cucumber/gremlin.js    |  10 ++
 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   |  11 +-
 .../gremlin/test/features/map/AddVertex.feature    |  37 +++++-
 .../gremlin/test/features/map/MergeVertex.feature  | 129 +++++++++++++++++++++
 45 files changed, 1027 insertions(+), 52 deletions(-)

diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc
index 3810e2a4b5..31dd4b11ba 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-6-XXX, 3.6.XXX>>.
 
+* Allowed `mergeV()` and `property(Map)` to more easily define `Cardinality` 
values for properties for `onMatch` and `onCreate` options.
 * Removed `connectOnStartup` configuration option from gremlin-javascript.
 * Changed `Gremlin.version()` to read from the more specifically named 
`tinkerpop-version` attribute.
 * Added warning on vertex property cardinality mismatch when reading GraphML.
diff --git a/docs/src/reference/gremlin-variants.asciidoc 
b/docs/src/reference/gremlin-variants.asciidoc
index cf4e064fb8..515964468f 100644
--- a/docs/src/reference/gremlin-variants.asciidoc
+++ b/docs/src/reference/gremlin-variants.asciidoc
@@ -510,6 +510,10 @@ To make the code more readable and close to the Gremlin 
query language), you can
        var order = gremlingo.Order
 ----
 
+Finally, the enum construct for `Cardinality` cannot have functions attached 
to it the way it can be done in Java,
+therefore cardinality functions that take a value like `list()`, `set()`, and 
`single()` are referenced from a
+`CardinalityValue` class rather than `Cardinality` itself.
+
 [[gremlin-go-limitations]]
 === Limitations
 
@@ -1308,6 +1312,8 @@ const pop = gremlin.process.pop
 const order = gremlin.process.order
 const scope = gremlin.process.scope
 const t = gremlin.process.t
+const cardinality = gremlin.process.cardinality
+const CardinalityValue = gremlin.process.CardinalityValue
 ----
 
 By defining these imports it becomes possible to write Gremlin in the more 
shorthand, canonical style that is
@@ -1570,6 +1576,10 @@ bits of conflicting Gremlin get an underscore appended 
as a suffix:
 *Steps* - <<from-step,from_()>>, <<in-step,in_()>>, <<with-step,with_()>>
 *Tokens* - `Direction.from_`
 
+In addition, the enum construct for `Cardinality` cannot have functions 
attached to it the way it can be done in Java,
+therefore cardinality functions that take a value like `list()`, `set()`, and 
`single()` are referenced from a
+`CardinalityValue` class rather than `Cardinality` itself.
+
 Gremlin allows for `Map` instances to include `null` keys, but `null` keys in 
Javascript have some interesting behavior
 as in:
 
@@ -1605,6 +1615,7 @@ g.V().hasLabel('person').groupCount().by('age')
 
 Either of the above two options accomplishes the desired goal as both prevent 
`groupCount()` from having to process
 the possibility of `null`.
+
 [[gremlin-javascript-limitations]]
 === Limitations
 
@@ -1919,14 +1930,18 @@ g.V().Repeat(__.Out()).Times(2).Values<string>("name");
 Gremlin allows for `Map` instances to include `null` keys, but `null` keys in 
C# `Dictionary` instances are not allowed.
 It is therefore necessary to rewrite a traversal such as:
 
-[source,javascript]
+[source,csharp]
 ----
 g.V().groupCount().by('age')
 ----
 
 where "age" is not a valid key for all vertices in a way that will remove the 
need for a `null` to be returned.
 
-[source,javascript]
+Finally, the enum construct for `Cardinality` cannot have functions attached 
to it the way it can be done in Java,
+therefore cardinality functions that take a value like `list()`, `set()`, and 
`single()` are referenced from a
+`CardinalityValue` class rather than `Cardinality` itself.
+
+[source,csharp]
 ----
 g.V().has('age').groupCount().by('age')
 g.V().hasLabel('person').groupCount().by('age')
@@ -2066,6 +2081,7 @@ from gremlin_python.driver.driver_remote_connection 
import DriverRemoteConnectio
 from gremlin_python.process.traversal import T
 from gremlin_python.process.traversal import Order
 from gremlin_python.process.traversal import Cardinality
+from gremlin_python.process.traversal import CardinalityValue
 from gremlin_python.process.traversal import Column
 from gremlin_python.process.traversal import Direction
 from gremlin_python.process.traversal import Operator
@@ -2457,6 +2473,10 @@ bits of conflicting Gremlin get an underscore appended 
as a suffix:
 
 *Tokens* - <<a-note-on-scopes,Scope.global_>>, `Direction.from_`, 
`Operator.sum_`
 
+In addition, the enum construct for `Cardinality` cannot have functions 
attached to it the way it can be done in Java,
+therefore cardinality functions that take a value like `list()`, `set()`, and 
`single()` are referenced from a
+`CardinalityValue` class rather than `Cardinality` itself.
+
 [[gremlin-python-limitations]]
 === Limitations
 
diff --git a/docs/src/reference/the-traversal.asciidoc 
b/docs/src/reference/the-traversal.asciidoc
index 5ab49721ac..109005ea69 100644
--- a/docs/src/reference/the-traversal.asciidoc
+++ b/docs/src/reference/the-traversal.asciidoc
@@ -2666,22 +2666,35 @@ Metadata > {}
 
======================================================================================================================================================================
 ----
 
-In some cases, such as when adding multi-properties or meta-properties to a 
vertex, one or
-more `property` steps can follow the `mergeV()` step. Note that 
meta-properties and
-multi-properties cannot be added directly using just `mergeV()` as a key/value 
pair such as
-`'my-numbers:[1,2,3,4]` will be treated as a `List` object and not a set of 
multi-property
-values.
+When working with multi-properties, there are two ways to specify them for 
`mergeV()`. First, you can specify them
+individually using a `CardinalityValue` as the value in the `Map`. The 
`CardinalityValue` allows you to specify the
+value as well as the `Cardinality` for that value. Note that it is only 
possible to specify one value with this syntax
+even if you are using `set` or `list`.
 
 [gremlin-groovy]
 ----
 g.mergeV([(T.label):'Dog', name:'Max']). <1>
-  property(list,'alias','Maximus').
-  property(list,'alias','Maxamillion') <2>
+    option(onCreate, [alias: set('Maximus')]). <2>
+  property(set,'alias','Maxamillion') <3>
 g.V().has('name','Max').valueMap().with(WithOptions.tokens)
 ----
 
 <1> Find or create a vertex for Max.
-<2> Whether Max was found or created, add some aliases with list cardinality.
+<2> If Max is not found then add an alias of `set` cardinality.
+<3> Whether Max was found or created, add another alias with `set` cardinality.
+
+The second option is to specify `Cardinality` for the entire range of values 
as follows:
+
+[gremlin-groovy]
+----
+g.mergeV([(T.label):'Dog', name:'Max']).
+    option(onCreate, [alias: 'Maximus', city: 'Boston'], set) <1>
+g.mergeV([(T.label):'Dog', name:'Max']).
+    option(onCreate, [alias: 'Maximus', city: single('Boston')], set) <2>
+----
+
+<1> If Max is created then set the alias and city with cardinality of `set`.
+<2> If Max is created then set the alias with cardinality of `set` and city 
with cardinality `single`.
 
 More than one vertex can be created by a single `mergeV()` operation. This is 
done by
 injecting a `List` of `Map` objects into the traversal and letting them stream 
into the `mergeV()`
@@ -3334,20 +3347,23 @@ g.V(1).property(['city': 'santa fe', 'state': 'new 
mexico'])  <1>
 g.V(1).property(list,'age',35)  <2>
 g.V(1).property(list, ['city': 'santa fe', 'state': 'new mexico'])  <3>
 g.V(1).valueMap()
-g.V(1).property('friendWeight',outE('knows').values('weight').sum(),'acl','private')
 <4>
-g.V(1).properties('friendWeight').valueMap() <5>
-g.addV().property(T.label,'person').valueMap().with(WithOptions.tokens) <6>
-g.addV().property(null) <7>
+g.V(1).property(list, ['age': single(36), 'city': 'wilmington', 'state': 
'delaware'])  <4>
+g.V(1).valueMap()
+g.V(1).property('friendWeight',outE('knows').values('weight').sum(),'acl','private')
 <5>
+g.V(1).properties('friendWeight').valueMap() <6>
+g.addV().property(T.label,'person').valueMap().with(WithOptions.tokens) <7>
+g.addV().property(null) <8>
 g.addV().property(set, null)
 ----
 
-<1> Properties can also take a Map as an argument.
+<1> Properties can also take a `Map` as an argument.
 <2> For vertices, a cardinality can be provided for <<vertex-properties,vertex 
properties>>.
-<3> If a cardinality is specified for a Map then that cardinality will be used 
for all properties in the map.  If you need different cardinalities per 
property then you should individually add the property values.
-<4> It is possible to select the property value (as well as key) via a 
traversal.
-<5> For vertices, the `property()`-step can add meta-properties.
-<6> The label value can be specified as a property only at the time a vertex 
is added and if one is not specified in the addV()
-<7> If you pass a `null` value for the Map this will be treated as a no-op and 
the input will be returned
+<3> If a cardinality is specified for a `Map` then that cardinality will be 
used for all properties in the map.
+<4> Assign the `Cardinality` individually to override the specified `list` or 
the default cardinality if not specified.
+<5> It is possible to select the property value (as well as key) via a 
traversal.
+<6> For vertices, the `property()`-step can add meta-properties.
+<7> The label value can be specified as a property only at the time a vertex 
is added and if one is not specified in the addV()
+<8> If you pass a `null` value for the Map this will be treated as a no-op and 
the input will be returned
 
 *Additional References*
 
diff --git a/docs/src/upgrade/release-3.7.x.asciidoc 
b/docs/src/upgrade/release-3.7.x.asciidoc
index 270afe904a..368ba70437 100644
--- a/docs/src/upgrade/release-3.7.x.asciidoc
+++ b/docs/src/upgrade/release-3.7.x.asciidoc
@@ -60,6 +60,77 @@ gremlin> g.union(V().has('name','vadas'),
 
 See: link:https://issues.apache.org/jira/browse/TINKERPOP-2873[TINKERPOP-2873]
 
+==== Map and Cardinality
+
+Relatively recent changes to the Gremlin language have allowed properties to 
be set by way of a `Map`. As it pertains
+to vertices, a `Map` can be given to `mergeV()` and `property()` steps. The 
limitation was that setting `Cardinality`
+with this syntax was not possible without reverting back to `property()` steps 
that took a `Cardinality` as an argument
+in some way. The following paragraphs show how changes for in 3.6.5 make this 
syntax much better for multi-properties.
+
+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,text]
+----
+gremlin> g.addV().property(id,1234).property('age',19).property(set, 'city', 
'detroit')
+==>v[1234]
+gremlin> g.mergeV([(T.id): 1234]).
+......1>   option(onMatch, ['age': single(20), 'city': set('miami')])
+==>v[1234]
+gremlin> g.V(1234).valueMap()
+==>[city:[detroit,miami],age:[20]]
+----
+
+The other option available is to provide a default `Cardinality` to the 
`option()` as follows, continuing from the
+previous example:
+
+[source,text]
+----
+gremlin> g.mergeV([(T.id): 1234]).
+......1>     option(onMatch, ['age': 21, 'city': set('orlando')], single)
+==>v[1234]
+gremlin> g.mergeV([(T.id): 1234]).
+......1>     option(onMatch, ['age': 22, 'city': set('boston')], single)
+==>v[1234]
+gremlin> g.V(1234).valueMap()
+==>[city:[detroit,miami,orlando,boston],age:[22]]
+----
+
+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.
+
+For `property(Map)` the `Cardinality` could be set universally for the `Map` 
with `property(Cardinality, Map)` but
+there was no mechanism to set that value individually. Using the same pattern 
above and constructing a
+`CardinalityValue` now allows this possibility.
+
+[source,text]
+----
+gremlin> g.addV().property(id,1234).property('age',19).property(set, 'city', 
'detroit')
+==>v[1234]
+gremlin> g.V(1234).property(['age': 20, 'city': set('miami')])
+==>v[1234]
+gremlin> g.V(1234).property(['age': single(21), 'city': set('orlando')])
+==>v[1234]
+gremlin> g.V(1234).property(single, ['age': 21, 'city': set('boston')])
+==>v[1234]
+gremlin> g.V(1234).valueMap()
+==>[city:[detroit,miami,orlando,boston],age:[21]]
+----
+
+See: link:https://issues.apache.org/jira/browse/TINKERPOP-2957[TINKERPOP-2957]
+
 ==== Properties on Elements
 
 ===== Introduction
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 f8884165de..647a6de253 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
@@ -1548,4 +1548,9 @@ public class DefaultGremlinBaseVisitor<T> extends 
AbstractParseTreeVisitor<T> im
         */
        @Override
        public T visitStringLiteralVarargs(final 
GremlinParser.StringLiteralVarargsContext 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; }
 }
\ No newline at end of file
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 bf8000bccd..b9efbd352d 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
@@ -504,7 +504,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));
+        }
     }
 
     /**
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 bb02fface8..1862a501da 100644
--- 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/TraversalMethodVisitor.java
+++ 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/TraversalMethodVisitor.java
@@ -18,6 +18,7 @@
  */
 package org.apache.tinkerpop.gremlin.language.grammar;
 
+import org.apache.tinkerpop.gremlin.process.traversal.Merge;
 import org.apache.tinkerpop.gremlin.process.traversal.Operator;
 import org.apache.tinkerpop.gremlin.process.traversal.Order;
 import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
@@ -1060,6 +1061,20 @@ public class TraversalMethodVisitor extends 
TraversalRootVisitor<GraphTraversal>
                 antlr.tvisitor.visitNestedTraversal(ctx.nestedTraversal()));
     }
 
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public Traversal visitTraversalMethod_option_Merge_Map_Cardinality(final 
GremlinParser.TraversalMethod_option_Merge_Map_CardinalityContext ctx) {
+        if (ctx.genericLiteralMapNullableArgument().nullLiteral() != null) {
+            return 
this.graphTraversal.option(antlr.argumentVisitor.parseMerge(ctx.traversalMergeArgument()),
 (Map) null);
+        }
+
+        return 
graphTraversal.option(antlr.argumentVisitor.parseMerge(ctx.traversalMergeArgument()),
+                (Map) new 
GenericLiteralVisitor(antlr).visitGenericLiteralMap(ctx.genericLiteralMapNullableArgument().genericLiteralMap()),
+                
TraversalEnumParser.parseTraversalEnumFromContext(Cardinality.class, 
ctx.traversalCardinality()));
+    }
+
     /**
      * {@inheritDoc}
      */
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 4251683860..ddc8f5d9da 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;
@@ -2541,6 +2542,8 @@ public interface GraphTraversal<S, E> extends 
Traversal<S, E> {
      * If a {@link Map} is supplied then each of the key/value pairs in the 
map will
      * be added as property.  This method is the long-hand version of looping 
through the 
      * {@link #property(Object, Object, Object...)} method for each key/value 
pair supplied.
+     * If a {@link CardinalityValueTraversal} is specified as a value then it 
will override any
+     * {@link VertexProperty.Cardinality} specified for the {@code key}.
      * <p />
      * This method is effectively calls {@link 
#property(VertexProperty.Cardinality, Object, Object, Object...)}
      * as {@code property(null, key, value, keyValues}.
@@ -2554,13 +2557,21 @@ public interface GraphTraversal<S, E> extends 
Traversal<S, E> {
      */
     public default GraphTraversal<S, E> property(final Object key, final 
Object value, final Object... keyValues) {
         if (key instanceof VertexProperty.Cardinality) {
-            if (value instanceof Map) { //Handle the property(Cardinality, 
Map) signature
-                final Map<Object, Object> map = (Map)value;
+            if (value instanceof Map) {
+                // Handle the property(Cardinality, Map) signature
+                final Map<Object, Object> map = (Map) value;
                 for (Map.Entry<Object, Object> entry : map.entrySet()) {
-                    property(key, entry.getKey(), entry.getValue());
+                    final Object val = entry.getValue();
+                    if (val instanceof CardinalityValueTraversal) {
+                        final CardinalityValueTraversal cardVal = 
(CardinalityValueTraversal) val;
+                        property(cardVal.getCardinality(), entry.getKey(), 
cardVal.getValue());
+                    } else {
+                        property(key, entry.getKey(), entry.getValue());
+                    }
                 }
                 return this;
-            } else if (value == null) { // Just return the input if you pass a 
null
+            } else if (value == null) {
+                // Just return the input if you pass a null
                 return this;
             } else {
                 return this.property((VertexProperty.Cardinality) key, value, 
null == keyValues ? null : keyValues[0],
@@ -2568,7 +2579,8 @@ public interface GraphTraversal<S, E> extends 
Traversal<S, E> {
                                 Arrays.copyOfRange(keyValues, 1, 
keyValues.length) :
                                 new Object[]{});
             }
-        } else  { //handles if cardinality is not the first parameter
+        } else  {
+            // handles if cardinality is not the first parameter
             return this.property(null, key, value, keyValues);
         }
     }
@@ -2578,6 +2590,9 @@ public interface GraphTraversal<S, E> extends 
Traversal<S, E> {
      * be added as property.  This method is the long-hand version of looping 
through the 
      * {@link #property(Object, Object, Object...)} method for each key/value 
pair supplied.
      * <p/>
+     * A value may use a {@link CardinalityValueTraversal} to allow 
specification of the
+     * {@link VertexProperty.Cardinality} along with the property value itself.
+     * <p/>
      * If a {@link Map} is not supplied then an exception is thrown.
      * <p />
      * This method is effectively calls {@link 
#property(VertexProperty.Cardinality, Object, Object, Object...)}
@@ -2591,7 +2606,13 @@ public interface GraphTraversal<S, E> extends 
Traversal<S, E> {
     public default GraphTraversal<S, E> property(final Map<Object, Object> 
value) {
         if (value != null) {
             for (Map.Entry<Object, Object> entry : value.entrySet()) {
-                property(null, entry.getKey(), entry.getValue());
+                final Object val = entry.getValue();
+                if (val instanceof CardinalityValueTraversal) {
+                    final CardinalityValueTraversal cardVal = 
(CardinalityValueTraversal) val;
+                    property(cardVal.getCardinality(), entry.getKey(), 
cardVal.getValue());
+                } else {
+                    property(null, entry.getKey(), entry.getValue());
+                }
             }
         }
         return this;
@@ -3286,6 +3307,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 ff50166c0b..af7a18a1d9 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 b4da7f146f..7b84f7ea5b 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,7 @@ package org.apache.tinkerpop.gremlin.language.grammar;
 
 import org.antlr.v4.runtime.CharStreams;
 import org.antlr.v4.runtime.CommonTokenStream;
+import org.apache.tinkerpop.gremlin.structure.VertexProperty;
 import org.junit.Assert;
 import org.junit.Test;
 import org.junit.experimental.runners.Enclosed;
@@ -43,7 +44,6 @@ import static 
org.hamcrest.number.OrderingComparison.greaterThanOrEqualTo;
 import static org.hamcrest.number.OrderingComparison.lessThan;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
 
 /**
  * Generic Literal visitor test
@@ -674,4 +674,42 @@ public class GeneralLiteralVisitorTest {
             assertEquals(Double.NEGATIVE_INFINITY, new 
GenericLiteralVisitor(new GremlinAntlrToJava()).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, new GenericLiteralVisitor(new 
GremlinAntlrToJava()).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 81e9559823..553912b7fa 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
@@ -62,6 +62,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..b184eee495
--- /dev/null
+++ b/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/CardinalityValue.cs
@@ -0,0 +1,92 @@
+#region License
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#endregion
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+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)
+        {
+            if (card == null)
+                throw new ArgumentNullException("Cardinality argument must not 
be null");
+            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 2d1633697c..3f55bfc3ab 100644
--- a/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/GraphTraversal.cs
+++ b/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/GraphTraversal.cs
@@ -1323,6 +1323,15 @@ namespace Gremlin.Net.Process.Traversal
             return Wrap<TStart, TEnd>(this);
         }
 
+        /// <summary>
+        ///     Adds the option step to this <see cref="GraphTraversal{SType, 
EType}" />.
+        /// </summary>
+        public GraphTraversal<TStart, TEnd> Option (object pickToken, 
IDictionary<object,object> traversalOption, Cardinality cardinality)
+        {
+            Bytecode.AddStep("option", pickToken, traversalOption, 
cardinality);
+            return Wrap<TStart, TEnd>(this);
+        }
+
         /// <summary>
         ///     Adds the option step to this <see cref="GraphTraversal{SType, 
EType}" />.
         /// </summary>
diff --git 
a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Docs/Reference/GremlinVariantsTests.cs
 
b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Docs/Reference/GremlinVariantsTests.cs
index 0d8a827496..9fed29ea2c 100644
--- 
a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Docs/Reference/GremlinVariantsTests.cs
+++ 
b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Docs/Reference/GremlinVariantsTests.cs
@@ -45,6 +45,8 @@ using static Gremlin.Net.Process.Traversal.Scope;
 using static Gremlin.Net.Process.Traversal.TextP;
 using static Gremlin.Net.Process.Traversal.Column;
 using static Gremlin.Net.Process.Traversal.Direction;
+using static Gremlin.Net.Process.Traversal.Cardinality;
+using static Gremlin.Net.Process.Traversal.CardinalityValue;
 using static Gremlin.Net.Process.Traversal.T;
 // end::commonImports[]
 
diff --git a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs 
b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs
index 20ff0385d3..67a3cee9df 100644
--- a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs
+++ b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs
@@ -459,6 +459,8 @@ namespace Gremlin.Net.IntegrationTest.Gherkin
                {"g_addV_propertyXlabel_personX", new 
List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> 
{(g,p) =>g.AddV().Property(T.Label,"person"), (g,p) 
=>g.V().HasLabel("person")}}, 
                {"g_addV_propertyXmapX", new List<Func<GraphTraversalSource, 
IDictionary<string, object>, ITraversal>> {(g,p) 
=>g.AddV().Property("name","foo").Property("age",42), (g,p) 
=>g.V().Has("name","foo")}}, 
                {"g_addV_propertyXsingle_mapX", new 
List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> 
{(g,p) 
=>g.AddV().Property(Cardinality.Single,"name","foo").Property(Cardinality.Single,"age",42),
 (g,p) =>g.V().Has("name","foo")}}, 
+               {"g_V_hasXname_fooX_propertyXname_setXbarX_age_43X", new 
List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> 
{(g,p) =>g.AddV().Property(Cardinality.Single,"name","foo").Property("age",42), 
(g,p) 
=>g.V().Has("name","foo").Property(Cardinality.Set,"name","bar").Property("age",43),
 (g,p) =>g.V().Has("name","foo"), (g,p) =>g.V().Has("name","bar"), (g,p) 
=>g.V().Has("age",43), (g,p) =>g.V().Has("age",42)}}, 
+               {"g_V_hasXname_fooX_propertyXset_name_bar_age_singleX43XX", new 
List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> 
{(g,p) =>g.AddV().Property(Cardinality.Single,"name","foo").Property("age",42), 
(g,p) 
=>g.V().Has("name","foo").Property(Cardinality.Set,"name","bar").Property(Cardinality.Single,"age",43),
 (g,p) =>g.V().Has("name","foo"), (g,p) =>g.V().Has("name","bar"), (g,p) 
=>g.V().Has("age",43), (g,p) =>g.V().Has("age",42)}}, 
                {"g_addV_propertyXnullX", new List<Func<GraphTraversalSource, 
IDictionary<string, object>, ITraversal>> {(g,p) =>g.AddV("person"), (g,p) 
=>g.V().HasLabel("person").Values<object>()}}, 
                {"g_addV_propertyXemptyX", new List<Func<GraphTraversalSource, 
IDictionary<string, object>, ITraversal>> {(g,p) =>g.AddV("person"), (g,p) 
=>g.V().HasLabel("person").Values<object>()}}, 
                {"g_addV_propertyXset_nullX", new 
List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> 
{(g,p) =>g.AddV("foo"), (g,p) =>g.V().HasLabel("foo").Values<object>()}}, 
@@ -721,6 +723,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 6361e89858..66dce5efbf 100644
--- a/gremlin-go/driver/cucumber/gremlin.go
+++ b/gremlin-go/driver/cucumber/gremlin.go
@@ -429,6 +429,8 @@ var translationMap = map[string][]func(g 
*gremlingo.GraphTraversalSource, p map[
     "g_addV_propertyXlabel_personX": {func(g *gremlingo.GraphTraversalSource, 
p map[string]interface{}) *gremlingo.GraphTraversal {return 
g.AddV().Property(gremlingo.T.Label, "person")}, func(g 
*gremlingo.GraphTraversalSource, p map[string]interface{}) 
*gremlingo.GraphTraversal {return g.V().HasLabel("person")}}, 
     "g_addV_propertyXmapX": {func(g *gremlingo.GraphTraversalSource, p 
map[string]interface{}) *gremlingo.GraphTraversal {return 
g.AddV().Property("name", "foo").Property("age", 42)}, func(g 
*gremlingo.GraphTraversalSource, p map[string]interface{}) 
*gremlingo.GraphTraversal {return g.V().Has("name", "foo")}}, 
     "g_addV_propertyXsingle_mapX": {func(g *gremlingo.GraphTraversalSource, p 
map[string]interface{}) *gremlingo.GraphTraversal {return 
g.AddV().Property(gremlingo.Cardinality.Single, "name", 
"foo").Property(gremlingo.Cardinality.Single, "age", 42)}, func(g 
*gremlingo.GraphTraversalSource, p map[string]interface{}) 
*gremlingo.GraphTraversal {return g.V().Has("name", "foo")}}, 
+    "g_V_hasXname_fooX_propertyXname_setXbarX_age_43X": {func(g 
*gremlingo.GraphTraversalSource, p map[string]interface{}) 
*gremlingo.GraphTraversal {return 
g.AddV().Property(gremlingo.Cardinality.Single, "name", "foo").Property("age", 
42)}, func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) 
*gremlingo.GraphTraversal {return g.V().Has("name", 
"foo").Property(gremlingo.Cardinality.Set, "name", "bar").Property("age", 43)}, 
func(g *gremlingo.GraphTraversalSource, p map[string [...]
+    "g_V_hasXname_fooX_propertyXset_name_bar_age_singleX43XX": {func(g 
*gremlingo.GraphTraversalSource, p map[string]interface{}) 
*gremlingo.GraphTraversal {return 
g.AddV().Property(gremlingo.Cardinality.Single, "name", "foo").Property("age", 
42)}, func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) 
*gremlingo.GraphTraversal {return g.V().Has("name", 
"foo").Property(gremlingo.Cardinality.Set, "name", 
"bar").Property(gremlingo.Cardinality.Single, "age", 43)}, func(g *gremlin [...]
     "g_addV_propertyXnullX": {func(g *gremlingo.GraphTraversalSource, p 
map[string]interface{}) *gremlingo.GraphTraversal {return g.AddV("person")}, 
func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) 
*gremlingo.GraphTraversal {return g.V().HasLabel("person").Values()}}, 
     "g_addV_propertyXemptyX": {func(g *gremlingo.GraphTraversalSource, p 
map[string]interface{}) *gremlingo.GraphTraversal {return g.AddV("person")}, 
func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) 
*gremlingo.GraphTraversal {return g.V().HasLabel("person").Values()}}, 
     "g_addV_propertyXset_nullX": {func(g *gremlingo.GraphTraversalSource, p 
map[string]interface{}) *gremlingo.GraphTraversal {return g.AddV("foo")}, 
func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) 
*gremlingo.GraphTraversal {return g.V().HasLabel("foo").Values()}}, 
@@ -691,6 +693,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 7b315da64a..3fc3e3d225 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 {
@@ -297,8 +327,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 328c7f3ed9..20b6b99751 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');
@@ -1453,6 +1453,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);
@@ -1564,5 +1603,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 b8e867e2fc..8d5966307a 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,
@@ -448,6 +449,8 @@ const gremlins = {
     g_addV_propertyXlabel_personX: [function({g}) { return 
g.addV().property(T.label,"person") }, function({g}) { return 
g.V().hasLabel("person") }], 
     g_addV_propertyXmapX: [function({g}) { return 
g.addV().property("name","foo").property("age",42) }, function({g}) { return 
g.V().has("name","foo") }], 
     g_addV_propertyXsingle_mapX: [function({g}) { return 
g.addV().property(Cardinality.single,"name","foo").property(Cardinality.single,"age",42)
 }, function({g}) { return g.V().has("name","foo") }], 
+    g_V_hasXname_fooX_propertyXname_setXbarX_age_43X: [function({g}) { return 
g.addV().property(Cardinality.single,"name","foo").property("age",42) }, 
function({g}) { return 
g.V().has("name","foo").property(Cardinality.set,"name","bar").property("age",43)
 }, function({g}) { return g.V().has("name","foo") }, function({g}) { return 
g.V().has("name","bar") }, function({g}) { return g.V().has("age",43) }, 
function({g}) { return g.V().has("age",42) }], 
+    g_V_hasXname_fooX_propertyXset_name_bar_age_singleX43XX: [function({g}) { 
return g.addV().property(Cardinality.single,"name","foo").property("age",42) }, 
function({g}) { return 
g.V().has("name","foo").property(Cardinality.set,"name","bar").property(Cardinality.single,"age",43)
 }, function({g}) { return g.V().has("name","foo") }, function({g}) { return 
g.V().has("name","bar") }, function({g}) { return g.V().has("age",43) }, 
function({g}) { return g.V().has("age",42) }], 
     g_addV_propertyXnullX: [function({g}) { return g.addV("person") }, 
function({g}) { return g.V().hasLabel("person").values() }], 
     g_addV_propertyXemptyX: [function({g}) { return g.addV("person") }, 
function({g}) { return g.V().hasLabel("person").values() }], 
     g_addV_propertyXset_nullX: [function({g}) { return g.addV("foo") }, 
function({g}) { return g.V().hasLabel("foo").values() }], 
@@ -710,6 +713,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 4b04e21f58..876524c934 100644
--- a/gremlin-language/src/main/antlr4/Gremlin.g4
+++ b/gremlin-language/src/main/antlr4/Gremlin.g4
@@ -575,6 +575,7 @@ traversalMethod_not
 traversalMethod_option
        : 'option' LPAREN traversalPredicate COMMA nestedTraversal RPAREN 
#traversalMethod_option_Predicate_Traversal
        | 'option' LPAREN traversalMergeArgument COMMA 
genericLiteralMapNullableArgument RPAREN #traversalMethod_option_Merge_Map
+       | 'option' LPAREN traversalMergeArgument COMMA 
genericLiteralMapNullableArgument COMMA traversalCardinality RPAREN 
#traversalMethod_option_Merge_Map_Cardinality
        | 'option' LPAREN traversalMergeArgument COMMA nestedTraversal RPAREN 
#traversalMethod_option_Merge_Traversal
        | 'option' LPAREN genericLiteralArgument COMMA nestedTraversal RPAREN 
#traversalMethod_option_Object_Traversal
        | 'option' LPAREN nestedTraversal RPAREN 
#traversalMethod_option_Traversal
@@ -916,7 +917,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 c44c33ecb6..cbcac3240a 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))],
 
@@ -430,6 +430,8 @@ world.gremlins = {
     'g_addV_propertyXlabel_personX': [(lambda 
g:g.addV().property(T.label,'person')), (lambda g:g.V().hasLabel('person'))], 
     'g_addV_propertyXmapX': [(lambda 
g:g.addV().property('name','foo').property('age',42)), (lambda 
g:g.V().has('name','foo'))], 
     'g_addV_propertyXsingle_mapX': [(lambda 
g:g.addV().property(Cardinality.single,'name','foo').property(Cardinality.single,'age',42)),
 (lambda g:g.V().has('name','foo'))], 
+    'g_V_hasXname_fooX_propertyXname_setXbarX_age_43X': [(lambda 
g:g.addV().property(Cardinality.single,'name','foo').property('age',42)), 
(lambda 
g:g.V().has('name','foo').property(Cardinality.set_,'name','bar').property('age',43)),
 (lambda g:g.V().has('name','foo')), (lambda g:g.V().has('name','bar')), 
(lambda g:g.V().has('age',43)), (lambda g:g.V().has('age',42))], 
+    'g_V_hasXname_fooX_propertyXset_name_bar_age_singleX43XX': [(lambda 
g:g.addV().property(Cardinality.single,'name','foo').property('age',42)), 
(lambda 
g:g.V().has('name','foo').property(Cardinality.set_,'name','bar').property(Cardinality.single,'age',43)),
 (lambda g:g.V().has('name','foo')), (lambda g:g.V().has('name','bar')), 
(lambda g:g.V().has('age',43)), (lambda g:g.V().has('age',42))], 
     'g_addV_propertyXnullX': [(lambda g:g.addV('person')), (lambda 
g:g.V().hasLabel('person').values())], 
     'g_addV_propertyXemptyX': [(lambda g:g.addV('person')), (lambda 
g:g.V().hasLabel('person').values())], 
     'g_addV_propertyXset_nullX': [(lambda g:g.addV('foo')), (lambda 
g:g.V().hasLabel('foo').values())], 
@@ -692,6 +694,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/AddVertex.feature
 
b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/AddVertex.feature
index 505969a59d..e46c91e064 100644
--- 
a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/AddVertex.feature
+++ 
b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/AddVertex.feature
@@ -429,7 +429,6 @@ Feature: Step - addV()
     Then the result should have a count of 1
     And the graph should return 1 for count of "g.V().hasLabel(\"person\")"
 
- 
   Scenario: g_addV_propertyXmapX
     Given the empty graph
     And the traversal of
@@ -450,6 +449,42 @@ Feature: Step - addV()
     Then the result should have a count of 1
     And the graph should return 1 for count of "g.V().has(\"name\",\"foo\")"
 
+  @MultiMetaProperties
+  Scenario: g_V_hasXname_fooX_propertyXname_setXbarX_age_43X
+    Given the empty graph
+    And the graph initializer of
+      """
+      g.addV().property(Cardinality.single, "name", "foo").property("age", 42)
+      """
+    And the traversal of
+      """
+      g.V().has('name','foo').property(["name": Cardinality.set("bar"), "age": 
43 ])
+      """
+    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(\"name\",\"foo\")"
+    And the graph should return 1 for count of "g.V().has(\"name\",\"bar\")"
+    And the graph should return 1 for count of "g.V().has(\"age\",43)"
+    And the graph should return 0 for count of "g.V().has(\"age\",42)"
+
+  @MultiMetaProperties
+  Scenario: g_V_hasXname_fooX_propertyXset_name_bar_age_singleX43XX
+    Given the empty graph
+    And the graph initializer of
+      """
+      g.addV().property(Cardinality.single, "name", "foo").property("age", 42)
+      """
+    And the traversal of
+      """
+      g.V().has('name','foo').property(Cardinality.set, ["name":"bar", "age": 
Cardinality.single(43) ])
+      """
+    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(\"name\",\"foo\")"
+    And the graph should return 1 for count of "g.V().has(\"name\",\"bar\")"
+    And the graph should return 1 for count of "g.V().has(\"age\",43)"
+    And the graph should return 0 for count of "g.V().has(\"age\",42)"
+
   Scenario: g_addV_propertyXnullX
     Given the empty graph
     And the traversal of
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

Reply via email to