This is an automated email from the ASF dual-hosted git repository. spmallette pushed a commit to branch TINKERPOP-2957 in repository https://gitbox.apache.org/repos/asf/tinkerpop.git
commit c4e32b786b6556275d409b7cee6a014b372d828e Author: Stephen Mallette <[email protected]> AuthorDate: Mon Jun 19 11:27:12 2023 -0400 TINKERPOP-2957 allow cardinality to be specified for mergeV() Provides a nicer way to allow users to control cardinality of property values when using mergeV() and property(Map). A bit tough to get this in without changes to the serializers but by treating the CardinalityValue as a special form of Bytecode it allowed this change to keep the serializers sacred. --- CHANGELOG.asciidoc | 1 + docs/src/reference/gremlin-variants.asciidoc | 24 +++- docs/src/reference/the-traversal.asciidoc | 52 ++++++--- docs/src/upgrade/release-3.7.x.asciidoc | 71 ++++++++++++ .../tinkerpop/gremlin/jsr223/JavaTranslator.java | 34 ++++-- .../grammar/DefaultGremlinBaseVisitor.java | 5 + .../language/grammar/GenericLiteralVisitor.java | 18 ++- .../language/grammar/TraversalMethodVisitor.java | 15 +++ .../gremlin/process/traversal/Bytecode.java | 4 +- .../gremlin/process/traversal/Translator.java | 14 ++- .../traversal/dsl/graph/GraphTraversal.java | 56 ++++++++- .../lambda/CardinalityValueTraversal.java | 82 +++++++++++++ .../traversal/step/map/MergeVertexStep.java | 17 ++- .../process/traversal/step/util/Parameters.java | 1 - .../traversal/translator/DotNetTranslator.java | 10 ++ .../traversal/translator/GolangTranslator.java | 10 ++ .../traversal/translator/GroovyTranslator.java | 9 ++ .../traversal/translator/JavascriptTranslator.java | 9 ++ .../traversal/translator/PythonTranslator.java | 9 ++ .../gremlin/structure/VertexProperty.java | 18 ++- .../structure/io/binary/GraphBinaryWriter.java | 4 +- .../io/binary/types/TransformSerializer.java | 2 +- .../grammar/GeneralLiteralVisitorTest.java | 40 ++++++- .../gremlin/process/traversal/CardinalityTest.java | 68 +++++++++++ .../traversal/translator/DotNetTranslatorTest.java | 5 + .../traversal/translator/GolangTranslatorTest.java | 6 + .../traversal/translator/GroovyTranslatorTest.java | 6 + .../translator/JavascriptTranslatorTest.java | 6 + .../traversal/translator/PythonTranslatorTest.java | 14 +++ .../Process/Traversal/CardinalityValue.cs | 92 +++++++++++++++ .../Process/Traversal/GraphTraversal.cs | 9 ++ .../Docs/Reference/GremlinVariantsTests.cs | 2 + .../Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs | 9 ++ .../Process/Traversal/CardinalityValueTests.cs | 55 +++++++++ gremlin-go/driver/cucumber/gremlin.go | 9 ++ gremlin-go/driver/traversal.go | 34 +++++- gremlin-javascript/build/generate.groovy | 1 + .../lib/process/graph-traversal.js | 42 ++++++- .../gremlin-javascript/test/cucumber/gremlin.js | 10 ++ gremlin-language/src/main/antlr4/Gremlin.g4 | 9 +- gremlin-python/build/generate.groovy | 2 +- .../python/gremlin_python/process/traversal.py | 18 +++ gremlin-python/src/main/python/radish/gremlin.py | 11 +- .../gremlin/test/features/map/AddVertex.feature | 37 +++++- .../gremlin/test/features/map/MergeVertex.feature | 129 +++++++++++++++++++++ 45 files changed, 1027 insertions(+), 52 deletions(-) diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index 3810e2a4b5..31dd4b11ba 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -25,6 +25,7 @@ image::https://raw.githubusercontent.com/apache/tinkerpop/master/docs/static/ima This release also includes changes from <<release-3-6-XXX, 3.6.XXX>>. +* Allowed `mergeV()` and `property(Map)` to more easily define `Cardinality` values for properties for `onMatch` and `onCreate` options. * Removed `connectOnStartup` configuration option from gremlin-javascript. * Changed `Gremlin.version()` to read from the more specifically named `tinkerpop-version` attribute. * Added warning on vertex property cardinality mismatch when reading GraphML. diff --git a/docs/src/reference/gremlin-variants.asciidoc b/docs/src/reference/gremlin-variants.asciidoc index cf4e064fb8..515964468f 100644 --- a/docs/src/reference/gremlin-variants.asciidoc +++ b/docs/src/reference/gremlin-variants.asciidoc @@ -510,6 +510,10 @@ To make the code more readable and close to the Gremlin query language), you can var order = gremlingo.Order ---- +Finally, the enum construct for `Cardinality` cannot have functions attached to it the way it can be done in Java, +therefore cardinality functions that take a value like `list()`, `set()`, and `single()` are referenced from a +`CardinalityValue` class rather than `Cardinality` itself. + [[gremlin-go-limitations]] === Limitations @@ -1308,6 +1312,8 @@ const pop = gremlin.process.pop const order = gremlin.process.order const scope = gremlin.process.scope const t = gremlin.process.t +const cardinality = gremlin.process.cardinality +const CardinalityValue = gremlin.process.CardinalityValue ---- By defining these imports it becomes possible to write Gremlin in the more shorthand, canonical style that is @@ -1570,6 +1576,10 @@ bits of conflicting Gremlin get an underscore appended as a suffix: *Steps* - <<from-step,from_()>>, <<in-step,in_()>>, <<with-step,with_()>> *Tokens* - `Direction.from_` +In addition, the enum construct for `Cardinality` cannot have functions attached to it the way it can be done in Java, +therefore cardinality functions that take a value like `list()`, `set()`, and `single()` are referenced from a +`CardinalityValue` class rather than `Cardinality` itself. + Gremlin allows for `Map` instances to include `null` keys, but `null` keys in Javascript have some interesting behavior as in: @@ -1605,6 +1615,7 @@ g.V().hasLabel('person').groupCount().by('age') Either of the above two options accomplishes the desired goal as both prevent `groupCount()` from having to process the possibility of `null`. + [[gremlin-javascript-limitations]] === Limitations @@ -1919,14 +1930,18 @@ g.V().Repeat(__.Out()).Times(2).Values<string>("name"); Gremlin allows for `Map` instances to include `null` keys, but `null` keys in C# `Dictionary` instances are not allowed. It is therefore necessary to rewrite a traversal such as: -[source,javascript] +[source,csharp] ---- g.V().groupCount().by('age') ---- where "age" is not a valid key for all vertices in a way that will remove the need for a `null` to be returned. -[source,javascript] +Finally, the enum construct for `Cardinality` cannot have functions attached to it the way it can be done in Java, +therefore cardinality functions that take a value like `list()`, `set()`, and `single()` are referenced from a +`CardinalityValue` class rather than `Cardinality` itself. + +[source,csharp] ---- g.V().has('age').groupCount().by('age') g.V().hasLabel('person').groupCount().by('age') @@ -2066,6 +2081,7 @@ from gremlin_python.driver.driver_remote_connection import DriverRemoteConnectio from gremlin_python.process.traversal import T from gremlin_python.process.traversal import Order from gremlin_python.process.traversal import Cardinality +from gremlin_python.process.traversal import CardinalityValue from gremlin_python.process.traversal import Column from gremlin_python.process.traversal import Direction from gremlin_python.process.traversal import Operator @@ -2457,6 +2473,10 @@ bits of conflicting Gremlin get an underscore appended as a suffix: *Tokens* - <<a-note-on-scopes,Scope.global_>>, `Direction.from_`, `Operator.sum_` +In addition, the enum construct for `Cardinality` cannot have functions attached to it the way it can be done in Java, +therefore cardinality functions that take a value like `list()`, `set()`, and `single()` are referenced from a +`CardinalityValue` class rather than `Cardinality` itself. + [[gremlin-python-limitations]] === Limitations diff --git a/docs/src/reference/the-traversal.asciidoc b/docs/src/reference/the-traversal.asciidoc index 5ab49721ac..109005ea69 100644 --- a/docs/src/reference/the-traversal.asciidoc +++ b/docs/src/reference/the-traversal.asciidoc @@ -2666,22 +2666,35 @@ Metadata > {} ====================================================================================================================================================================== ---- -In some cases, such as when adding multi-properties or meta-properties to a vertex, one or -more `property` steps can follow the `mergeV()` step. Note that meta-properties and -multi-properties cannot be added directly using just `mergeV()` as a key/value pair such as -`'my-numbers:[1,2,3,4]` will be treated as a `List` object and not a set of multi-property -values. +When working with multi-properties, there are two ways to specify them for `mergeV()`. First, you can specify them +individually using a `CardinalityValue` as the value in the `Map`. The `CardinalityValue` allows you to specify the +value as well as the `Cardinality` for that value. Note that it is only possible to specify one value with this syntax +even if you are using `set` or `list`. [gremlin-groovy] ---- g.mergeV([(T.label):'Dog', name:'Max']). <1> - property(list,'alias','Maximus'). - property(list,'alias','Maxamillion') <2> + option(onCreate, [alias: set('Maximus')]). <2> + property(set,'alias','Maxamillion') <3> g.V().has('name','Max').valueMap().with(WithOptions.tokens) ---- <1> Find or create a vertex for Max. -<2> Whether Max was found or created, add some aliases with list cardinality. +<2> If Max is not found then add an alias of `set` cardinality. +<3> Whether Max was found or created, add another alias with `set` cardinality. + +The second option is to specify `Cardinality` for the entire range of values as follows: + +[gremlin-groovy] +---- +g.mergeV([(T.label):'Dog', name:'Max']). + option(onCreate, [alias: 'Maximus', city: 'Boston'], set) <1> +g.mergeV([(T.label):'Dog', name:'Max']). + option(onCreate, [alias: 'Maximus', city: single('Boston')], set) <2> +---- + +<1> If Max is created then set the alias and city with cardinality of `set`. +<2> If Max is created then set the alias with cardinality of `set` and city with cardinality `single`. More than one vertex can be created by a single `mergeV()` operation. This is done by injecting a `List` of `Map` objects into the traversal and letting them stream into the `mergeV()` @@ -3334,20 +3347,23 @@ g.V(1).property(['city': 'santa fe', 'state': 'new mexico']) <1> g.V(1).property(list,'age',35) <2> g.V(1).property(list, ['city': 'santa fe', 'state': 'new mexico']) <3> g.V(1).valueMap() -g.V(1).property('friendWeight',outE('knows').values('weight').sum(),'acl','private') <4> -g.V(1).properties('friendWeight').valueMap() <5> -g.addV().property(T.label,'person').valueMap().with(WithOptions.tokens) <6> -g.addV().property(null) <7> +g.V(1).property(list, ['age': single(36), 'city': 'wilmington', 'state': 'delaware']) <4> +g.V(1).valueMap() +g.V(1).property('friendWeight',outE('knows').values('weight').sum(),'acl','private') <5> +g.V(1).properties('friendWeight').valueMap() <6> +g.addV().property(T.label,'person').valueMap().with(WithOptions.tokens) <7> +g.addV().property(null) <8> g.addV().property(set, null) ---- -<1> Properties can also take a Map as an argument. +<1> Properties can also take a `Map` as an argument. <2> For vertices, a cardinality can be provided for <<vertex-properties,vertex properties>>. -<3> If a cardinality is specified for a Map then that cardinality will be used for all properties in the map. If you need different cardinalities per property then you should individually add the property values. -<4> It is possible to select the property value (as well as key) via a traversal. -<5> For vertices, the `property()`-step can add meta-properties. -<6> The label value can be specified as a property only at the time a vertex is added and if one is not specified in the addV() -<7> If you pass a `null` value for the Map this will be treated as a no-op and the input will be returned +<3> If a cardinality is specified for a `Map` then that cardinality will be used for all properties in the map. +<4> Assign the `Cardinality` individually to override the specified `list` or the default cardinality if not specified. +<5> It is possible to select the property value (as well as key) via a traversal. +<6> For vertices, the `property()`-step can add meta-properties. +<7> The label value can be specified as a property only at the time a vertex is added and if one is not specified in the addV() +<8> If you pass a `null` value for the Map this will be treated as a no-op and the input will be returned *Additional References* diff --git a/docs/src/upgrade/release-3.7.x.asciidoc b/docs/src/upgrade/release-3.7.x.asciidoc index 270afe904a..368ba70437 100644 --- a/docs/src/upgrade/release-3.7.x.asciidoc +++ b/docs/src/upgrade/release-3.7.x.asciidoc @@ -60,6 +60,77 @@ gremlin> g.union(V().has('name','vadas'), See: link:https://issues.apache.org/jira/browse/TINKERPOP-2873[TINKERPOP-2873] +==== Map and Cardinality + +Relatively recent changes to the Gremlin language have allowed properties to be set by way of a `Map`. As it pertains +to vertices, a `Map` can be given to `mergeV()` and `property()` steps. The limitation was that setting `Cardinality` +with this syntax was not possible without reverting back to `property()` steps that took a `Cardinality` as an argument +in some way. The following paragraphs show how changes for in 3.6.5 make this syntax much better for multi-properties. + +The `mergeV()` step makes it much easier to write upsert-like traversals. Of course, if you had a graph that required +the use of multi-properties, some of the ease of `mergeV()` was lost. It typically meant falling back to traversals +using `sideEffect()` or similar direct uses of `property()` to allow it to work properly: + +[source,groovy] +---- +g.mergeV([(T.id): '1234']). + option(onMatch, sideEffect(property(single,'age', 20). + property(set,'city','miami')).constant([:])) +---- + +For this version, `mergeV()` gets two new bits of syntax. First, it is possible to individually define the cardinality +for each property value in the `Map` for `onCreate` or `onMerge` events. Therefore, the above example could be written +as: + +[source,text] +---- +gremlin> g.addV().property(id,1234).property('age',19).property(set, 'city', 'detroit') +==>v[1234] +gremlin> g.mergeV([(T.id): 1234]). +......1> option(onMatch, ['age': single(20), 'city': set('miami')]) +==>v[1234] +gremlin> g.V(1234).valueMap() +==>[city:[detroit,miami],age:[20]] +---- + +The other option available is to provide a default `Cardinality` to the `option()` as follows, continuing from the +previous example: + +[source,text] +---- +gremlin> g.mergeV([(T.id): 1234]). +......1> option(onMatch, ['age': 21, 'city': set('orlando')], single) +==>v[1234] +gremlin> g.mergeV([(T.id): 1234]). +......1> option(onMatch, ['age': 22, 'city': set('boston')], single) +==>v[1234] +gremlin> g.V(1234).valueMap() +==>[city:[detroit,miami,orlando,boston],age:[22]] +---- + +In the above example, any property value that does not have its cardinality explicitly defined, will be assumed to be +the cardinality of the argument specified. + +For `property(Map)` the `Cardinality` could be set universally for the `Map` with `property(Cardinality, Map)` but +there was no mechanism to set that value individually. Using the same pattern above and constructing a +`CardinalityValue` now allows this possibility. + +[source,text] +---- +gremlin> g.addV().property(id,1234).property('age',19).property(set, 'city', 'detroit') +==>v[1234] +gremlin> g.V(1234).property(['age': 20, 'city': set('miami')]) +==>v[1234] +gremlin> g.V(1234).property(['age': single(21), 'city': set('orlando')]) +==>v[1234] +gremlin> g.V(1234).property(single, ['age': 21, 'city': set('boston')]) +==>v[1234] +gremlin> g.V(1234).valueMap() +==>[city:[detroit,miami,orlando,boston],age:[21]] +---- + +See: link:https://issues.apache.org/jira/browse/TINKERPOP-2957[TINKERPOP-2957] + ==== Properties on Elements ===== Introduction diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/JavaTranslator.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/JavaTranslator.java index 91bc19cc0f..830ca2093e 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/JavaTranslator.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/JavaTranslator.java @@ -26,6 +26,7 @@ import org.apache.tinkerpop.gremlin.process.traversal.Translator; import org.apache.tinkerpop.gremlin.process.traversal.Traversal; import org.apache.tinkerpop.gremlin.process.traversal.TraversalSource; import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal; +import org.apache.tinkerpop.gremlin.process.traversal.lambda.CardinalityValueTraversal; import org.apache.tinkerpop.gremlin.process.traversal.step.util.BulkSet; import org.apache.tinkerpop.gremlin.process.traversal.step.util.Tree; import org.apache.tinkerpop.gremlin.process.traversal.strategy.TraversalStrategyProxy; @@ -111,14 +112,33 @@ public final class JavaTranslator<S extends TraversalSource, T extends Traversal if (object instanceof Bytecode.Binding) return translateObject(((Bytecode.Binding) object).value()); else if (object instanceof Bytecode) { - try { - final Traversal.Admin<?, ?> traversal = (Traversal.Admin) this.anonymousTraversalStart.invoke(null); - for (final Bytecode.Instruction instruction : ((Bytecode) object).getStepInstructions()) { - invokeMethod(traversal, Traversal.class, instruction.getOperator(), instruction.getArguments()); + // source based bytecode at this stage of translation could have special meaning, but generally this is + // going to spawn a new anonymous traversal. + final Bytecode bc = (Bytecode) object; + if (!bc.getSourceInstructions().isEmpty()) { + // currently, valid source instructions will be singly defined. would be odd to get this error. could + // be just bad construction from a language variant if it appears. maybe better as an assertion but + // third-party variants might benefit from this error + if (bc.getSourceInstructions().size() != 1) { + throw new IllegalStateException("More than one source instruction defined in bytecode"); + } + + final Bytecode.Instruction inst = bc.getSourceInstructions().get(0); + if (inst.getOperator().equals(CardinalityValueTraversal.class.getSimpleName())) { + return CardinalityValueTraversal.from(inst); + } else { + throw new IllegalStateException(String.format("Unknown source instruction for %s", inst.getOperator())); + } + } else { + try { + final Traversal.Admin<?, ?> traversal = (Traversal.Admin) this.anonymousTraversalStart.invoke(null); + for (final Bytecode.Instruction instruction : bc.getStepInstructions()) { + invokeMethod(traversal, Traversal.class, instruction.getOperator(), instruction.getArguments()); + } + return traversal; + } catch (final Throwable e) { + throw new IllegalStateException(e.getMessage()); } - return traversal; - } catch (final Throwable e) { - throw new IllegalStateException(e.getMessage()); } } else if (object instanceof TraversalStrategyProxy) { final Map<String, Object> map = new HashMap<>(); diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/DefaultGremlinBaseVisitor.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/DefaultGremlinBaseVisitor.java index f8884165de..647a6de253 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/DefaultGremlinBaseVisitor.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/DefaultGremlinBaseVisitor.java @@ -1548,4 +1548,9 @@ public class DefaultGremlinBaseVisitor<T> extends AbstractParseTreeVisitor<T> im */ @Override public T visitStringLiteralVarargs(final GremlinParser.StringLiteralVarargsContext ctx) { notImplemented(ctx); return null; } + /** + * {@inheritDoc} + */ + @Override + public T visitTraversalMethod_option_Merge_Map_Cardinality(final GremlinParser.TraversalMethod_option_Merge_Map_CardinalityContext ctx) { notImplemented(ctx); return null; } } \ No newline at end of file diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/GenericLiteralVisitor.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/GenericLiteralVisitor.java index bf8000bccd..b9efbd352d 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/GenericLiteralVisitor.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/GenericLiteralVisitor.java @@ -504,7 +504,23 @@ public class GenericLiteralVisitor extends DefaultGremlinBaseVisitor<Object> { */ @Override public Object visitTraversalCardinality(final GremlinParser.TraversalCardinalityContext ctx) { - return TraversalEnumParser.parseTraversalEnumFromContext(VertexProperty.Cardinality.class, ctx); + // could be Cardinality.single() the method or single the enum so grab the right child index based on + // number of children + if (ctx.getChildCount() == 1) { + return TraversalEnumParser.parseTraversalEnumFromContext(VertexProperty.Cardinality.class, ctx); + } else { + final int idx = ctx.getChildCount() == 5 ? 1 : 0; + final String specifiedCard = ctx.children.get(idx).getText(); + if (specifiedCard.endsWith(VertexProperty.Cardinality.single.name())) + return VertexProperty.Cardinality.single(visitGenericLiteral(ctx.genericLiteral())); + else if (specifiedCard.endsWith(VertexProperty.Cardinality.list.name())) + return VertexProperty.Cardinality.list(visitGenericLiteral(ctx.genericLiteral())); + else if (specifiedCard.endsWith(VertexProperty.Cardinality.set.name())) + return VertexProperty.Cardinality.set(visitGenericLiteral(ctx.genericLiteral())); + else + throw new GremlinParserException(String.format( + "A Cardinality value not recognized: %s", specifiedCard)); + } } /** diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/TraversalMethodVisitor.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/TraversalMethodVisitor.java index bb02fface8..1862a501da 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/TraversalMethodVisitor.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/TraversalMethodVisitor.java @@ -18,6 +18,7 @@ */ package org.apache.tinkerpop.gremlin.language.grammar; +import org.apache.tinkerpop.gremlin.process.traversal.Merge; import org.apache.tinkerpop.gremlin.process.traversal.Operator; import org.apache.tinkerpop.gremlin.process.traversal.Order; import org.apache.tinkerpop.gremlin.process.traversal.Traversal; @@ -1060,6 +1061,20 @@ public class TraversalMethodVisitor extends TraversalRootVisitor<GraphTraversal> antlr.tvisitor.visitNestedTraversal(ctx.nestedTraversal())); } + /** + * {@inheritDoc} + */ + @Override + public Traversal visitTraversalMethod_option_Merge_Map_Cardinality(final GremlinParser.TraversalMethod_option_Merge_Map_CardinalityContext ctx) { + if (ctx.genericLiteralMapNullableArgument().nullLiteral() != null) { + return this.graphTraversal.option(antlr.argumentVisitor.parseMerge(ctx.traversalMergeArgument()), (Map) null); + } + + return graphTraversal.option(antlr.argumentVisitor.parseMerge(ctx.traversalMergeArgument()), + (Map) new GenericLiteralVisitor(antlr).visitGenericLiteralMap(ctx.genericLiteralMapNullableArgument().genericLiteralMap()), + TraversalEnumParser.parseTraversalEnumFromContext(Cardinality.class, ctx.traversalCardinality())); + } + /** * {@inheritDoc} */ diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Bytecode.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Bytecode.java index dc8eca3add..5b88cea5fc 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Bytecode.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Bytecode.java @@ -47,7 +47,7 @@ import java.util.Set; * * @author Marko A. Rodriguez (http://markorodriguez.com) */ -public final class Bytecode implements Cloneable, Serializable { +public class Bytecode implements Cloneable, Serializable { private static final Object[] EMPTY_ARRAY = new Object[]{}; @@ -56,7 +56,7 @@ public final class Bytecode implements Cloneable, Serializable { public Bytecode() {} - Bytecode(final String sourceName, final Object... arguments) { + public Bytecode(final String sourceName, final Object... arguments) { this.sourceInstructions.add(new Instruction(sourceName, flattenArguments(arguments))); } diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Translator.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Translator.java index 0be7555797..120176821a 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Translator.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Translator.java @@ -19,6 +19,7 @@ package org.apache.tinkerpop.gremlin.process.traversal; +import org.apache.tinkerpop.gremlin.process.traversal.lambda.CardinalityValueTraversal; import org.apache.tinkerpop.gremlin.process.traversal.strategy.TraversalStrategyProxy; import org.apache.tinkerpop.gremlin.structure.Edge; import org.apache.tinkerpop.gremlin.structure.Vertex; @@ -231,6 +232,11 @@ public interface Translator<S, T> { */ protected abstract Script produceScript(final P<?> p); + /** + * Take the {@link Bytecode} and write the syntax for it directly to the member {@link #script} variable. + */ + protected abstract Script produceCardinalityValue(final Bytecode o); + /** * For each operator argument, if withParameters set true, try parametrization as follows: * @@ -272,7 +278,13 @@ public interface Translator<S, T> { if (object instanceof Bytecode.Binding) { return script.getBoundKeyOrAssign(withParameters, ((Bytecode.Binding) object).variable()); } else if (object instanceof Bytecode) { - return produceScript(getAnonymousTraversalPrefix(), (Bytecode) object); + final Bytecode bc = (Bytecode) object; + if (bc.getSourceInstructions().size() == 1 && + bc.getSourceInstructions().get(0).getOperator().equals(CardinalityValueTraversal.class.getSimpleName())) { + return produceCardinalityValue(bc); + } else { + return produceScript(getAnonymousTraversalPrefix(), bc); + } } else if (object instanceof Traversal) { return convertToScript(((Traversal) object).asAdmin().getBytecode()); } else if (object instanceof String) { diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.java index 4251683860..ddc8f5d9da 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.java @@ -36,6 +36,7 @@ import org.apache.tinkerpop.gremlin.process.traversal.Scope; import org.apache.tinkerpop.gremlin.process.traversal.Step; import org.apache.tinkerpop.gremlin.process.traversal.Traversal; import org.apache.tinkerpop.gremlin.process.traversal.Traverser; +import org.apache.tinkerpop.gremlin.process.traversal.lambda.CardinalityValueTraversal; import org.apache.tinkerpop.gremlin.process.traversal.lambda.ColumnTraversal; import org.apache.tinkerpop.gremlin.process.traversal.lambda.ConstantTraversal; import org.apache.tinkerpop.gremlin.process.traversal.lambda.FunctionTraverser; @@ -2541,6 +2542,8 @@ public interface GraphTraversal<S, E> extends Traversal<S, E> { * If a {@link Map} is supplied then each of the key/value pairs in the map will * be added as property. This method is the long-hand version of looping through the * {@link #property(Object, Object, Object...)} method for each key/value pair supplied. + * If a {@link CardinalityValueTraversal} is specified as a value then it will override any + * {@link VertexProperty.Cardinality} specified for the {@code key}. * <p /> * This method is effectively calls {@link #property(VertexProperty.Cardinality, Object, Object, Object...)} * as {@code property(null, key, value, keyValues}. @@ -2554,13 +2557,21 @@ public interface GraphTraversal<S, E> extends Traversal<S, E> { */ public default GraphTraversal<S, E> property(final Object key, final Object value, final Object... keyValues) { if (key instanceof VertexProperty.Cardinality) { - if (value instanceof Map) { //Handle the property(Cardinality, Map) signature - final Map<Object, Object> map = (Map)value; + if (value instanceof Map) { + // Handle the property(Cardinality, Map) signature + final Map<Object, Object> map = (Map) value; for (Map.Entry<Object, Object> entry : map.entrySet()) { - property(key, entry.getKey(), entry.getValue()); + final Object val = entry.getValue(); + if (val instanceof CardinalityValueTraversal) { + final CardinalityValueTraversal cardVal = (CardinalityValueTraversal) val; + property(cardVal.getCardinality(), entry.getKey(), cardVal.getValue()); + } else { + property(key, entry.getKey(), entry.getValue()); + } } return this; - } else if (value == null) { // Just return the input if you pass a null + } else if (value == null) { + // Just return the input if you pass a null return this; } else { return this.property((VertexProperty.Cardinality) key, value, null == keyValues ? null : keyValues[0], @@ -2568,7 +2579,8 @@ public interface GraphTraversal<S, E> extends Traversal<S, E> { Arrays.copyOfRange(keyValues, 1, keyValues.length) : new Object[]{}); } - } else { //handles if cardinality is not the first parameter + } else { + // handles if cardinality is not the first parameter return this.property(null, key, value, keyValues); } } @@ -2578,6 +2590,9 @@ public interface GraphTraversal<S, E> extends Traversal<S, E> { * be added as property. This method is the long-hand version of looping through the * {@link #property(Object, Object, Object...)} method for each key/value pair supplied. * <p/> + * A value may use a {@link CardinalityValueTraversal} to allow specification of the + * {@link VertexProperty.Cardinality} along with the property value itself. + * <p/> * If a {@link Map} is not supplied then an exception is thrown. * <p /> * This method is effectively calls {@link #property(VertexProperty.Cardinality, Object, Object, Object...)} @@ -2591,7 +2606,13 @@ public interface GraphTraversal<S, E> extends Traversal<S, E> { public default GraphTraversal<S, E> property(final Map<Object, Object> value) { if (value != null) { for (Map.Entry<Object, Object> entry : value.entrySet()) { - property(null, entry.getKey(), entry.getValue()); + final Object val = entry.getValue(); + if (val instanceof CardinalityValueTraversal) { + final CardinalityValueTraversal cardVal = (CardinalityValueTraversal) val; + property(cardVal.getCardinality(), entry.getKey(), cardVal.getValue()); + } else { + property(null, entry.getKey(), entry.getValue()); + } } } return this; @@ -3286,6 +3307,29 @@ public interface GraphTraversal<S, E> extends Traversal<S, E> { return this; } + /** + * This is a step modulator to a {@link TraversalOptionParent} like {@code choose()} or {@code mergeV()} where the + * provided argument associated to the {@code token} is applied according to the semantics of the step. Please see + * the documentation of such steps to understand the usage context. + * + * @param m Provides a {@code Map} as the option which is the same as doing {@code constant(m)}. + * @return the traversal with the modulated step + * @see <a href="http://tinkerpop.apache.org/docs/${project.version}/reference/#mergev-step" target="_blank">Reference Documentation - MergeV Step</a> + * @see <a href="http://tinkerpop.apache.org/docs/${project.version}/reference/#mergee-step" target="_blank">Reference Documentation - MergeE Step</a> + * @since 3.6.0 + */ + public default <M, E2> GraphTraversal<S, E> option(final Merge merge, final Map<Object, Object> m, final VertexProperty.Cardinality cardinality) { + this.asAdmin().getBytecode().addStep(Symbols.option, merge, m, cardinality); + // do explicit cardinality for every single pair in the map + for (Object k : m.keySet()) { + final Object o = m.get(k); + if (!(o instanceof CardinalityValueTraversal)) + m.put(k, new CardinalityValueTraversal(cardinality, o)); + } + ((TraversalOptionParent<M, E, E2>) this.asAdmin().getEndStep()).addChildOption((M) merge, (Traversal.Admin<E, E2>) new ConstantTraversal<>(m).asAdmin()); + return this; + } + /** * This step modifies {@link #choose(Function)} to specifies the available choices that might be executed. * diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/lambda/CardinalityValueTraversal.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/lambda/CardinalityValueTraversal.java new file mode 100644 index 0000000000..8c13e0649b --- /dev/null +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/lambda/CardinalityValueTraversal.java @@ -0,0 +1,82 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.tinkerpop.gremlin.process.traversal.lambda; + +import org.apache.tinkerpop.gremlin.process.traversal.Bytecode; +import org.apache.tinkerpop.gremlin.structure.VertexProperty; + +import java.util.Objects; + +public final class CardinalityValueTraversal extends AbstractLambdaTraversal { + + private final VertexProperty.Cardinality cardinality; + + private final Object value; + + private final Bytecode bytecode; + + public CardinalityValueTraversal(final VertexProperty.Cardinality cardinality, final Object value) { + this.cardinality = cardinality; + this.value = value; + this.bytecode = new Bytecode(CardinalityValueTraversal.class.getSimpleName(), cardinality.name(), value); + } + + public static CardinalityValueTraversal from(final Bytecode.Instruction inst) { + return new CardinalityValueTraversal(VertexProperty.Cardinality.valueOf(inst.getArguments()[0].toString()), + inst.getArguments()[1]); + } + + @Override + public Bytecode getBytecode() { + return this.bytecode; + } + + public VertexProperty.Cardinality getCardinality() { + return cardinality; + } + + public Object getValue() { + return value; + } + + @Override + public String toString() { + return "[" + cardinality + ", " + value + "]"; + } + + @Override + public boolean equals(final Object o) { + if (this == o) return true; + if (!(o instanceof CardinalityValueTraversal)) return false; + if (!super.equals(o)) return false; + + final CardinalityValueTraversal that = (CardinalityValueTraversal) o; + + if (cardinality != that.cardinality) return false; + return Objects.equals(value, that.value); + } + + @Override + public int hashCode() { + int result = super.hashCode(); + result = 31 * result + cardinality.hashCode(); + result = 31 * result + (value != null ? value.hashCode() : 0); + return result; + } +} diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MergeVertexStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MergeVertexStep.java index 1408cfbfec..ed1e26a34a 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MergeVertexStep.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MergeVertexStep.java @@ -29,12 +29,15 @@ import java.util.stream.Stream; import org.apache.tinkerpop.gremlin.process.traversal.Traversal; import org.apache.tinkerpop.gremlin.process.traversal.Traverser; import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal; +import org.apache.tinkerpop.gremlin.process.traversal.lambda.CardinalityValueTraversal; +import org.apache.tinkerpop.gremlin.process.traversal.step.sideEffect.AddPropertyStep; import org.apache.tinkerpop.gremlin.process.traversal.step.util.event.Event; import org.apache.tinkerpop.gremlin.process.traversal.strategy.decoration.EventStrategy; import org.apache.tinkerpop.gremlin.structure.Graph; import org.apache.tinkerpop.gremlin.structure.Property; import org.apache.tinkerpop.gremlin.structure.T; import org.apache.tinkerpop.gremlin.structure.Vertex; +import org.apache.tinkerpop.gremlin.structure.VertexProperty; import org.apache.tinkerpop.gremlin.structure.util.CloseableIterator; import org.apache.tinkerpop.gremlin.util.iterator.IteratorUtils; @@ -96,18 +99,28 @@ public class MergeVertexStep<S> extends MergeStep<S, Vertex, Map> { validateMapInput(onMatchMap, true); onMatchMap.forEach((key, value) -> { + Object val = value; + VertexProperty.Cardinality card = graph.features().vertex().getCardinality(key); + + // a value can be a traversal in the case where the user specifies the cardinality for the value. + if (value instanceof CardinalityValueTraversal) { + final CardinalityValueTraversal cardinalityValueTraversal = (CardinalityValueTraversal) value; + card = cardinalityValueTraversal.getCardinality(); + val = cardinalityValueTraversal.getValue(); + } + // trigger callbacks for eventing - in this case, it's a VertexPropertyChangedEvent. if there's no // registry/callbacks then just set the property if (this.callbackRegistry != null && !callbackRegistry.getCallbacks().isEmpty()) { final EventStrategy eventStrategy = getTraversal().getStrategies().getStrategy(EventStrategy.class).get(); final Property<?> p = v.property(key); final Property<Object> oldValue = p.isPresent() ? eventStrategy.detach(v.property(key)) : null; - final Event.VertexPropertyChangedEvent vpce = new Event.VertexPropertyChangedEvent(eventStrategy.detach(v), oldValue, value); + final Event.VertexPropertyChangedEvent vpce = new Event.VertexPropertyChangedEvent(eventStrategy.detach(v), oldValue, val); this.callbackRegistry.getCallbacks().forEach(c -> c.accept(vpce)); } // try to detect proper cardinality for the key according to the graph - v.property(graph.features().vertex().getCardinality(key), key, value); + v.property(card, key, val); }); }); } diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/util/Parameters.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/util/Parameters.java index 55d69367df..d1654a344e 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/util/Parameters.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/util/Parameters.java @@ -135,7 +135,6 @@ public class Parameters implements Cloneable, Serializable { public <E> List<E> get(final Object key, final Supplier<E> defaultValue) { final List<E> list = (List<E>) this.parameters.get(key); return (null == list) ? (null == defaultValue ? Collections.emptyList() : Collections.singletonList(defaultValue.get())) : list; - } /** diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/translator/DotNetTranslator.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/translator/DotNetTranslator.java index 20ee8ec65c..5e37147b25 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/translator/DotNetTranslator.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/translator/DotNetTranslator.java @@ -192,6 +192,16 @@ public final class DotNetTranslator implements Translator.ScriptTranslator { return o.toString(); } + @Override + protected Script produceCardinalityValue(final Bytecode o) { + final Bytecode.Instruction inst = o.getSourceInstructions().get(0); + final String card = inst.getArguments()[0].toString(); + script.append("CardinalityValue." + card.substring(0, 1).toUpperCase() + card.substring(1) + "("); + convertToScript(inst.getArguments()[1]); + script.append(")"); + return script; + } + @Override protected Script produceScript(final Set<?> o) { final Iterator<?> iterator = o.iterator(); diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/translator/GolangTranslator.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/translator/GolangTranslator.java index ee1c38cbc0..dece576e72 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/translator/GolangTranslator.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/translator/GolangTranslator.java @@ -181,6 +181,16 @@ public final class GolangTranslator implements Translator.ScriptTranslator { return GO_PACKAGE_NAME + "Pick." + resolveSymbol(o.toString()); } + @Override + protected Script produceCardinalityValue(final Bytecode o) { + final Bytecode.Instruction inst = o.getSourceInstructions().get(0); + final String card = inst.getArguments()[0].toString(); + script.append(GO_PACKAGE_NAME + "CardinalityValue." + card.substring(0, 1).toUpperCase() + card.substring(1) + "("); + convertToScript(inst.getArguments()[1]); + script.append(")"); + return script; + } + @Override protected Script produceScript(final Set<?> o) { final Iterator<?> iterator = o.iterator(); diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/translator/GroovyTranslator.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/translator/GroovyTranslator.java index 7fea7eb829..ad5498b003 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/translator/GroovyTranslator.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/translator/GroovyTranslator.java @@ -206,6 +206,15 @@ public final class GroovyTranslator implements Translator.ScriptTranslator { return o.toString(); } + @Override + protected Script produceCardinalityValue(final Bytecode o) { + final Bytecode.Instruction inst = o.getSourceInstructions().get(0); + script.append("VertexProperty.Cardinality." + inst.getArguments()[0] + "("); + convertToScript(inst.getArguments()[1]); + script.append(")"); + return script; + } + @Override protected Script produceScript(final Set<?> o) { return produceScript(new ArrayList<>(o)).append(" as Set"); diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/translator/JavascriptTranslator.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/translator/JavascriptTranslator.java index c03fd01ebb..1ebf25a34c 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/translator/JavascriptTranslator.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/translator/JavascriptTranslator.java @@ -187,6 +187,15 @@ public final class JavascriptTranslator implements Translator.ScriptTranslator { return o.toString(); } + @Override + protected Script produceCardinalityValue(final Bytecode o) { + final Bytecode.Instruction inst = o.getSourceInstructions().get(0); + script.append("CardinalityValue." + inst.getArguments()[0] + "("); + convertToScript(inst.getArguments()[1]); + script.append(")"); + return script; + } + @Override protected Script produceScript(final Set<?> o) { return produceScript(new ArrayList<>(o)); diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/translator/PythonTranslator.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/translator/PythonTranslator.java index 41f8382643..e26b02f8ad 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/translator/PythonTranslator.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/translator/PythonTranslator.java @@ -362,6 +362,15 @@ public final class PythonTranslator implements Translator.ScriptTranslator { return script; } + @Override + protected Script produceCardinalityValue(final Bytecode o) { + final Bytecode.Instruction inst = o.getSourceInstructions().get(0); + script.append("CardinalityValue." + resolveSymbol(inst.getArguments()[0].toString()) + "("); + convertToScript(inst.getArguments()[1]); + script.append(")"); + return script; + } + protected String resolveSymbol(final String methodName) { return SymbolHelper.toPython(methodName); } diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/VertexProperty.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/VertexProperty.java index ff50166c0b..af7a18a1d9 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/VertexProperty.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/VertexProperty.java @@ -18,6 +18,10 @@ */ package org.apache.tinkerpop.gremlin.structure; +import org.apache.tinkerpop.gremlin.process.traversal.Traversal; +import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal; +import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__; +import org.apache.tinkerpop.gremlin.process.traversal.lambda.CardinalityValueTraversal; import org.apache.tinkerpop.gremlin.structure.util.empty.EmptyVertexProperty; import java.util.Iterator; @@ -40,7 +44,19 @@ public interface VertexProperty<V> extends Property<V>, Element { public static final String DEFAULT_LABEL = "vertexProperty"; public enum Cardinality { - single, list, set + single, list, set; + + public static CardinalityValueTraversal single(final Object value) { + return new CardinalityValueTraversal(single, value); + } + + public static CardinalityValueTraversal list(final Object value) { + return new CardinalityValueTraversal(list, value); + } + + public static CardinalityValueTraversal set(final Object value) { + return new CardinalityValueTraversal(set, value); + } } /** diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/binary/GraphBinaryWriter.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/binary/GraphBinaryWriter.java index ab0b094329..80b1df0b1b 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/binary/GraphBinaryWriter.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/binary/GraphBinaryWriter.java @@ -102,7 +102,7 @@ public class GraphBinaryWriter { if (serializer instanceof TransformSerializer) { // For historical reasons, there are types that need to be transformed into another type // before serialization, e.g., Map.Entry - TransformSerializer<T> transformSerializer = (TransformSerializer<T>) serializer; + final TransformSerializer<T> transformSerializer = (TransformSerializer<T>) serializer; write(transformSerializer.transform(value), buffer); return; } @@ -118,7 +118,7 @@ public class GraphBinaryWriter { * <p>Note that for simple types, the provided information will be <code>null</code>.</p> */ public <T> void writeFullyQualifiedNull(final Class<T> objectClass, Buffer buffer, final Object information) throws IOException { - TypeSerializer<T> serializer = registry.getSerializer(objectClass); + final TypeSerializer<T> serializer = registry.getSerializer(objectClass); serializer.write(null, buffer, this); } diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/binary/types/TransformSerializer.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/binary/types/TransformSerializer.java index 97ccbbaea3..7275b1cce6 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/binary/types/TransformSerializer.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/binary/types/TransformSerializer.java @@ -24,5 +24,5 @@ import org.apache.tinkerpop.gremlin.structure.io.binary.TypeSerializer; * Represents a special TypeSerializer placeholder that transforms the value into another before serializing it. */ public interface TransformSerializer<T> extends TypeSerializer<T> { - Object transform(T value); + Object transform(final T value); } diff --git a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/language/grammar/GeneralLiteralVisitorTest.java b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/language/grammar/GeneralLiteralVisitorTest.java index b4da7f146f..7b84f7ea5b 100644 --- a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/language/grammar/GeneralLiteralVisitorTest.java +++ b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/language/grammar/GeneralLiteralVisitorTest.java @@ -20,6 +20,7 @@ package org.apache.tinkerpop.gremlin.language.grammar; import org.antlr.v4.runtime.CharStreams; import org.antlr.v4.runtime.CommonTokenStream; +import org.apache.tinkerpop.gremlin.structure.VertexProperty; import org.junit.Assert; import org.junit.Test; import org.junit.experimental.runners.Enclosed; @@ -43,7 +44,6 @@ import static org.hamcrest.number.OrderingComparison.greaterThanOrEqualTo; import static org.hamcrest.number.OrderingComparison.lessThan; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; /** * Generic Literal visitor test @@ -674,4 +674,42 @@ public class GeneralLiteralVisitorTest { assertEquals(Double.NEGATIVE_INFINITY, new GenericLiteralVisitor(new GremlinAntlrToJava()).visitInfLiteral(ctx)); } } + + @RunWith(Parameterized.class) + public static class CardinalityTest { + @Parameterized.Parameter(value = 0) + public String script; + + @Parameterized.Parameter(value = 1) + public Object expected; + + @Parameterized.Parameters() + public static Iterable<Object[]> generateTestParameters() { + return Arrays.asList(new Object[][]{ + {"single(\"test\")", VertexProperty.Cardinality.single("test")}, + {"list(\"test\")", VertexProperty.Cardinality.list("test")}, + {"set(\"test\")", VertexProperty.Cardinality.set("test")}, + {"Cardinality.single(\"test\")", VertexProperty.Cardinality.single("test")}, + {"Cardinality.list(\"test\")", VertexProperty.Cardinality.list("test")}, + {"Cardinality.set(\"test\")", VertexProperty.Cardinality.set("test")}, + {"single(1l)", VertexProperty.Cardinality.single(1L)}, + {"list(1l)", VertexProperty.Cardinality.list(1L)}, + {"set(1l)", VertexProperty.Cardinality.set(1L)}, + {"Cardinality.single", VertexProperty.Cardinality.single}, + {"Cardinality.list", VertexProperty.Cardinality.list}, + {"Cardinality.set", VertexProperty.Cardinality.set}, + {"single", VertexProperty.Cardinality.single}, + {"list", VertexProperty.Cardinality.list}, + {"set", VertexProperty.Cardinality.set}, + }); + } + + @Test + public void shouldParse() { + final GremlinLexer lexer = new GremlinLexer(CharStreams.fromString(script)); + final GremlinParser parser = new GremlinParser(new CommonTokenStream(lexer)); + final GremlinParser.TraversalCardinalityContext ctx = parser.traversalCardinality(); + assertEquals(expected, new GenericLiteralVisitor(new GremlinAntlrToJava()).visitTraversalCardinality(ctx)); + } + } } diff --git a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/CardinalityTest.java b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/CardinalityTest.java new file mode 100644 index 0000000000..b5c578e2f4 --- /dev/null +++ b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/CardinalityTest.java @@ -0,0 +1,68 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.tinkerpop.gremlin.process.traversal; + +import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal; +import org.apache.tinkerpop.gremlin.process.traversal.lambda.CardinalityValueTraversal; +import org.apache.tinkerpop.gremlin.process.traversal.step.sideEffect.AddPropertyStep; +import org.apache.tinkerpop.gremlin.structure.T; +import org.apache.tinkerpop.gremlin.structure.VertexProperty; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; + +public class CardinalityTest { + + @Test + public void shouldCreateSingle() { + final CardinalityValueTraversal t = VertexProperty.Cardinality.single("test"); + assertEquals("test", t.getValue()); + assertEquals(VertexProperty.Cardinality.single, t.getCardinality()); + } + + @Test + public void shouldCreateSet() { + final CardinalityValueTraversal t = VertexProperty.Cardinality.set("test"); + assertEquals("test", t.getValue()); + assertEquals(VertexProperty.Cardinality.set, t.getCardinality()); + } + + @Test + public void shouldCreateList() { + final CardinalityValueTraversal t = VertexProperty.Cardinality.list("test"); + assertEquals("test", t.getValue()); + assertEquals(VertexProperty.Cardinality.list, t.getCardinality()); + } + + @Test + public void shouldBeEqual() { + assertEquals(VertexProperty.Cardinality.single("test"), VertexProperty.Cardinality.single("test")); + assertEquals(VertexProperty.Cardinality.single(1), VertexProperty.Cardinality.single(1)); + assertEquals(VertexProperty.Cardinality.single(null), VertexProperty.Cardinality.single(null)); + } + + @Test + public void shouldNotBeEqual() { + assertNotEquals(VertexProperty.Cardinality.single(100), VertexProperty.Cardinality.single("testing")); + assertNotEquals(VertexProperty.Cardinality.single("test"), VertexProperty.Cardinality.single("testing")); + assertNotEquals(VertexProperty.Cardinality.single(100), VertexProperty.Cardinality.single(1)); + assertNotEquals(VertexProperty.Cardinality.single("null"), VertexProperty.Cardinality.single(null)); + } +} diff --git a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/translator/DotNetTranslatorTest.java b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/translator/DotNetTranslatorTest.java index 28256f79fe..b3b7f1a855 100644 --- a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/translator/DotNetTranslatorTest.java +++ b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/translator/DotNetTranslatorTest.java @@ -199,6 +199,11 @@ public class DotNetTranslatorTest { g.V().hasLabel("person").property(VertexProperty.Cardinality.single, "name", null)).getScript()); } + @Test + public void shouldTranslateCardinalityValue() { + assertTranslation("CardinalityValue.Set(\"test\")", VertexProperty.Cardinality.set("test")); + } + @Test public void shouldTranslateHasNull() { String script = translator.translate(g.V().has("k", (Object) null).asAdmin().getBytecode()).getScript(); diff --git a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/translator/GolangTranslatorTest.java b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/translator/GolangTranslatorTest.java index 81e9559823..553912b7fa 100644 --- a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/translator/GolangTranslatorTest.java +++ b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/translator/GolangTranslatorTest.java @@ -62,6 +62,12 @@ public class GolangTranslatorTest { assertEquals("g.AddV(\"person\").Property(gremlingo.Cardinality.List, \"name\", \"marko\")", gremlinAsGo); } + @Test + public void shouldTranslateCardinalityValue() { + assertEquals("g.Inject(gremlingo.CardinalityValue.Set(\"test\"))", translator.translate( + g.inject(VertexProperty.Cardinality.set("test")).asAdmin().getBytecode()).getScript()); + } + @Test public void shouldTranslateMultilineStrings() { final String gremlinAsGo = translator.translate( diff --git a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/translator/GroovyTranslatorTest.java b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/translator/GroovyTranslatorTest.java index b9910fbbc8..499e76b4b4 100644 --- a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/translator/GroovyTranslatorTest.java +++ b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/translator/GroovyTranslatorTest.java @@ -34,6 +34,7 @@ import org.apache.tinkerpop.gremlin.structure.Column; import org.apache.tinkerpop.gremlin.structure.Direction; import org.apache.tinkerpop.gremlin.structure.Edge; import org.apache.tinkerpop.gremlin.structure.Vertex; +import org.apache.tinkerpop.gremlin.structure.VertexProperty; import org.apache.tinkerpop.gremlin.structure.util.detached.DetachedEdge; import org.apache.tinkerpop.gremlin.structure.util.detached.DetachedVertex; import org.apache.tinkerpop.gremlin.structure.util.empty.EmptyGraph; @@ -160,6 +161,11 @@ public class GroovyTranslatorTest { assertTranslation("Column.keys", Column.keys); } + @Test + public void shouldTranslateCardinalityValue() { + assertTranslation("VertexProperty.Cardinality.set(\"test\")", VertexProperty.Cardinality.set("test")); + } + @Test public void shouldTranslateDateUsingLanguageTypeTranslator() { final Translator.ScriptTranslator t = GroovyTranslator.of("g", diff --git a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/translator/JavascriptTranslatorTest.java b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/translator/JavascriptTranslatorTest.java index 98de2f988c..ba99d44969 100644 --- a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/translator/JavascriptTranslatorTest.java +++ b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/translator/JavascriptTranslatorTest.java @@ -32,6 +32,7 @@ import org.apache.tinkerpop.gremlin.structure.Column; import org.apache.tinkerpop.gremlin.structure.Direction; import org.apache.tinkerpop.gremlin.structure.Edge; import org.apache.tinkerpop.gremlin.structure.Vertex; +import org.apache.tinkerpop.gremlin.structure.VertexProperty; import org.apache.tinkerpop.gremlin.structure.util.detached.DetachedEdge; import org.apache.tinkerpop.gremlin.structure.util.detached.DetachedVertex; import org.apache.tinkerpop.gremlin.structure.util.empty.EmptyGraph; @@ -120,6 +121,11 @@ public class JavascriptTranslatorTest { assertTranslation("Scope.local", Scope.local); } + @Test + public void shouldTranslateCardinalityValue() { + assertTranslation("CardinalityValue.set(\"test\")", VertexProperty.Cardinality.set("test")); + } + @Test public void shouldHaveValidToString() { assertEquals("translator[h:gremlin-javascript]", JavascriptTranslator.of("h").toString()); diff --git a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/translator/PythonTranslatorTest.java b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/translator/PythonTranslatorTest.java index 9fee3ed4a9..ebbca186f8 100644 --- a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/translator/PythonTranslatorTest.java +++ b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/translator/PythonTranslatorTest.java @@ -19,11 +19,13 @@ package org.apache.tinkerpop.gremlin.process.traversal.translator; import org.apache.tinkerpop.gremlin.process.traversal.Bytecode; +import org.apache.tinkerpop.gremlin.process.traversal.Merge; import org.apache.tinkerpop.gremlin.process.traversal.TextP; import org.apache.tinkerpop.gremlin.process.traversal.Translator; import org.apache.tinkerpop.gremlin.process.traversal.Traverser; import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource; import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__; +import org.apache.tinkerpop.gremlin.process.traversal.lambda.CardinalityValueTraversal; import org.apache.tinkerpop.gremlin.process.traversal.strategy.decoration.SeedStrategy; import org.apache.tinkerpop.gremlin.process.traversal.strategy.decoration.SubgraphStrategy; import org.apache.tinkerpop.gremlin.process.traversal.strategy.verification.ReadOnlyStrategy; @@ -32,6 +34,9 @@ import org.apache.tinkerpop.gremlin.structure.util.empty.EmptyGraph; import org.apache.tinkerpop.gremlin.util.function.Lambda; import org.junit.Test; +import java.util.HashMap; +import java.util.Map; + import static org.apache.tinkerpop.gremlin.process.traversal.AnonymousTraversalSource.traversal; import static org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__.hasLabel; import static org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__.inE; @@ -62,6 +67,15 @@ public class PythonTranslatorTest { assertEquals("g.addV('person').property(Cardinality.list_,'name','marko')", gremlinAsPython); } + @Test + public void shouldTranslateCardinalityValue() { + final Map<Object, Object> m = new HashMap<>(); + m.put("name", VertexProperty.Cardinality.set("marko")); + final String gremlinAsPython = translator.translate( + g.mergeV(new HashMap<>()).option(Merge.onMatch, m).asAdmin().getBytecode()).getScript(); + assertEquals("g.merge_v({}).option(Merge.on_match,{'name':CardinalityValue.set_('marko')})", gremlinAsPython); + } + @Test public void shouldTranslateMultilineStrings() { final String gremlinAsPython = translator.translate( diff --git a/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/CardinalityValue.cs b/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/CardinalityValue.cs new file mode 100644 index 0000000000..b184eee495 --- /dev/null +++ b/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/CardinalityValue.cs @@ -0,0 +1,92 @@ +#region License + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#endregion + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; + +namespace Gremlin.Net.Process.Traversal +{ + /// <summary> + /// Holds a property value with the associated <see cref="Traversal.Cardinality" />. + /// </summary> + public class CardinalityValue : Bytecode + { + /// <summary> + /// Gets the <see cref="Traversal.Cardinality" /> for the value. + /// </summary> + public Cardinality? Cardinality + { + get + { + return this.SourceInstructions[0].Arguments[0]; + } + } + + /// <summary> + /// Gets the value. + /// </summary> + public object? Value + { + get + { + return this.SourceInstructions[0].Arguments[1]; + } + } + + /// <summary> + /// Initializes a new instance of the <see cref="CardinalityValue" /> class. + /// </summary> + public CardinalityValue(Cardinality card, object val) + { + if (card == null) + throw new ArgumentNullException("Cardinality argument must not be null"); + this.AddSource("CardinalityValueTraversal", card, val); + } + + /// <summary> + /// Creates a new <see cref="CardinalityValue" /> with a particular value and Single <see cref="Cardinality" />. + /// </summary> + public static CardinalityValue Single(object value) + { + return new CardinalityValue(Cardinality.Single, value); + } + + /// <summary> + /// Creates a new <see cref="CardinalityValue" /> with a particular value and Set <see cref="Cardinality" />. + /// </summary> + public static CardinalityValue Set(object value) + { + return new CardinalityValue(Cardinality.Set, value); + } + + /// <summary> + /// Creates a new <see cref="CardinalityValue" /> with a particular value and List <see cref="Cardinality" />. + /// </summary> + public static CardinalityValue List(object value) + { + return new CardinalityValue(Cardinality.List, value); + } + } +} \ No newline at end of file diff --git a/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/GraphTraversal.cs b/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/GraphTraversal.cs index 2d1633697c..3f55bfc3ab 100644 --- a/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/GraphTraversal.cs +++ b/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/GraphTraversal.cs @@ -1323,6 +1323,15 @@ namespace Gremlin.Net.Process.Traversal return Wrap<TStart, TEnd>(this); } + /// <summary> + /// Adds the option step to this <see cref="GraphTraversal{SType, EType}" />. + /// </summary> + public GraphTraversal<TStart, TEnd> Option (object pickToken, IDictionary<object,object> traversalOption, Cardinality cardinality) + { + Bytecode.AddStep("option", pickToken, traversalOption, cardinality); + return Wrap<TStart, TEnd>(this); + } + /// <summary> /// Adds the option step to this <see cref="GraphTraversal{SType, EType}" />. /// </summary> diff --git a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Docs/Reference/GremlinVariantsTests.cs b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Docs/Reference/GremlinVariantsTests.cs index 0d8a827496..9fed29ea2c 100644 --- a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Docs/Reference/GremlinVariantsTests.cs +++ b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Docs/Reference/GremlinVariantsTests.cs @@ -45,6 +45,8 @@ using static Gremlin.Net.Process.Traversal.Scope; using static Gremlin.Net.Process.Traversal.TextP; using static Gremlin.Net.Process.Traversal.Column; using static Gremlin.Net.Process.Traversal.Direction; +using static Gremlin.Net.Process.Traversal.Cardinality; +using static Gremlin.Net.Process.Traversal.CardinalityValue; using static Gremlin.Net.Process.Traversal.T; // end::commonImports[] diff --git a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs index 20ff0385d3..67a3cee9df 100644 --- a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs +++ b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs @@ -459,6 +459,8 @@ namespace Gremlin.Net.IntegrationTest.Gherkin {"g_addV_propertyXlabel_personX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.AddV().Property(T.Label,"person"), (g,p) =>g.V().HasLabel("person")}}, {"g_addV_propertyXmapX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.AddV().Property("name","foo").Property("age",42), (g,p) =>g.V().Has("name","foo")}}, {"g_addV_propertyXsingle_mapX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.AddV().Property(Cardinality.Single,"name","foo").Property(Cardinality.Single,"age",42), (g,p) =>g.V().Has("name","foo")}}, + {"g_V_hasXname_fooX_propertyXname_setXbarX_age_43X", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.AddV().Property(Cardinality.Single,"name","foo").Property("age",42), (g,p) =>g.V().Has("name","foo").Property(Cardinality.Set,"name","bar").Property("age",43), (g,p) =>g.V().Has("name","foo"), (g,p) =>g.V().Has("name","bar"), (g,p) =>g.V().Has("age",43), (g,p) =>g.V().Has("age",42)}}, + {"g_V_hasXname_fooX_propertyXset_name_bar_age_singleX43XX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.AddV().Property(Cardinality.Single,"name","foo").Property("age",42), (g,p) =>g.V().Has("name","foo").Property(Cardinality.Set,"name","bar").Property(Cardinality.Single,"age",43), (g,p) =>g.V().Has("name","foo"), (g,p) =>g.V().Has("name","bar"), (g,p) =>g.V().Has("age",43), (g,p) =>g.V().Has("age",42)}}, {"g_addV_propertyXnullX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.AddV("person"), (g,p) =>g.V().HasLabel("person").Values<object>()}}, {"g_addV_propertyXemptyX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.AddV("person"), (g,p) =>g.V().HasLabel("person").Values<object>()}}, {"g_addV_propertyXset_nullX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.AddV("foo"), (g,p) =>g.V().HasLabel("foo").Values<object>()}}, @@ -721,6 +723,13 @@ namespace Gremlin.Net.IntegrationTest.Gherkin {"g_mergeV_hidden_id_key_onMatch_matched_prohibited", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.AddV("vertex"), (g,p) =>g.MergeV((IDictionary<object,object>) new Dictionary<object,object> {}).Option(Merge.OnMatch, (IDictionary<object,object>) p["xx1"])}}, {"g_mergeV_hidden_label_key_matched_onMatch_matched_prohibited", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.AddV("vertex"), (g,p) =>g.MergeV((IDictionary<object,object>) new Dictionary<object,object> {}).Option(Merge.OnMatch, (IDictionary<object,object>) p["xx1"])}}, {"g_mergeV_hidden_label_value_onMatch_matched_prohibited", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.AddV("vertex"), (g,p) =>g.MergeV((IDictionary<object,object>) new Dictionary<object,object> {}).Option(Merge.OnMatch, (IDictionary<object,object>) p["xx1"])}}, + {"g_mergeVXname_markoX_optionXonMatch_age_listX33XX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.AddV("person").Property("name","marko").Property(Cardinality.List,"age",29).Property(Cardinality.List,"age",31).Property(Cardinality.List,"age",32), (g,p) =>g.MergeV((IDictionary<object,object>) new Dictionary<object,object> {{"name", "marko"}}).Option(Merge.OnMatch, (IDictionary<object,object>) new Dictionary<object,object> {{"age" [...] + {"g_mergeVXname_markoX_optionXonMatch_age_setX33XX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.AddV("person").Property("name","marko").Property(Cardinality.List,"age",29).Property(Cardinality.List,"age",31).Property(Cardinality.List,"age",32), (g,p) =>g.MergeV((IDictionary<object,object>) new Dictionary<object,object> {{"name", "marko"}}).Option(Merge.OnMatch, (IDictionary<object,object>) new Dictionary<object,object> {{"age", [...] + {"g_mergeVXname_markoX_optionXonMatch_age_setX31XX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.AddV("person").Property("name","marko").Property(Cardinality.List,"age",29).Property(Cardinality.List,"age",31).Property(Cardinality.List,"age",32), (g,p) =>g.MergeV((IDictionary<object,object>) new Dictionary<object,object> {{"name", "marko"}}).Option(Merge.OnMatch, (IDictionary<object,object>) new Dictionary<object,object> {{"age", [...] + {"g_mergeVXname_markoX_optionXonMatch_age_singleX33XX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.AddV("person").Property("name","marko").Property(Cardinality.List,"age",29).Property(Cardinality.List,"age",31).Property(Cardinality.List,"age",32), (g,p) =>g.MergeV((IDictionary<object,object>) new Dictionary<object,object> {{"name", "marko"}}).Option(Merge.OnMatch, (IDictionary<object,object>) new Dictionary<object,object> {{"ag [...] + {"g_mergeVXname_markoX_optionXonMatch_age_33_singleX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.AddV("person").Property("name","marko").Property(Cardinality.List,"age",29).Property(Cardinality.List,"age",31).Property(Cardinality.List,"age",32), (g,p) =>g.MergeV((IDictionary<object,object>) new Dictionary<object,object> {{"name", "marko"}}).Option(Merge.OnMatch,new Dictionary<object,object> {{"age", 33}},Cardinality.Single), ( [...] + {"g_mergeVXname_markoX_optionXonMatch_name_allen_age_setX31X_singleX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.AddV("person").Property("name","marko").Property(Cardinality.List,"age",29).Property(Cardinality.List,"age",31).Property(Cardinality.List,"age",32), (g,p) =>g.MergeV((IDictionary<object,object>) new Dictionary<object,object> {{"name", "marko"}}).Option(Merge.OnMatch,new Dictionary<object,object> {{"name", "allen"}, [...] + {"g_mergeVXname_markoX_optionXonMatch_name_allen_age_singleX31X_singleX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.AddV("person").Property("name","marko").Property(Cardinality.List,"age",29).Property(Cardinality.List,"age",31).Property(Cardinality.List,"age",32), (g,p) =>g.MergeV((IDictionary<object,object>) new Dictionary<object,object> {{"name", "marko"}}).Option(Merge.OnMatch,new Dictionary<object,object> {{"name", "allen" [...] {"g_V_age_min", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Values<object>("age").Min<object>()}}, {"g_V_foo_min", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Values<object>("foo").Min<object>()}}, {"g_V_name_min", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Values<object>("name").Min<object>()}}, diff --git a/gremlin-dotnet/test/Gremlin.Net.UnitTest/Process/Traversal/CardinalityValueTests.cs b/gremlin-dotnet/test/Gremlin.Net.UnitTest/Process/Traversal/CardinalityValueTests.cs new file mode 100644 index 0000000000..c30aaf3950 --- /dev/null +++ b/gremlin-dotnet/test/Gremlin.Net.UnitTest/Process/Traversal/CardinalityValueTests.cs @@ -0,0 +1,55 @@ +#region License + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#endregion + +using Gremlin.Net.Process.Traversal; +using Xunit; + +namespace Gremlin.Net.UnitTest.Process.Traversal +{ + public class CardinalityValueTests + { + [Fact] + public void ShouldProduceSingleValue() + { + CardinalityValue val = CardinalityValue.Single("test"); + Assert.Equal("test", val.Value); + Assert.Equal(Cardinality.Single, val.Cardinality); + } + + [Fact] + public void ShouldProduceListValue() + { + CardinalityValue val = CardinalityValue.List("test"); + Assert.Equal("test", val.Value); + Assert.Equal(Cardinality.List, val.Cardinality); + } + + [Fact] + public void ShouldProduceSetValue() + { + CardinalityValue val = CardinalityValue.Set("test"); + Assert.Equal("test", val.Value); + Assert.Equal(Cardinality.Set, val.Cardinality); + } + } +} \ No newline at end of file diff --git a/gremlin-go/driver/cucumber/gremlin.go b/gremlin-go/driver/cucumber/gremlin.go index 6361e89858..66dce5efbf 100644 --- a/gremlin-go/driver/cucumber/gremlin.go +++ b/gremlin-go/driver/cucumber/gremlin.go @@ -429,6 +429,8 @@ var translationMap = map[string][]func(g *gremlingo.GraphTraversalSource, p map[ "g_addV_propertyXlabel_personX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.AddV().Property(gremlingo.T.Label, "person")}, func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().HasLabel("person")}}, "g_addV_propertyXmapX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.AddV().Property("name", "foo").Property("age", 42)}, func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Has("name", "foo")}}, "g_addV_propertyXsingle_mapX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.AddV().Property(gremlingo.Cardinality.Single, "name", "foo").Property(gremlingo.Cardinality.Single, "age", 42)}, func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Has("name", "foo")}}, + "g_V_hasXname_fooX_propertyXname_setXbarX_age_43X": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.AddV().Property(gremlingo.Cardinality.Single, "name", "foo").Property("age", 42)}, func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Has("name", "foo").Property(gremlingo.Cardinality.Set, "name", "bar").Property("age", 43)}, func(g *gremlingo.GraphTraversalSource, p map[string [...] + "g_V_hasXname_fooX_propertyXset_name_bar_age_singleX43XX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.AddV().Property(gremlingo.Cardinality.Single, "name", "foo").Property("age", 42)}, func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Has("name", "foo").Property(gremlingo.Cardinality.Set, "name", "bar").Property(gremlingo.Cardinality.Single, "age", 43)}, func(g *gremlin [...] "g_addV_propertyXnullX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.AddV("person")}, func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().HasLabel("person").Values()}}, "g_addV_propertyXemptyX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.AddV("person")}, func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().HasLabel("person").Values()}}, "g_addV_propertyXset_nullX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.AddV("foo")}, func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().HasLabel("foo").Values()}}, @@ -691,6 +693,13 @@ var translationMap = map[string][]func(g *gremlingo.GraphTraversalSource, p map[ "g_mergeV_hidden_id_key_onMatch_matched_prohibited": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.AddV("vertex")}, func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.MergeV(map[interface{}]interface{}{}).Option(gremlingo.Merge.OnMatch, p["xx1"])}}, "g_mergeV_hidden_label_key_matched_onMatch_matched_prohibited": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.AddV("vertex")}, func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.MergeV(map[interface{}]interface{}{}).Option(gremlingo.Merge.OnMatch, p["xx1"])}}, "g_mergeV_hidden_label_value_onMatch_matched_prohibited": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.AddV("vertex")}, func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.MergeV(map[interface{}]interface{}{}).Option(gremlingo.Merge.OnMatch, p["xx1"])}}, + "g_mergeVXname_markoX_optionXonMatch_age_listX33XX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.AddV("person").Property("name", "marko").Property(gremlingo.Cardinality.List, "age", 29).Property(gremlingo.Cardinality.List, "age", 31).Property(gremlingo.Cardinality.List, "age", 32)}, func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.MergeV(map[interface{}]interface{}{"name": [...] + "g_mergeVXname_markoX_optionXonMatch_age_setX33XX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.AddV("person").Property("name", "marko").Property(gremlingo.Cardinality.List, "age", 29).Property(gremlingo.Cardinality.List, "age", 31).Property(gremlingo.Cardinality.List, "age", 32)}, func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.MergeV(map[interface{}]interface{}{"name": " [...] + "g_mergeVXname_markoX_optionXonMatch_age_setX31XX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.AddV("person").Property("name", "marko").Property(gremlingo.Cardinality.List, "age", 29).Property(gremlingo.Cardinality.List, "age", 31).Property(gremlingo.Cardinality.List, "age", 32)}, func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.MergeV(map[interface{}]interface{}{"name": " [...] + "g_mergeVXname_markoX_optionXonMatch_age_singleX33XX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.AddV("person").Property("name", "marko").Property(gremlingo.Cardinality.List, "age", 29).Property(gremlingo.Cardinality.List, "age", 31).Property(gremlingo.Cardinality.List, "age", 32)}, func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.MergeV(map[interface{}]interface{}{"name" [...] + "g_mergeVXname_markoX_optionXonMatch_age_33_singleX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.AddV("person").Property("name", "marko").Property(gremlingo.Cardinality.List, "age", 29).Property(gremlingo.Cardinality.List, "age", 31).Property(gremlingo.Cardinality.List, "age", 32)}, func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.MergeV(map[interface{}]interface{}{"name": [...] + "g_mergeVXname_markoX_optionXonMatch_name_allen_age_setX31X_singleX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.AddV("person").Property("name", "marko").Property(gremlingo.Cardinality.List, "age", 29).Property(gremlingo.Cardinality.List, "age", 31).Property(gremlingo.Cardinality.List, "age", 32)}, func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.MergeV(map[interface{}]int [...] + "g_mergeVXname_markoX_optionXonMatch_name_allen_age_singleX31X_singleX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.AddV("person").Property("name", "marko").Property(gremlingo.Cardinality.List, "age", 29).Property(gremlingo.Cardinality.List, "age", 31).Property(gremlingo.Cardinality.List, "age", 32)}, func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.MergeV(map[interface{}] [...] "g_V_age_min": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Values("age").Min()}}, "g_V_foo_min": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Values("foo").Min()}}, "g_V_name_min": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Values("name").Min()}}, diff --git a/gremlin-go/driver/traversal.go b/gremlin-go/driver/traversal.go index 7b315da64a..3fc3e3d225 100644 --- a/gremlin-go/driver/traversal.go +++ b/gremlin-go/driver/traversal.go @@ -153,6 +153,36 @@ var Cardinality = cardinalities{ Set: "set", } +type cv struct { + Bytecode *Bytecode +} + +type CardValue interface { + Single(val interface{}) Bytecode + Set(val interface{}) Bytecode + List(val interface{}) Bytecode +} + +var CardinalityValue CardValue = &cv{} + +func (*cv) Single(val interface{}) Bytecode { + bc := Bytecode{} + bc.AddSource("CardinalityValueTraversal", Cardinality.Single, val) + return bc +} + +func (*cv) Set(val interface{}) Bytecode { + bc := Bytecode{} + bc.AddSource("CardinalityValueTraversal", Cardinality.Set, val) + return bc +} + +func (*cv) List(val interface{}) Bytecode { + bc := Bytecode{} + bc.AddSource("CardinalityValueTraversal", Cardinality.List, val) + return bc +} + type column string type columns struct { @@ -297,8 +327,8 @@ type merges struct { var Merge = merges{ OnCreate: "onCreate", OnMatch: "onMatch", - OutV: "outV", - InV: "inV", + OutV: "outV", + InV: "inV", } type operator string diff --git a/gremlin-javascript/build/generate.groovy b/gremlin-javascript/build/generate.groovy index c726d0e2ff..c7d92e0b29 100644 --- a/gremlin-javascript/build/generate.groovy +++ b/gremlin-javascript/build/generate.groovy @@ -95,6 +95,7 @@ radishGremlinFile.withWriter('UTF-8') { Writer writer -> 'const __ = graphTraversalModule.statics;\n' + 'const Barrier = traversalModule.barrier\n' + 'const Cardinality = traversalModule.cardinality\n' + + 'const CardinalityValue = graphTraversalModule.CardinalityValue;\n' + 'const Column = traversalModule.column\n' + 'const Direction = {\n' + ' BOTH: traversalModule.direction.both,\n' + diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/process/graph-traversal.js b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/process/graph-traversal.js index 328c7f3ed9..20b6b99751 100644 --- a/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/process/graph-traversal.js +++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/process/graph-traversal.js @@ -22,7 +22,7 @@ */ 'use strict'; -const { Traversal } = require('./traversal'); +const { Traversal, cardinality } = require('./traversal'); const { Transaction } = require('./transaction'); const remote = require('../driver/remote-connection'); const Bytecode = require('./bytecode'); @@ -1453,6 +1453,45 @@ class GraphTraversal extends Traversal { } } +class CardinalityValue extends Bytecode { + /** + * Creates a new instance of {@link CardinalityValue}. + * @param {String} card + * @param {Object} value + */ + constructor(card, value) { + super(); + this.addSource('CardinalityValueTraversal', [card, value]); + } + + /** + * Create a value with single cardinality. + * @param {Array} value + * @returns {CardinalityValue} + */ + static single(value) { + return new CardinalityValue(cardinality.single, value); + } + + /** + * Create a value with list cardinality. + * @param {Array} value + * @returns {CardinalityValue} + */ + static list(value) { + return new CardinalityValue(cardinality.list, value); + } + + /** + * Create a value with set cardinality. + * @param {Array} value + * @returns {CardinalityValue} + */ + static set(value) { + return new CardinalityValue(cardinality.set, value); + } +} + function callOnEmptyTraversal(fnName, args) { const g = new GraphTraversal(null, null, new Bytecode()); return g[fnName].apply(g, args); @@ -1564,5 +1603,6 @@ const statics = { module.exports = { GraphTraversal, GraphTraversalSource, + CardinalityValue, statics, }; diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/test/cucumber/gremlin.js b/gremlin-javascript/src/main/javascript/gremlin-javascript/test/cucumber/gremlin.js index b8e867e2fc..8d5966307a 100644 --- a/gremlin-javascript/src/main/javascript/gremlin-javascript/test/cucumber/gremlin.js +++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/test/cucumber/gremlin.js @@ -30,6 +30,7 @@ const { TraversalStrategies, VertexProgramStrategy, OptionsStrategy, ReadOnlyStr const __ = graphTraversalModule.statics; const Barrier = traversalModule.barrier const Cardinality = traversalModule.cardinality +const CardinalityValue = graphTraversalModule.CardinalityValue; const Column = traversalModule.column const Direction = { BOTH: traversalModule.direction.both, @@ -448,6 +449,8 @@ const gremlins = { g_addV_propertyXlabel_personX: [function({g}) { return g.addV().property(T.label,"person") }, function({g}) { return g.V().hasLabel("person") }], g_addV_propertyXmapX: [function({g}) { return g.addV().property("name","foo").property("age",42) }, function({g}) { return g.V().has("name","foo") }], g_addV_propertyXsingle_mapX: [function({g}) { return g.addV().property(Cardinality.single,"name","foo").property(Cardinality.single,"age",42) }, function({g}) { return g.V().has("name","foo") }], + g_V_hasXname_fooX_propertyXname_setXbarX_age_43X: [function({g}) { return g.addV().property(Cardinality.single,"name","foo").property("age",42) }, function({g}) { return g.V().has("name","foo").property(Cardinality.set,"name","bar").property("age",43) }, function({g}) { return g.V().has("name","foo") }, function({g}) { return g.V().has("name","bar") }, function({g}) { return g.V().has("age",43) }, function({g}) { return g.V().has("age",42) }], + g_V_hasXname_fooX_propertyXset_name_bar_age_singleX43XX: [function({g}) { return g.addV().property(Cardinality.single,"name","foo").property("age",42) }, function({g}) { return g.V().has("name","foo").property(Cardinality.set,"name","bar").property(Cardinality.single,"age",43) }, function({g}) { return g.V().has("name","foo") }, function({g}) { return g.V().has("name","bar") }, function({g}) { return g.V().has("age",43) }, function({g}) { return g.V().has("age",42) }], g_addV_propertyXnullX: [function({g}) { return g.addV("person") }, function({g}) { return g.V().hasLabel("person").values() }], g_addV_propertyXemptyX: [function({g}) { return g.addV("person") }, function({g}) { return g.V().hasLabel("person").values() }], g_addV_propertyXset_nullX: [function({g}) { return g.addV("foo") }, function({g}) { return g.V().hasLabel("foo").values() }], @@ -710,6 +713,13 @@ const gremlins = { g_mergeV_hidden_id_key_onMatch_matched_prohibited: [function({g, xx1}) { return g.addV("vertex") }, function({g, xx1}) { return g.mergeV(new Map([])).option(Merge.onMatch,xx1) }], g_mergeV_hidden_label_key_matched_onMatch_matched_prohibited: [function({g, xx1}) { return g.addV("vertex") }, function({g, xx1}) { return g.mergeV(new Map([])).option(Merge.onMatch,xx1) }], g_mergeV_hidden_label_value_onMatch_matched_prohibited: [function({g, xx1}) { return g.addV("vertex") }, function({g, xx1}) { return g.mergeV(new Map([])).option(Merge.onMatch,xx1) }], + g_mergeVXname_markoX_optionXonMatch_age_listX33XX: [function({g}) { return g.addV("person").property("name","marko").property(Cardinality.list,"age",29).property(Cardinality.list,"age",31).property(Cardinality.list,"age",32) }, function({g}) { return g.mergeV(new Map([["name","marko"]])).option(Merge.onMatch,new Map([["age",CardinalityValue.list(33)]])) }, function({g}) { return g.V().has("person","name","marko").has("age",33) }, function({g}) { return g.V().has("person","name","mark [...] + g_mergeVXname_markoX_optionXonMatch_age_setX33XX: [function({g}) { return g.addV("person").property("name","marko").property(Cardinality.list,"age",29).property(Cardinality.list,"age",31).property(Cardinality.list,"age",32) }, function({g}) { return g.mergeV(new Map([["name","marko"]])).option(Merge.onMatch,new Map([["age",CardinalityValue.set(33)]])) }, function({g}) { return g.V().has("person","name","marko").has("age",33) }, function({g}) { return g.V().has("person","name","marko" [...] + g_mergeVXname_markoX_optionXonMatch_age_setX31XX: [function({g}) { return g.addV("person").property("name","marko").property(Cardinality.list,"age",29).property(Cardinality.list,"age",31).property(Cardinality.list,"age",32) }, function({g}) { return g.mergeV(new Map([["name","marko"]])).option(Merge.onMatch,new Map([["age",CardinalityValue.set(31)]])) }, function({g}) { return g.V().has("person","name","marko").has("age",31) }, function({g}) { return g.V().has("person","name","marko" [...] + g_mergeVXname_markoX_optionXonMatch_age_singleX33XX: [function({g}) { return g.addV("person").property("name","marko").property(Cardinality.list,"age",29).property(Cardinality.list,"age",31).property(Cardinality.list,"age",32) }, function({g}) { return g.mergeV(new Map([["name","marko"]])).option(Merge.onMatch,new Map([["age",CardinalityValue.single(33)]])) }, function({g}) { return g.V().has("person","name","marko").has("age",33) }, function({g}) { return g.V().has("person","name"," [...] + g_mergeVXname_markoX_optionXonMatch_age_33_singleX: [function({g}) { return g.addV("person").property("name","marko").property(Cardinality.list,"age",29).property(Cardinality.list,"age",31).property(Cardinality.list,"age",32) }, function({g}) { return g.mergeV(new Map([["name","marko"]])).option(Merge.onMatch,new Map([["age",33]]),Cardinality.single) }, function({g}) { return g.V().has("person","name","marko").has("age",33) }, function({g}) { return g.V().has("person","name","marko") [...] + g_mergeVXname_markoX_optionXonMatch_name_allen_age_setX31X_singleX: [function({g}) { return g.addV("person").property("name","marko").property(Cardinality.list,"age",29).property(Cardinality.list,"age",31).property(Cardinality.list,"age",32) }, function({g}) { return g.mergeV(new Map([["name","marko"]])).option(Merge.onMatch,new Map([["name","allen"],["age",CardinalityValue.set(31)]]),Cardinality.single) }, function({g}) { return g.V().has("person","name","marko") }, function({g}) { [...] + g_mergeVXname_markoX_optionXonMatch_name_allen_age_singleX31X_singleX: [function({g}) { return g.addV("person").property("name","marko").property(Cardinality.list,"age",29).property(Cardinality.list,"age",31).property(Cardinality.list,"age",32) }, function({g}) { return g.mergeV(new Map([["name","marko"]])).option(Merge.onMatch,new Map([["name","allen"],["age",CardinalityValue.single(31)]]),Cardinality.single) }, function({g}) { return g.V().has("person","name","marko") }, function({ [...] g_V_age_min: [function({g}) { return g.V().values("age").min() }], g_V_foo_min: [function({g}) { return g.V().values("foo").min() }], g_V_name_min: [function({g}) { return g.V().values("name").min() }], diff --git a/gremlin-language/src/main/antlr4/Gremlin.g4 b/gremlin-language/src/main/antlr4/Gremlin.g4 index 4b04e21f58..876524c934 100644 --- a/gremlin-language/src/main/antlr4/Gremlin.g4 +++ b/gremlin-language/src/main/antlr4/Gremlin.g4 @@ -575,6 +575,7 @@ traversalMethod_not traversalMethod_option : 'option' LPAREN traversalPredicate COMMA nestedTraversal RPAREN #traversalMethod_option_Predicate_Traversal | 'option' LPAREN traversalMergeArgument COMMA genericLiteralMapNullableArgument RPAREN #traversalMethod_option_Merge_Map + | 'option' LPAREN traversalMergeArgument COMMA genericLiteralMapNullableArgument COMMA traversalCardinality RPAREN #traversalMethod_option_Merge_Map_Cardinality | 'option' LPAREN traversalMergeArgument COMMA nestedTraversal RPAREN #traversalMethod_option_Merge_Traversal | 'option' LPAREN genericLiteralArgument COMMA nestedTraversal RPAREN #traversalMethod_option_Object_Traversal | 'option' LPAREN nestedTraversal RPAREN #traversalMethod_option_Traversal @@ -916,7 +917,13 @@ traversalDirection ; traversalCardinality - : 'single' | 'Cardinality.single' + : 'Cardinality.single' LPAREN genericLiteral RPAREN + | 'Cardinality.set' LPAREN genericLiteral RPAREN + | 'Cardinality.list' LPAREN genericLiteral RPAREN + | 'single' LPAREN genericLiteral RPAREN + | 'set' LPAREN genericLiteral RPAREN + | 'list' LPAREN genericLiteral RPAREN + | 'single' | 'Cardinality.single' | 'set' | 'Cardinality.set' | 'list' | 'Cardinality.list' ; diff --git a/gremlin-python/build/generate.groovy b/gremlin-python/build/generate.groovy index 6ccfa9ee86..78a065499b 100644 --- a/gremlin-python/build/generate.groovy +++ b/gremlin-python/build/generate.groovy @@ -93,7 +93,7 @@ radishGremlinFile.withWriter('UTF-8') { Writer writer -> 'from gremlin_python.process.traversal import TraversalStrategy\n' + 'from gremlin_python.process.graph_traversal import __\n' + 'from gremlin_python.structure.graph import Graph\n' + - 'from gremlin_python.process.traversal import Barrier, Cardinality, P, TextP, Pop, Scope, Column, Order, Direction, Merge, T, Pick, Operator, IO, WithOptions\n') + 'from gremlin_python.process.traversal import Barrier, Cardinality, CardinalityValue, P, TextP, Pop, Scope, Column, Order, Direction, Merge, T, Pick, Operator, IO, WithOptions\n') // Groovy can't process certain null oriented calls because it gets confused with the right overload to call // at runtime. using this approach for now as these are the only such situations encountered so far. a better diff --git a/gremlin-python/src/main/python/gremlin_python/process/traversal.py b/gremlin-python/src/main/python/gremlin_python/process/traversal.py index dc259d3d6f..a83048e3b0 100644 --- a/gremlin-python/src/main/python/gremlin_python/process/traversal.py +++ b/gremlin-python/src/main/python/gremlin_python/process/traversal.py @@ -817,6 +817,24 @@ class Bytecode(object): return Bytecode._create_graph_op("tx", "rollback") +class CardinalityValue(Bytecode): + def __init__(self, cardinality, val): + super().__init__() + self.add_source("CardinalityValueTraversal", cardinality, val) + + @classmethod + def single(cls, val): + return CardinalityValue(Cardinality.single, val) + + @classmethod + def list_(cls, val): + return CardinalityValue(Cardinality.list_, val) + + @classmethod + def set_(cls, val): + return CardinalityValue(Cardinality.set_, val) + + ''' BINDINGS ''' diff --git a/gremlin-python/src/main/python/radish/gremlin.py b/gremlin-python/src/main/python/radish/gremlin.py index c44c33ecb6..cbcac3240a 100644 --- a/gremlin-python/src/main/python/radish/gremlin.py +++ b/gremlin-python/src/main/python/radish/gremlin.py @@ -29,7 +29,7 @@ from gremlin_python.process.anonymous_traversal import traversal from gremlin_python.process.traversal import TraversalStrategy from gremlin_python.process.graph_traversal import __ from gremlin_python.structure.graph import Graph -from gremlin_python.process.traversal import Barrier, Cardinality, P, TextP, Pop, Scope, Column, Order, Direction, Merge, T, Pick, Operator, IO, WithOptions +from gremlin_python.process.traversal import Barrier, Cardinality, CardinalityValue, P, TextP, Pop, Scope, Column, Order, Direction, Merge, T, Pick, Operator, IO, WithOptions world.gremlins = { 'g_V_branchXlabel_eq_person__a_bX_optionXa__ageX_optionXb__langX_optionXb__nameX': [(lambda g, l1=None:g.V().branch(l1).option('a',__.age).option('b',__.lang).option('b',__.name))], @@ -430,6 +430,8 @@ world.gremlins = { 'g_addV_propertyXlabel_personX': [(lambda g:g.addV().property(T.label,'person')), (lambda g:g.V().hasLabel('person'))], 'g_addV_propertyXmapX': [(lambda g:g.addV().property('name','foo').property('age',42)), (lambda g:g.V().has('name','foo'))], 'g_addV_propertyXsingle_mapX': [(lambda g:g.addV().property(Cardinality.single,'name','foo').property(Cardinality.single,'age',42)), (lambda g:g.V().has('name','foo'))], + 'g_V_hasXname_fooX_propertyXname_setXbarX_age_43X': [(lambda g:g.addV().property(Cardinality.single,'name','foo').property('age',42)), (lambda g:g.V().has('name','foo').property(Cardinality.set_,'name','bar').property('age',43)), (lambda g:g.V().has('name','foo')), (lambda g:g.V().has('name','bar')), (lambda g:g.V().has('age',43)), (lambda g:g.V().has('age',42))], + 'g_V_hasXname_fooX_propertyXset_name_bar_age_singleX43XX': [(lambda g:g.addV().property(Cardinality.single,'name','foo').property('age',42)), (lambda g:g.V().has('name','foo').property(Cardinality.set_,'name','bar').property(Cardinality.single,'age',43)), (lambda g:g.V().has('name','foo')), (lambda g:g.V().has('name','bar')), (lambda g:g.V().has('age',43)), (lambda g:g.V().has('age',42))], 'g_addV_propertyXnullX': [(lambda g:g.addV('person')), (lambda g:g.V().hasLabel('person').values())], 'g_addV_propertyXemptyX': [(lambda g:g.addV('person')), (lambda g:g.V().hasLabel('person').values())], 'g_addV_propertyXset_nullX': [(lambda g:g.addV('foo')), (lambda g:g.V().hasLabel('foo').values())], @@ -692,6 +694,13 @@ world.gremlins = { 'g_mergeV_hidden_id_key_onMatch_matched_prohibited': [(lambda g, xx1=None:g.addV('vertex')), (lambda g, xx1=None:g.merge_v({}).option(Merge.on_match,xx1))], 'g_mergeV_hidden_label_key_matched_onMatch_matched_prohibited': [(lambda g, xx1=None:g.addV('vertex')), (lambda g, xx1=None:g.merge_v({}).option(Merge.on_match,xx1))], 'g_mergeV_hidden_label_value_onMatch_matched_prohibited': [(lambda g, xx1=None:g.addV('vertex')), (lambda g, xx1=None:g.merge_v({}).option(Merge.on_match,xx1))], + 'g_mergeVXname_markoX_optionXonMatch_age_listX33XX': [(lambda g:g.addV('person').property('name','marko').property(Cardinality.list_,'age',29).property(Cardinality.list_,'age',31).property(Cardinality.list_,'age',32)), (lambda g:g.merge_v({'name':'marko'}).option(Merge.on_match,{'age':CardinalityValue.list_(33)})), (lambda g:g.V().has('person','name','marko').has('age',33)), (lambda g:g.V().has('person','name','marko').has('age')), (lambda g:g.V().has('person','name','marko').propert [...] + 'g_mergeVXname_markoX_optionXonMatch_age_setX33XX': [(lambda g:g.addV('person').property('name','marko').property(Cardinality.list_,'age',29).property(Cardinality.list_,'age',31).property(Cardinality.list_,'age',32)), (lambda g:g.merge_v({'name':'marko'}).option(Merge.on_match,{'age':CardinalityValue.set_(33)})), (lambda g:g.V().has('person','name','marko').has('age',33)), (lambda g:g.V().has('person','name','marko').has('age')), (lambda g:g.V().has('person','name','marko').propertie [...] + 'g_mergeVXname_markoX_optionXonMatch_age_setX31XX': [(lambda g:g.addV('person').property('name','marko').property(Cardinality.list_,'age',29).property(Cardinality.list_,'age',31).property(Cardinality.list_,'age',32)), (lambda g:g.merge_v({'name':'marko'}).option(Merge.on_match,{'age':CardinalityValue.set_(31)})), (lambda g:g.V().has('person','name','marko').has('age',31)), (lambda g:g.V().has('person','name','marko').has('age')), (lambda g:g.V().has('person','name','marko').propertie [...] + 'g_mergeVXname_markoX_optionXonMatch_age_singleX33XX': [(lambda g:g.addV('person').property('name','marko').property(Cardinality.list_,'age',29).property(Cardinality.list_,'age',31).property(Cardinality.list_,'age',32)), (lambda g:g.merge_v({'name':'marko'}).option(Merge.on_match,{'age':CardinalityValue.single(33)})), (lambda g:g.V().has('person','name','marko').has('age',33)), (lambda g:g.V().has('person','name','marko').has('age')), (lambda g:g.V().has('person','name','marko').prop [...] + 'g_mergeVXname_markoX_optionXonMatch_age_33_singleX': [(lambda g:g.addV('person').property('name','marko').property(Cardinality.list_,'age',29).property(Cardinality.list_,'age',31).property(Cardinality.list_,'age',32)), (lambda g:g.merge_v({'name':'marko'}).option(Merge.on_match,{'age':33},Cardinality.single)), (lambda g:g.V().has('person','name','marko').has('age',33)), (lambda g:g.V().has('person','name','marko').has('age')), (lambda g:g.V().has('person','name','marko').properties( [...] + 'g_mergeVXname_markoX_optionXonMatch_name_allen_age_setX31X_singleX': [(lambda g:g.addV('person').property('name','marko').property(Cardinality.list_,'age',29).property(Cardinality.list_,'age',31).property(Cardinality.list_,'age',32)), (lambda g:g.merge_v({'name':'marko'}).option(Merge.on_match,{'name':'allen','age':CardinalityValue.set_(31)},Cardinality.single)), (lambda g:g.V().has('person','name','marko')), (lambda g:g.V().has('person','name','allen').has('age',31)), (lambda g:g.V [...] + 'g_mergeVXname_markoX_optionXonMatch_name_allen_age_singleX31X_singleX': [(lambda g:g.addV('person').property('name','marko').property(Cardinality.list_,'age',29).property(Cardinality.list_,'age',31).property(Cardinality.list_,'age',32)), (lambda g:g.merge_v({'name':'marko'}).option(Merge.on_match,{'name':'allen','age':CardinalityValue.single(31)},Cardinality.single)), (lambda g:g.V().has('person','name','marko')), (lambda g:g.V().has('person','name','allen').has('age',33)), (lambda [...] 'g_V_age_min': [(lambda g:g.V().age.min_())], 'g_V_foo_min': [(lambda g:g.V().foo.min_())], 'g_V_name_min': [(lambda g:g.V().name.min_())], diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/AddVertex.feature b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/AddVertex.feature index 505969a59d..e46c91e064 100644 --- a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/AddVertex.feature +++ b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/AddVertex.feature @@ -429,7 +429,6 @@ Feature: Step - addV() Then the result should have a count of 1 And the graph should return 1 for count of "g.V().hasLabel(\"person\")" - Scenario: g_addV_propertyXmapX Given the empty graph And the traversal of @@ -450,6 +449,42 @@ Feature: Step - addV() Then the result should have a count of 1 And the graph should return 1 for count of "g.V().has(\"name\",\"foo\")" + @MultiMetaProperties + Scenario: g_V_hasXname_fooX_propertyXname_setXbarX_age_43X + Given the empty graph + And the graph initializer of + """ + g.addV().property(Cardinality.single, "name", "foo").property("age", 42) + """ + And the traversal of + """ + g.V().has('name','foo').property(["name": Cardinality.set("bar"), "age": 43 ]) + """ + When iterated to list + Then the result should have a count of 1 + And the graph should return 1 for count of "g.V().has(\"name\",\"foo\")" + And the graph should return 1 for count of "g.V().has(\"name\",\"bar\")" + And the graph should return 1 for count of "g.V().has(\"age\",43)" + And the graph should return 0 for count of "g.V().has(\"age\",42)" + + @MultiMetaProperties + Scenario: g_V_hasXname_fooX_propertyXset_name_bar_age_singleX43XX + Given the empty graph + And the graph initializer of + """ + g.addV().property(Cardinality.single, "name", "foo").property("age", 42) + """ + And the traversal of + """ + g.V().has('name','foo').property(Cardinality.set, ["name":"bar", "age": Cardinality.single(43) ]) + """ + When iterated to list + Then the result should have a count of 1 + And the graph should return 1 for count of "g.V().has(\"name\",\"foo\")" + And the graph should return 1 for count of "g.V().has(\"name\",\"bar\")" + And the graph should return 1 for count of "g.V().has(\"age\",43)" + And the graph should return 0 for count of "g.V().has(\"age\",42)" + Scenario: g_addV_propertyXnullX Given the empty graph And the traversal of diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/MergeVertex.feature b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/MergeVertex.feature index 91de838781..a71737e4fb 100644 --- a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/MergeVertex.feature +++ b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/MergeVertex.feature @@ -842,3 +842,132 @@ Feature: Step - mergeV() """ When iterated to list Then the traversal will raise an error + + @MultiMetaProperties + Scenario: g_mergeVXname_markoX_optionXonMatch_age_listX33XX + Given the empty graph + And the graph initializer of + """ + g.addV("person").property("name", "marko").property(Cardinality.list, "age", 29).property(Cardinality.list, "age", 31).property(Cardinality.list, "age", 32) + """ + And the traversal of + """ + g.mergeV([name: "marko"]). + option(Merge.onMatch, [age: Cardinality.list(33)]) + """ + When iterated to list + Then the result should have a count of 1 + And the graph should return 1 for count of "g.V().has(\"person\",\"name\",\"marko\").has(\"age\", 33)" + And the graph should return 1 for count of "g.V().has(\"person\",\"name\",\"marko\").has(\"age\")" + And the graph should return 4 for count of "g.V().has(\"person\",\"name\",\"marko\").properties(\"age\")" + + @MultiMetaProperties + Scenario: g_mergeVXname_markoX_optionXonMatch_age_setX33XX + Given the empty graph + And the graph initializer of + """ + g.addV("person").property("name", "marko").property(Cardinality.list, "age", 29).property(Cardinality.list, "age", 31).property(Cardinality.list, "age", 32) + """ + And the traversal of + """ + g.mergeV([name: "marko"]). + option(Merge.onMatch, [age: Cardinality.set(33)]) + """ + When iterated to list + Then the result should have a count of 1 + And the graph should return 1 for count of "g.V().has(\"person\",\"name\",\"marko\").has(\"age\", 33)" + And the graph should return 1 for count of "g.V().has(\"person\",\"name\",\"marko\").has(\"age\")" + And the graph should return 4 for count of "g.V().has(\"person\",\"name\",\"marko\").properties(\"age\")" + + @MultiMetaProperties + Scenario: g_mergeVXname_markoX_optionXonMatch_age_setX31XX + Given the empty graph + And the graph initializer of + """ + g.addV("person").property("name", "marko").property(Cardinality.list, "age", 29).property(Cardinality.list, "age", 31).property(Cardinality.list, "age", 32) + """ + And the traversal of + """ + g.mergeV([name: "marko"]). + option(Merge.onMatch, [age: Cardinality.set(31)]) + """ + When iterated to list + Then the result should have a count of 1 + And the graph should return 1 for count of "g.V().has(\"person\",\"name\",\"marko\").has(\"age\", 31)" + And the graph should return 1 for count of "g.V().has(\"person\",\"name\",\"marko\").has(\"age\")" + And the graph should return 3 for count of "g.V().has(\"person\",\"name\",\"marko\").properties(\"age\")" + + @MultiMetaProperties + Scenario: g_mergeVXname_markoX_optionXonMatch_age_singleX33XX + Given the empty graph + And the graph initializer of + """ + g.addV("person").property("name", "marko").property(Cardinality.list, "age", 29).property(Cardinality.list, "age", 31).property(Cardinality.list, "age", 32) + """ + And the traversal of + """ + g.mergeV([name: "marko"]). + option(Merge.onMatch, [age: Cardinality.single(33)]) + """ + When iterated to list + Then the result should have a count of 1 + And the graph should return 1 for count of "g.V().has(\"person\",\"name\",\"marko\").has(\"age\", 33)" + And the graph should return 1 for count of "g.V().has(\"person\",\"name\",\"marko\").has(\"age\")" + And the graph should return 1 for count of "g.V().has(\"person\",\"name\",\"marko\").properties(\"age\")" + + @MultiMetaProperties + Scenario: g_mergeVXname_markoX_optionXonMatch_age_33_singleX + Given the empty graph + And the graph initializer of + """ + g.addV("person").property("name", "marko").property(Cardinality.list, "age", 29).property(Cardinality.list, "age", 31).property(Cardinality.list, "age", 32) + """ + And the traversal of + """ + g.mergeV([name: "marko"]). + option(Merge.onMatch, [age: 33], Cardinality.single) + """ + When iterated to list + Then the result should have a count of 1 + And the graph should return 1 for count of "g.V().has(\"person\",\"name\",\"marko\").has(\"age\", 33)" + And the graph should return 1 for count of "g.V().has(\"person\",\"name\",\"marko\").has(\"age\")" + And the graph should return 1 for count of "g.V().has(\"person\",\"name\",\"marko\").properties(\"age\")" + + @MultiMetaProperties + Scenario: g_mergeVXname_markoX_optionXonMatch_name_allen_age_setX31X_singleX + Given the empty graph + And the graph initializer of + """ + g.addV("person").property("name", "marko").property(Cardinality.list, "age", 29).property(Cardinality.list, "age", 31).property(Cardinality.list, "age", 32) + """ + And the traversal of + """ + g.mergeV([name: "marko"]). + option(Merge.onMatch, [name: "allen", age: Cardinality.set(31)], single) + """ + When iterated to list + Then the result should have a count of 1 + And the graph should return 0 for count of "g.V().has(\"person\",\"name\",\"marko\")" + And the graph should return 1 for count of "g.V().has(\"person\",\"name\",\"allen\").has(\"age\", 31)" + And the graph should return 1 for count of "g.V().has(\"person\",\"name\",\"allen\").has(\"age\")" + And the graph should return 3 for count of "g.V().has(\"person\",\"name\",\"allen\").properties(\"age\")" + + @MultiMetaProperties + Scenario: g_mergeVXname_markoX_optionXonMatch_name_allen_age_singleX31X_singleX + Given the empty graph + And the graph initializer of + """ + g.addV("person").property("name", "marko").property(Cardinality.list, "age", 29).property(Cardinality.list, "age", 31).property(Cardinality.list, "age", 32) + """ + And the traversal of + """ + g.mergeV([name: "marko"]). + option(Merge.onMatch, [name: "allen", age: Cardinality.single(31)], single) + """ + When iterated to list + Then the result should have a count of 1 + And the graph should return 0 for count of "g.V().has(\"person\",\"name\",\"marko\")" + And the graph should return 0 for count of "g.V().has(\"person\",\"name\",\"allen\").has(\"age\", 33)" + And the graph should return 1 for count of "g.V().has(\"person\",\"name\",\"allen\").has(\"age\", 31)" + And the graph should return 1 for count of "g.V().has(\"person\",\"name\",\"allen\").has(\"age\")" + And the graph should return 1 for count of "g.V().has(\"person\",\"name\",\"allen\").properties(\"age\")" \ No newline at end of file
