You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tinkerpop.apache.org by sp...@apache.org on 2023/07/07 15:03:19 UTC
[tinkerpop] branch TINKERPOP-2957 updated (c81786c1ee -> c6ca7eb5dc)
This is an automated email from the ASF dual-hosted git repository.
spmallette pushed a change to branch TINKERPOP-2957
in repository https://gitbox.apache.org/repos/asf/tinkerpop.git
discard c81786c1ee TINKERPOP-2957 allow cardinality to be specified for mergeV()
add b355b6d002 Changelog formatting CTR
add d9d8d76d1a Merge branch '3.6-dev'
new c6ca7eb5dc TINKERPOP-2957 allow cardinality to be specified for mergeV()
This update added new revisions after undoing existing revisions.
That is to say, some revisions that were in the old version of the
branch are not in the new version. This situation occurs
when a user --force pushes a change and generates a repository
containing something like this:
* -- * -- B -- O -- O -- O (c81786c1ee)
\
N -- N -- N refs/heads/TINKERPOP-2957 (c6ca7eb5dc)
You should already have received notification emails for all of the O
revisions, and so the following emails describe only the N revisions
from the common base, B.
Any revisions marked "omit" are not gone; other references still
refer to them. Any revisions marked "discard" are gone forever.
The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
Summary of changes:
CHANGELOG.asciidoc | 14 ++++++-------
docs/src/reference/gremlin-variants.asciidoc | 24 ++++++++++++++++++++--
.../Docs/Reference/GremlinVariantsTests.cs | 2 ++
3 files changed, 31 insertions(+), 9 deletions(-)
[tinkerpop] 01/01: TINKERPOP-2957 allow cardinality to be specified for mergeV()
Posted by sp...@apache.org.
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 c6ca7eb5dc3f275d125d9319a03d1d5cdea9e742
Author: Stephen Mallette <st...@amazon.com>
AuthorDate: Mon Jun 19 11:27:12 2023 -0400
TINKERPOP-2957 allow cardinality to be specified for mergeV()
Provides a nicer way to allow users to control cardinality of property values when using mergeV() 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.6.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..47f32d0f21 100644
--- a/CHANGELOG.asciidoc
+++ b/CHANGELOG.asciidoc
@@ -64,6 +64,7 @@ image::https://raw.githubusercontent.com/apache/tinkerpop/master/docs/static/ima
This release also includes changes from <<release-3-5-7, 3.5.7>>.
+* Allowed `mergeV()` and `property(Map)` to more easily define `Cardinality` values for properties for `onMatch` and `onCreate` options.
* Added `text/plain` MIME type to the HTTP endpoint to return a Gremlin Console-like representation of the data.
* Added GraphBinary serialization option to the HTTP endpoint.
* Fixed bug with `fail` step not working with a `VertexProgram` running on the server.
diff --git a/docs/src/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.6.x.asciidoc b/docs/src/upgrade/release-3.6.x.asciidoc
index d54ad7a38c..4bb957a376 100644
--- a/docs/src/upgrade/release-3.6.x.asciidoc
+++ b/docs/src/upgrade/release-3.6.x.asciidoc
@@ -48,6 +48,77 @@ $ curl -H "Accept:text/plain" -X POST -d "{\"gremlin\":\"g.V()\"}" "http://local
See: link:https://issues.apache.org/jira/browse/TINKERPOP-2947[TINKERPOP-2947]
+==== 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]
+
== TinkerPop 3.6.4
*Release Date: May 12, 2023*
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/JavaTranslator.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/JavaTranslator.java
index 91bc19cc0f..830ca2093e 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/JavaTranslator.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/JavaTranslator.java
@@ -26,6 +26,7 @@ import org.apache.tinkerpop.gremlin.process.traversal.Translator;
import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
import org.apache.tinkerpop.gremlin.process.traversal.TraversalSource;
import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal;
+import org.apache.tinkerpop.gremlin.process.traversal.lambda.CardinalityValueTraversal;
import org.apache.tinkerpop.gremlin.process.traversal.step.util.BulkSet;
import org.apache.tinkerpop.gremlin.process.traversal.step.util.Tree;
import org.apache.tinkerpop.gremlin.process.traversal.strategy.TraversalStrategyProxy;
@@ -111,14 +112,33 @@ public final class JavaTranslator<S extends TraversalSource, T extends Traversal
if (object instanceof Bytecode.Binding)
return translateObject(((Bytecode.Binding) object).value());
else if (object instanceof Bytecode) {
- try {
- final Traversal.Admin<?, ?> traversal = (Traversal.Admin) this.anonymousTraversalStart.invoke(null);
- for (final Bytecode.Instruction instruction : ((Bytecode) object).getStepInstructions()) {
- invokeMethod(traversal, Traversal.class, instruction.getOperator(), instruction.getArguments());
+ // source based bytecode at this stage of translation could have special meaning, but generally this is
+ // going to spawn a new anonymous traversal.
+ final Bytecode bc = (Bytecode) object;
+ if (!bc.getSourceInstructions().isEmpty()) {
+ // currently, valid source instructions will be singly defined. would be odd to get this error. could
+ // be just bad construction from a language variant if it appears. maybe better as an assertion but
+ // third-party variants might benefit from this error
+ if (bc.getSourceInstructions().size() != 1) {
+ throw new IllegalStateException("More than one source instruction defined in bytecode");
+ }
+
+ final Bytecode.Instruction inst = bc.getSourceInstructions().get(0);
+ if (inst.getOperator().equals(CardinalityValueTraversal.class.getSimpleName())) {
+ return CardinalityValueTraversal.from(inst);
+ } else {
+ throw new IllegalStateException(String.format("Unknown source instruction for %s", inst.getOperator()));
+ }
+ } else {
+ try {
+ final Traversal.Admin<?, ?> traversal = (Traversal.Admin) this.anonymousTraversalStart.invoke(null);
+ for (final Bytecode.Instruction instruction : bc.getStepInstructions()) {
+ invokeMethod(traversal, Traversal.class, instruction.getOperator(), instruction.getArguments());
+ }
+ return traversal;
+ } catch (final Throwable e) {
+ throw new IllegalStateException(e.getMessage());
}
- return traversal;
- } catch (final Throwable e) {
- throw new IllegalStateException(e.getMessage());
}
} else if (object instanceof TraversalStrategyProxy) {
final Map<String, Object> map = new HashMap<>();
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/DefaultGremlinBaseVisitor.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/DefaultGremlinBaseVisitor.java
index 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