diff --git a/docker/gremlin-server/gremlin-server-integration.yaml b/docker/gremlin-server/gremlin-server-integration.yaml index a730970f851..4f2f226b420 100644 --- a/docker/gremlin-server/gremlin-server-integration.yaml +++ b/docker/gremlin-server/gremlin-server-integration.yaml @@ -44,6 +44,9 @@ graphs: { sink: { configuration: conf/tinkergraph-service.properties, traversalSources: [{name: gsink}]}, + zoo: { + configuration: conf/tinkergraph-multilabel.properties, + traversalSources: [{name: gzoo}]}, tx: { configuration: conf/tinkertransactiongraph-service.properties, traversalSources: [{name: gtx}]}} @@ -53,6 +56,7 @@ lifecycleHooks: - { className: org.apache.tinkerpop.gremlin.server.util.TinkerFactoryDataLoader, config: {graph: crew, dataset: crew}} - { className: org.apache.tinkerpop.gremlin.server.util.TinkerFactoryDataLoader, config: {graph: grateful, dataset: grateful}} - { className: org.apache.tinkerpop.gremlin.server.util.TinkerFactoryDataLoader, config: {graph: sink, dataset: sink}} + - { className: org.apache.tinkerpop.gremlin.server.util.TinkerFactoryDataLoader, config: {graph: zoo, dataset: zoo}} scriptEngines: { gremlin-lang : {}, gremlin-groovy: { diff --git a/docs/src/reference/the-graph.asciidoc b/docs/src/reference/the-graph.asciidoc index 75bd02a67ce..5e92a2dd1e4 100644 --- a/docs/src/reference/the-graph.asciidoc +++ b/docs/src/reference/the-graph.asciidoc @@ -206,6 +206,34 @@ link:https://tinkerpop.apache.org/docs/x.y.z/dev/provider/#_label_cardinality[pr details and the <>, <>, <>, and <> steps for traversal access. +[[the-zoo-toy-graph]] +TIP: A toy graph demonstrating multi-label vertices alongside a variety of property types is available at +`TinkerFactory.createTheZoo()` and `data/tinkerpop-zoo*`. Unlike "The Crew" above, which requires a graph configured +for vertex multi-properties and meta-properties, "The Zoo" requires a `LabelCardinality` of `ONE_OR_MORE` or +`ZERO_OR_MORE`. + +.The Zoo +image::the-zoo-graph.png[width=685] + +"The Zoo" models animals, their habitats, and the people who care for them. Animals carry the "animal" label plus +any number of descriptive labels - such as "aquatic", "nocturnal", or "endangered" - reflecting the traits that +apply to them, which is where the graph's multi-label vertices come into play: + +[gremlin-groovy,theZoo] +---- +g.V().hasLabel('animal').valueMap('name','species') +g.V().has('name','tux').labels() <1> +g.V().hasLabel('endangered').values('name') <2> +g.V().has('name','tux').out('livesIn').values('name','biome') <3> +---- + +<1> `tux` carries several labels at once - "animal", "bird", "aquatic", and "endangered" - since vertex labels are +not limited to exactly one for this graph. +<2> Filtering by a single label, such as "endangered", surfaces every animal that carries it, regardless of what +other labels are also present. +<3> Habitats, like "lagoon" and "canopy", are modeled as ordinary single-labeled vertices connected to their +resident animals with a "livesIn" edge. + [[nullable-property-values]] === Nullable Property Values diff --git a/docs/src/reference/the-traversal.asciidoc b/docs/src/reference/the-traversal.asciidoc index ebff11d799b..ec2d4649cb8 100644 --- a/docs/src/reference/the-traversal.asciidoc +++ b/docs/src/reference/the-traversal.asciidoc @@ -1112,23 +1112,17 @@ multi-label vertex returns only one label chosen non-deterministically. Steps li `dedup()`, `order()`, `path()`, `simplePath()`, and `tree()` that modulate `by(label)` will therefore behave unpredictably on multi-label vertices. -[gremlin-groovy] +[gremlin-groovy,theZoo] ---- -conf = new BaseConfiguration() -conf.setProperty("gremlin.tinkergraph.vertexLabelCardinality", "ZERO_OR_MORE") -graph = TinkerGraph.open(conf) -g = traversal().with(graph) -g.addV('person','employee').property('name','marko') -g.addV('person').property('name','vadas') g.V().group().by(label).by(values('name').fold()) <1> g.V().group().by(labels().fold()).by(values('name').fold()) <2> ---- -<1> marko has both the "person" and "employee" labels, but is grouped under only one of them, chosen -non-deterministically, so this grouping is not reliable for multi-label vertices. -<2> Grouping by the full set of labels instead keeps marko under `[person, employee]`, distinct from vadas under -`[person]`, regardless of which order the labels were assigned in. Prefer this pattern with the -<> step over `by(label)` for graphs that support multiple labels per vertex. +<1> `atlas` carries the "animal", "reptile", "aquatic", and "endangered" labels, but is grouped under only one of +them, chosen non-deterministically, so this grouping is not reliable for multi-label vertices. +<2> Grouping by the full set of labels instead keeps each animal under its own distinct combination of labels. +Prefer this pattern with the <> step over `by(label)` for graphs that support multiple +labels per vertex. *Additional References* @@ -1323,22 +1317,17 @@ On the "modern" graph, that works reliably because each vertex has exactly one l tells a different story, since `choose(T.label)` still selects a branch based on a single label chosen non-deterministically from the full set: -[gremlin-groovy] +[gremlin-groovy,theZoo] ---- -conf = new BaseConfiguration() -conf.setProperty("gremlin.tinkergraph.vertexLabelCardinality", "ZERO_OR_MORE") -graph = TinkerGraph.open(conf) -g = traversal().with(graph) -g.addV('person','employee').property('name','marko') -g.V().has('name','marko').choose(T.label). - option('person', constant('routed as a person')). - option('employee', constant('routed as an employee')) <1> +g.V().has('name','tux').choose(T.label). + option('bird', constant('routed as a bird')). + option('aquatic', constant('routed as aquatic')) <1> ---- -<1> marko carries both the "person" and "employee" labels, so which `option()` branch is taken depends on which -label the graph happens to return from `label()` and is not guaranteed to be consistent. Filtering with -<> beforehand, or restructuring the choice around `labels()`, gives predictable results for -multi-label vertices. +<1> `tux` carries the "animal", "bird", "aquatic", and "endangered" labels, so which `option()` branch is taken +depends on which label the graph happens to return from `label()` and is not guaranteed to be consistent. Filtering +with <> beforehand, or restructuring the choice around `labels()`, gives predictable results +for multi-label vertices. The `Pick` enum was introduced in an example earlier to handle non-matching scenarios. The following `Pick` options may be used with `choose()`: @@ -1954,18 +1943,13 @@ as the `id` is the only available data to the star graph. For multi-label graphs, `elementMap()` returns labels as a single string by default. To receive all labels as a set, use `with("multilabel")` either per-traversal or on a persistent source: -[gremlin-groovy] +[gremlin-groovy,theZoo] ---- -conf = new BaseConfiguration() -conf.setProperty("gremlin.tinkergraph.vertexLabelCardinality", "ZERO_OR_MORE") -graph = TinkerGraph.open(conf) -g = traversal().with(graph) -g.addV('person').property('name','marko').addLabel('employee').addLabel('manager').iterate() -g.V().has('name','marko').elementMap() <1> -g.with("multilabel").V().has('name','marko').elementMap() <2> +g.V().has('name','tux').elementMap() <1> +g.with("multilabel").V().has('name','tux').elementMap() <2> gml = g.with("multilabel") -gml.V().has('name','marko').elementMap() <3> -gml.V().has('name','marko').valueMap(true) <4> +gml.V().has('name','tux').elementMap() <3> +gml.V().has('name','tux').valueMap(true) <4> ---- <1> Without `"multilabel"`, the label entry is a single string. @@ -2326,20 +2310,15 @@ the key,value pairs for those vertices. Chained `hasLabel()` calls requires that a vertex carry all of the specified labels, which matters in cases where a vertex can have more than one label: -[gremlin-groovy] +[gremlin-groovy,theZoo] ---- -conf = new BaseConfiguration() -conf.setProperty("gremlin.tinkergraph.vertexLabelCardinality", "ZERO_OR_MORE") -graph = TinkerGraph.open(conf) -g = traversal().with(graph) -g.addV('person','employee').property('name','marko') -g.addV('person').property('name','vadas') -g.V().hasLabel('person','employee').values('name') <1> -g.V().hasLabel('person').hasLabel('employee').values('name') <2> +g.V().hasLabel('bird','reptile').values('name') <1> +g.V().hasLabel('bird').hasLabel('aquatic').values('name') <2> ---- -<1> OR semantics: matches both marko and vadas, since each has at least one of "person" or "employee". -<2> Chaining `hasLabel()` calls requires both labels to be present, matching only marko. +<1> OR semantics: matches `tux`, `atlas`, and `monty`, since each has at least one of "bird" or "reptile". +<2> Chaining `hasLabel()` calls requires both labels to be present, matching only `tux`, which has both "bird" and +"aquatic". *Additional References* @@ -2732,19 +2711,14 @@ g.V(1).properties().label() For elements that carry exactly one label, as in the "modern" graph above, `label()` behaves as expected. A multi-label vertex complicates matters, since `label()` still returns only a single `String`: -[gremlin-groovy] +[gremlin-groovy,theZoo] ---- -conf = new BaseConfiguration() -conf.setProperty("gremlin.tinkergraph.vertexLabelCardinality", "ZERO_OR_MORE") -graph = TinkerGraph.open(conf) -g = traversal().with(graph) -g.addV('person','employee').property('name','marko') -g.V().has('name','marko').label() <1> -g.V().has('name','marko').labels() <2> +g.V().has('name','tux').label() <1> +g.V().has('name','tux').labels() <2> ---- -<1> marko carries both the "person" and "employee" labels, but `label()` returns only one of them, chosen -non-deterministically. +<1> `tux` carries the "animal", "bird", "aquatic", and "endangered" labels, but `label()` returns only one of them, +chosen non-deterministically. <2> Use the <> step instead to reliably retrieve every label a vertex carries. *Additional References* @@ -2757,13 +2731,8 @@ link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gre The `labels()`-step (*flatMap*) takes an `Element` and emits each of its labels as a separate traverser. For elements with a single label this behaves identically to `label()`. For multi-label elements, it emits one traverser per label. -[gremlin-groovy] +[gremlin-groovy,theZoo] ---- -conf = new BaseConfiguration() -conf.setProperty("gremlin.tinkergraph.vertexLabelCardinality", "ZERO_OR_MORE") -graph = TinkerGraph.open(conf) -g = traversal().with(graph) -g.addV('person').property('name','marko').addLabel('employee').iterate() g.V().labels() g.V().labels().count() ---- @@ -5685,15 +5654,10 @@ g.V().hasLabel('person').properties('location').valueMap().with(WithOptions.toke For multi-label graphs, the label value in the map is a single string by default. Use `with("multilabel")` to receive all labels as a set: -[gremlin-groovy] +[gremlin-groovy,theZoo] ---- -conf = new BaseConfiguration() -conf.setProperty("gremlin.tinkergraph.vertexLabelCardinality", "ZERO_OR_MORE") -graph = TinkerGraph.open(conf) -g = traversal().with(graph) -g.addV('person').property('name','marko').addLabel('employee').iterate() -g.V().has('name','marko').valueMap(true) <1> -g.with("multilabel").V().has('name','marko').valueMap(true) <2> +g.V().has('name','tux').valueMap(true) <1> +g.with("multilabel").V().has('name','tux').valueMap(true) <2> ---- <1> Without `"multilabel"`, the label entry is a single string. diff --git a/docs/src/tutorials/the-gremlin-console/index.asciidoc b/docs/src/tutorials/the-gremlin-console/index.asciidoc index 195ba630ade..659ffbdf74c 100644 --- a/docs/src/tutorials/the-gremlin-console/index.asciidoc +++ b/docs/src/tutorials/the-gremlin-console/index.asciidoc @@ -113,6 +113,9 @@ labels are defined and the "weight" edge property is a `double` rather than a `f * `createTheCrew()` - A graph that demonstrates usage of the new structural features of TinkerPop 3.x such as link:https://tinkerpop.apache.org/docs/x.y.z/reference/#vertex-properties[vertex meta-properties and multi-properties] (link:https://tinkerpop.apache.org/docs/x.y.z/images/the-crew-graph.png[diagram]). +* `createTheZoo()` - A graph that demonstrates usage of TinkerPop 4.x +link:https://tinkerpop.apache.org/docs/x.y.z/reference/#vertex-labels[multi-label vertices] alongside a variety of +property types (link:https://tinkerpop.apache.org/docs/x.y.z/images/the-zoo-graph.png[diagram]). [gremlin-groovy] ---- diff --git a/docs/static/images/the-zoo-graph.png b/docs/static/images/the-zoo-graph.png new file mode 100644 index 00000000000..7f09a860e7a Binary files /dev/null and b/docs/static/images/the-zoo-graph.png differ diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/util/Attachable.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/util/Attachable.java index 76671fdaa48..6e834a80c6b 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/util/Attachable.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/util/Attachable.java @@ -36,7 +36,9 @@ import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.Set; import java.util.function.Function; +import java.util.stream.Collectors; /** * An interface that provides methods for detached properties and elements to be re-attached to the {@link Graph}. @@ -304,6 +306,12 @@ public static Vertex createVertex(final Attachable attachableVertex, fin final Vertex vertex = hostGraph.features().vertex().willAllowId(baseVertex.id()) ? hostGraph.addVertex(T.id, baseVertex.id(), T.label, baseVertex.label()) : hostGraph.addVertex(T.label, baseVertex.label()); + final Set labels = baseVertex.labels(); + if (labels.size() > 1) { + final List additionalLabels = labels.stream().filter(l -> !l.equals(baseVertex.label())).collect(Collectors.toList()); + if (!additionalLabels.isEmpty()) + vertex.addLabel(additionalLabels.get(0), additionalLabels.subList(1, additionalLabels.size()).toArray(new String[0])); + } final Map>> propertyMap = new HashMap<>(); baseVertex.properties().forEachRemaining(vp -> propertyMap.computeIfAbsent(vp.key(), k -> new ArrayList<>()).add(vp)); for (Map.Entry>> entry : propertyMap.entrySet()) { diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/util/star/StarGraphGraphSONDeserializer.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/util/star/StarGraphGraphSONDeserializer.java index 2742e7d3804..c2224f55887 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/util/star/StarGraphGraphSONDeserializer.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/util/star/StarGraphGraphSONDeserializer.java @@ -25,6 +25,8 @@ import org.apache.tinkerpop.gremlin.structure.util.Attachable; import java.io.IOException; +import java.util.Collections; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.function.Function; @@ -69,7 +71,12 @@ public static void readStarGraphEdges(final Function, Edge> edg */ public static StarGraph readStarGraphVertex(final Map vertexData) throws IOException { final StarGraph starGraph = StarGraph.open(); - starGraph.addVertex(T.id, vertexData.get(GraphSONTokens.ID), T.label, vertexData.get(GraphSONTokens.LABEL)); + final Object labelData = vertexData.get(GraphSONTokens.LABEL); + final List labels = labelData instanceof List ? (List) labelData : Collections.singletonList((String) labelData); + starGraph.addVertex(T.id, vertexData.get(GraphSONTokens.ID), T.label, labels.get(0)); + if (labels.size() > 1) { + starGraph.getStarVertex().setLabels(new LinkedHashSet<>(labels)); + } if (vertexData.containsKey(GraphSONTokens.PROPERTIES)) { final Map>> properties = (Map>>) vertexData.get(GraphSONTokens.PROPERTIES); for (Map.Entry>> property : properties.entrySet()) { diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/util/star/StarGraphGraphSONSerializerV4.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/util/star/StarGraphGraphSONSerializerV4.java index db47b04ef79..59d0982ae42 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/util/star/StarGraphGraphSONSerializerV4.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/util/star/StarGraphGraphSONSerializerV4.java @@ -71,7 +71,7 @@ private void ser(final DirectionalStarGraph directionalStarGraph, final JsonGene final StarGraph starGraph = directionalStarGraph.getStarGraphToSerialize(); GraphSONUtil.writeStartObject(starGraph, jsonGenerator, typeSerializer); GraphSONUtil.writeWithType(GraphSONTokens.ID, starGraph.starVertex.id, jsonGenerator, serializerProvider, typeSerializer); - jsonGenerator.writeStringField(GraphSONTokens.LABEL, starGraph.starVertex.label); + writeLabels(jsonGenerator, starGraph.starVertex.labels()); if (directionalStarGraph.getDirection() != null) writeEdges(directionalStarGraph, jsonGenerator, serializerProvider, typeSerializer, Direction.IN); if (directionalStarGraph.getDirection() != null) writeEdges(directionalStarGraph, jsonGenerator, serializerProvider, typeSerializer, Direction.OUT); if (starGraph.starVertex.vertexProperties != null && !starGraph.starVertex.vertexProperties.isEmpty()) { @@ -162,4 +162,16 @@ private static List sort(final List listToSort, final Comparator compa return listToSort; } + /** + * Helper method for writing a vertex's labels as an array. Used for multi-label vertex support. + */ + private static void writeLabels(final JsonGenerator jsonGenerator, final Set labels) throws IOException { + jsonGenerator.writeFieldName(GraphSONTokens.LABEL); + jsonGenerator.writeStartArray(); + for (final String label : labels) { + jsonGenerator.writeString(label); + } + jsonGenerator.writeEndArray(); + } + } diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/util/star/StarGraphSerializer.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/util/star/StarGraphSerializer.java index 8248164a0c2..6d8164e608f 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/util/star/StarGraphSerializer.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/util/star/StarGraphSerializer.java @@ -18,7 +18,9 @@ */ package org.apache.tinkerpop.gremlin.structure.util.star; +import java.util.ArrayList; import java.util.HashMap; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; @@ -49,6 +51,13 @@ public class StarGraphSerializer implements SerializerShim { private final static byte VERSION_1 = Byte.MIN_VALUE; + /** + * Introduced to support multi-label vertices: the vertex label is written as a list of labels rather than a + * single string. Readers dispatch on this byte, so {@link #VERSION_1} data (single label per vertex) written + * by prior versions of this serializer remains readable. + */ + private final static byte VERSION_2 = Byte.MIN_VALUE + 1; + public StarGraphSerializer(final Direction edgeDirectionToSerialize, final GraphFilter graphFilter) { this.edgeDirectionToSerialize = edgeDirectionToSerialize; this.graphFilter = graphFilter; @@ -56,11 +65,11 @@ public StarGraphSerializer(final Direction edgeDirectionToSerialize, final Graph @Override public void write(final KryoShim kryo, final O output, final StarGraph starGraph) { - output.writeByte(VERSION_1); + output.writeByte(VERSION_2); kryo.writeObjectOrNull(output, starGraph.edgeProperties, HashMap.class); kryo.writeObjectOrNull(output, starGraph.metaProperties, HashMap.class); kryo.writeClassAndObject(output, starGraph.starVertex.id); - kryo.writeObject(output, starGraph.starVertex.label); + kryo.writeObject(output, new ArrayList<>(starGraph.starVertex.labels())); writeEdges(kryo, output, starGraph, Direction.IN); writeEdges(kryo, output, starGraph, Direction.OUT); kryo.writeObject(output, null != starGraph.starVertex.vertexProperties); @@ -83,10 +92,19 @@ public void write(final KryoShim kryo, final O outp @Override public StarGraph read(final KryoShim kryo, final I input, final Class clazz) { final StarGraph starGraph = StarGraph.open(); - input.readByte(); // version field ignored for now - for future use with backward compatibility + final byte version = input.readByte(); starGraph.edgeProperties = kryo.readObjectOrNull(input, HashMap.class); starGraph.metaProperties = kryo.readObjectOrNull(input, HashMap.class); - starGraph.addVertex(T.id, kryo.readClassAndObject(input), T.label, kryo.readObject(input, String.class)); + final Object vertexId = kryo.readClassAndObject(input); + if (version == VERSION_2) { + final List labels = (List) kryo.readObject(input, ArrayList.class); + starGraph.addVertex(T.id, vertexId, T.label, labels.get(0)); + if (labels.size() > 1) { + starGraph.getStarVertex().setLabels(new LinkedHashSet<>(labels)); + } + } else { + starGraph.addVertex(T.id, vertexId, T.label, kryo.readObject(input, String.class)); + } readEdges(kryo, input, starGraph, Direction.IN); readEdges(kryo, input, starGraph, Direction.OUT); if (kryo.readObject(input, Boolean.class)) { diff --git a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/structure/util/star/StarGraphGraphSONV4MultiLabelTest.java b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/structure/util/star/StarGraphGraphSONV4MultiLabelTest.java new file mode 100644 index 00000000000..cca3e0c7767 --- /dev/null +++ b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/structure/util/star/StarGraphGraphSONV4MultiLabelTest.java @@ -0,0 +1,86 @@ +/* + * 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.structure.util.star; + +import org.apache.tinkerpop.gremlin.structure.Direction; +import org.apache.tinkerpop.gremlin.structure.T; +import org.apache.tinkerpop.gremlin.structure.Vertex; +import org.apache.tinkerpop.gremlin.structure.io.graphson.GraphSONMapper; +import org.apache.tinkerpop.gremlin.structure.io.graphson.GraphSONReader; +import org.apache.tinkerpop.gremlin.structure.io.graphson.GraphSONVersion; +import org.apache.tinkerpop.gremlin.structure.io.graphson.GraphSONWriter; +import org.apache.tinkerpop.gremlin.structure.io.graphson.TypeInfo; +import org.apache.tinkerpop.gremlin.structure.util.Attachable; +import org.junit.Test; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.util.Arrays; +import java.util.LinkedHashSet; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.is; + +/** + * Confirms that a {@link StarGraph}-wrapped multi-label {@link Vertex} round-trips through GraphSON V4 without + * losing any of its labels, and that single-label vertices continue to round-trip through V1/V2/V3 as before. + */ +public class StarGraphGraphSONV4MultiLabelTest { + + @Test + public void shouldRoundTripMultiLabelVertexThroughGraphSONV4() throws Exception { + final StarGraph starGraph = StarGraph.open(); + final Vertex original = starGraph.addVertex(T.label, "animal"); + ((StarGraph.StarVertex) original).setLabels(new LinkedHashSet<>(Arrays.asList("animal", "bird", "aquatic", "endangered"))); + + final GraphSONWriter writer = GraphSONWriter.build(). + mapper(GraphSONMapper.build().version(GraphSONVersion.V4_0).typeInfo(TypeInfo.PARTIAL_TYPES).create()). + create(); + final ByteArrayOutputStream baos = new ByteArrayOutputStream(); + writer.writeVertex(baos, original, Direction.BOTH); + + final GraphSONReader reader = GraphSONReader.build(). + mapper(GraphSONMapper.build().version(GraphSONVersion.V4_0).typeInfo(TypeInfo.PARTIAL_TYPES).create()). + create(); + final Vertex roundTripped = reader.readVertex(new ByteArrayInputStream(baos.toByteArray()), Attachable::get); + + assertThat(roundTripped.labels(), is(new LinkedHashSet<>(Arrays.asList("animal", "bird", "aquatic", "endangered")))); + } + + @Test + public void shouldRoundTripSingleLabelVertexThroughGraphSONV3() throws Exception { + final StarGraph starGraph = StarGraph.open(); + final Vertex original = starGraph.addVertex(T.label, "person"); + + final GraphSONWriter writer = GraphSONWriter.build(). + mapper(GraphSONMapper.build().version(GraphSONVersion.V3_0).typeInfo(TypeInfo.PARTIAL_TYPES).create()). + create(); + final ByteArrayOutputStream baos = new ByteArrayOutputStream(); + writer.writeVertex(baos, original, Direction.BOTH); + + final GraphSONReader reader = GraphSONReader.build(). + mapper(GraphSONMapper.build().version(GraphSONVersion.V3_0).typeInfo(TypeInfo.PARTIAL_TYPES).create()). + create(); + final Vertex roundTripped = reader.readVertex(new ByteArrayInputStream(baos.toByteArray()), Attachable::get); + + assertThat(roundTripped.label(), is("person")); + assertThat(roundTripped.labels(), contains("person")); + } +} diff --git a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/structure/util/star/StarGraphSerializerMultiLabelTest.java b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/structure/util/star/StarGraphSerializerMultiLabelTest.java new file mode 100644 index 00000000000..3d9c07ea033 --- /dev/null +++ b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/structure/util/star/StarGraphSerializerMultiLabelTest.java @@ -0,0 +1,116 @@ +/* + * 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.structure.util.star; + +import org.apache.tinkerpop.gremlin.process.computer.GraphFilter; +import org.apache.tinkerpop.gremlin.structure.Direction; +import org.apache.tinkerpop.gremlin.structure.T; +import org.apache.tinkerpop.gremlin.structure.Vertex; +import org.apache.tinkerpop.gremlin.structure.io.gryo.GryoReader; +import org.apache.tinkerpop.gremlin.structure.io.gryo.GryoWriter; +import org.apache.tinkerpop.gremlin.structure.io.gryo.kryoshim.shaded.ShadedSerializerAdapter; +import org.apache.tinkerpop.gremlin.structure.util.Attachable; +import org.apache.tinkerpop.shaded.kryo.Kryo; +import org.apache.tinkerpop.shaded.kryo.io.Input; +import org.apache.tinkerpop.shaded.kryo.io.Output; +import org.junit.Test; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.util.Arrays; +import java.util.HashMap; +import java.util.LinkedHashSet; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.is; + +/** + * Confirms that a multi-label {@link org.apache.tinkerpop.gremlin.structure.Vertex} round-trips through + * {@link StarGraphSerializer}'s Kryo format without losing any of its labels, and that the {@code VERSION_1} + * (single label per vertex) byte format written by prior versions of this serializer remains readable. + */ +public class StarGraphSerializerMultiLabelTest { + + /** + * Replicates the pre-multi-label {@code StarGraphSerializer.write()} logic exactly (a single string label, + * under the {@code VERSION_1} byte) so that this test can confirm the current reader still accepts data + * written in that legacy shape, without depending on the current (fixed) writer to produce it. + */ + private static final byte LEGACY_VERSION_1 = Byte.MIN_VALUE; + + @Test + public void shouldRoundTripMultiLabelVertex() throws Exception { + final StarGraph starGraph = StarGraph.open(); + final Vertex original = starGraph.addVertex(T.label, "animal"); + ((StarGraph.StarVertex) original).setLabels(new LinkedHashSet<>(Arrays.asList("animal", "bird", "aquatic", "endangered"))); + + final GryoWriter writer = GryoWriter.build().create(); + final ByteArrayOutputStream baos = new ByteArrayOutputStream(); + writer.writeVertex(baos, original, Direction.BOTH); + + final GryoReader reader = GryoReader.build().create(); + final Vertex roundTripped = reader.readVertex(new ByteArrayInputStream(baos.toByteArray()), Attachable::get); + + assertThat(roundTripped.labels(), is(new LinkedHashSet<>(Arrays.asList("animal", "bird", "aquatic", "endangered")))); + } + + @Test + public void shouldRoundTripSingleLabelVertex() throws Exception { + final StarGraph starGraph = StarGraph.open(); + final Vertex original = starGraph.addVertex(T.label, "person"); + + final GryoWriter writer = GryoWriter.build().create(); + final ByteArrayOutputStream baos = new ByteArrayOutputStream(); + writer.writeVertex(baos, original, Direction.BOTH); + + final GryoReader reader = GryoReader.build().create(); + final Vertex roundTripped = reader.readVertex(new ByteArrayInputStream(baos.toByteArray()), Attachable::get); + + assertThat(roundTripped.label(), is("person")); + assertThat(roundTripped.labels(), contains("person")); + } + + @Test + public void shouldReadLegacyVersion1SingleLabelFormat() throws Exception { + final Kryo kryo = new Kryo(); + kryo.setReferences(false); + kryo.register(StarGraph.class, new ShadedSerializerAdapter<>(new StarGraphSerializer(Direction.BOTH, new GraphFilter()))); + + final ByteArrayOutputStream baos = new ByteArrayOutputStream(); + final Output output = new Output(baos); + // hand-write the exact legacy (pre-multi-label) byte layout: version byte, null edge/meta property maps, + // a class-and-object vertex id, then a single string label - mirroring the old write() implementation. + output.writeByte(LEGACY_VERSION_1); + kryo.writeObjectOrNull(output, null, HashMap.class); // edgeProperties + kryo.writeObjectOrNull(output, null, HashMap.class); // metaProperties + kryo.writeClassAndObject(output, 100); // vertex id + kryo.writeObject(output, "person"); // single string label, as the old format wrote it + kryo.writeObject(output, false); // no in-edges + kryo.writeObject(output, false); // no out-edges + kryo.writeObject(output, false); // no vertex properties + output.flush(); + + final Input input = new Input(baos.toByteArray()); + final StarGraph starGraph = kryo.readObject(input, StarGraph.class); + + assertThat(starGraph.getStarVertex().label(), is("person")); + assertThat(starGraph.getStarVertex().labels(), contains("person")); + } +} diff --git a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/DeepEqualityExtensions.cs b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/DeepEqualityExtensions.cs index af7114c7564..93a7e6570ef 100644 --- a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/DeepEqualityExtensions.cs +++ b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/DeepEqualityExtensions.cs @@ -76,6 +76,13 @@ private static bool DeepEqual(this IEnumerable first, IEnumerable second) { return second is Dictionary dict2 && dict1.DeepEqual(dict2); } + if (first is ISet set1) + { + // Sets are unordered by definition, so comparing them with a SequenceEqual below is not reliable - + // different insertion histories/capacities can yield different enumeration orders for otherwise + // identical sets (e.g. a multi-label vertex's set of labels). + return second is ISet set2 && set1.SetEquals(set2); + } var objectEnum1 = first.ToObjectEnumerable(); var objectEnum2 = second.ToObjectEnumerable(); diff --git a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs index 25950147782..a83fd6500ad 100644 --- a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs +++ b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs @@ -1210,15 +1210,15 @@ private static IDictionary, ITraversal>> {(g,p) =>g.With("singlelabel").V().ElementMap("name", "age", null)}}, {"g_withXmultilabelX_VXmarkoX_elementMap", new List, ITraversal>> {(g,p) =>g.With("multilabel").V().Has("name", "marko").ElementMap()}}, {"g_V_elementMap_single_label_default", new List, ITraversal>> {(g,p) =>g.AddV((string) "person").AddLabel("employee").Property("name", "marko"), (g,p) =>g.V().ElementMap()}}, - {"g_withXmultilabelX_V_elementMap_multilabel", new List, ITraversal>> {(g,p) =>g.AddV((string) "person").AddLabel("employee").Property("name", "marko"), (g,p) =>g.With("multilabel").V().ElementMap()}}, + {"g_withXmultilabelX_V_elementMap_multilabel", new List, ITraversal>> {(g,p) =>g.With("multilabel").V().Has("name", "tux").ElementMap("name", "species")}}, {"g_V_elementMap_multi_label_default", new List, ITraversal>> {(g,p) =>g.AddV((string) "person").AddLabel("employee").Property("name", "marko"), (g,p) =>g.V().ElementMap()}}, - {"g_withXsinglelabelX_V_elementMap_multi_label_default", new List, ITraversal>> {(g,p) =>g.AddV((string) "person").AddLabel("employee").Property("name", "marko"), (g,p) =>g.With("singlelabel").V().ElementMap()}}, + {"g_withXsinglelabelX_V_elementMap_multi_label_default", new List, ITraversal>> {(g,p) =>g.With("singlelabel").V().Has("name", "lagoon").ElementMap("name", "biome")}}, {"g_V_elementMap_single_label_vertex_multi_label_default", new List, ITraversal>> {(g,p) =>g.AddV((string) "person").Property("name", "marko"), (g,p) =>g.V().ElementMap()}}, {"g_withXsinglelabelX_V_elementMap_zero_label_vertex", new List, ITraversal>> {(g,p) =>g.AddV().Property("name", "nobody"), (g,p) =>g.With("singlelabel").V().ElementMap()}}, {"g_withXmultilabelX_V_elementMap_zero_label_vertex", new List, ITraversal>> {(g,p) =>g.AddV().Property("name", "nobody"), (g,p) =>g.With("multilabel").V().ElementMap()}}, {"g_V_elementMap_zero_label_vertex_multi_label_default", new List, ITraversal>> {(g,p) =>g.AddV().Property("name", "nobody"), (g,p) =>g.V().ElementMap()}}, {"g_V_elementMap_zero_label_vertex_single_label_default", new List, ITraversal>> {(g,p) =>g.AddV().Property("name", "nobody"), (g,p) =>g.V().ElementMap()}}, - {"g_withXmultilabelX_E_elementMap", new List, ITraversal>> {(g,p) =>g.AddV((string) "person").Property("name", "marko").As("a").AddV((string) "person").Property("name", "josh").As("b").AddE((string) "knows").From("a").To("b").Property("weight", 0.5), (g,p) =>g.With("multilabel").E().ElementMap()}}, + {"g_withXmultilabelX_E_elementMap", new List, ITraversal>> {(g,p) =>g.With("multilabel").E().Has("since", 2018).HasLabel("livesIn").ElementMap()}}, {"g_E_elementMap_multi_label_default", new List, ITraversal>> {(g,p) =>g.AddV((string) "person").Property("name", "marko").As("a").AddV((string) "person").Property("name", "josh").As("b").AddE((string) "knows").From("a").To("b").Property("weight", 0.5), (g,p) =>g.E().ElementMap()}}, {"g_E_elementMap_single_label_default", new List, ITraversal>> {(g,p) =>g.AddV((string) "person").Property("name", "marko").As("a").AddV((string) "person").Property("name", "josh").As("b").AddE((string) "knows").From("a").To("b").Property("weight", 0.5), (g,p) =>g.E().ElementMap()}}, {"g_withXsinglelabelX_E_elementMap", new List, ITraversal>> {(g,p) =>g.AddV((string) "person").Property("name", "marko").As("a").AddV((string) "person").Property("name", "josh").As("b").AddE((string) "knows").From("a").To("b").Property("weight", 0.5), (g,p) =>g.With("singlelabel").E().ElementMap()}}, @@ -1273,11 +1273,11 @@ private static IDictionary, ITraversal>> {(g,p) =>g.AddV((string) "person").Property("name", " marko ").Property("age", 29).As("marko").AddV((string) "person").Property("name", " vadas ").Property("age", 27).As("vadas").AddV((string) "software").Property("name", " lop").Property("lang", "java").As("lop").AddV((string) "person").Property("name", "josh ").Property("age", 32).As("josh").AddV((string) "software").Property("name", " ripple ").Property("lang", "java").As("ripple").AddV((string) "person").Property("name", "peter").Property("age", 35).As("peter").AddE((string) "knows").From("marko").To("vadas").Property("weight", 0.5d).AddE((string) "knows").From("marko").To("josh").Property("weight", 1.0d).AddE((string) "created").From("marko").To("lop").Property("weight", 0.4d).AddE((string) "created").From("josh").To("ripple").Property("weight", 1.0d).AddE((string) "created").From("josh").To("lop").Property("weight", 0.4d).AddE((string) "created").From("peter").To("lop").Property("weight", 0.2d), (g,p) =>g.V().Values("name").LTrim()}}, {"g_V_valuesXnameX_order_fold_lTrimXlocalX", new List, ITraversal>> {(g,p) =>g.AddV((string) "person").Property("name", " marko ").Property("age", 29).As("marko").AddV((string) "person").Property("name", " vadas ").Property("age", 27).As("vadas").AddV((string) "software").Property("name", " lop").Property("lang", "java").As("lop").AddV((string) "person").Property("name", "josh ").Property("age", 32).As("josh").AddV((string) "software").Property("name", " ripple ").Property("lang", "java").As("ripple").AddV((string) "person").Property("name", "peter").Property("age", 35).As("peter").AddE((string) "knows").From("marko").To("vadas").Property("weight", 0.5d).AddE((string) "knows").From("marko").To("josh").Property("weight", 1.0d).AddE((string) "created").From("marko").To("lop").Property("weight", 0.4d).AddE((string) "created").From("josh").To("ripple").Property("weight", 1.0d).AddE((string) "created").From("josh").To("lop").Property("weight", 0.4d).AddE((string) "created").From("peter").To("lop").Property("weight", 0.2d), (g,p) =>g.V().Values("name").Order().Fold().LTrim(Scope.Local)}}, {"g_V_label_single_label_graph", new List, ITraversal>> {(g,p) =>g.V().HasLabel("person").Label()}}, - {"g_V_label_deprecated_multilabel", new List, ITraversal>> {(g,p) =>g.AddV((string) "a", (string) "b").Property("name", "test"), (g,p) =>g.V().Label().Count()}}, - {"g_V_label_deprecated_multilabel_value_is_one_of_labels", new List, ITraversal>> {(g,p) =>g.AddV((string) "a", (string) "b").Property("name", "test"), (g,p) =>g.V().Filter(__.Label().Is(P.Within("a", "b")))}}, + {"g_V_label_deprecated_multilabel", new List, ITraversal>> {(g,p) =>g.V().Has("name", "tux").Label().Count()}}, + {"g_V_label_deprecated_multilabel_value_is_one_of_labels", new List, ITraversal>> {(g,p) =>g.V().Has("name", "tux").Filter(__.Label().Is(P.Within("animal", "bird", "aquatic", "endangered")))}}, {"g_E_label_deprecated_multilabel_graph", new List, ITraversal>> {(g,p) =>g.AddV((string) "person").As("a").AddV((string) "person").As("b").AddE((string) "knows").From("a").To("b"), (g,p) =>g.E().Label()}}, {"g_V_hasLabelXpersonX_labels", new List, ITraversal>> {(g,p) =>g.V().HasLabel("person").Labels()}}, - {"g_V_labels_multilabel", new List, ITraversal>> {(g,p) =>g.AddV((string) "a", (string) "b"), (g,p) =>g.V().Labels()}}, + {"g_V_labels_multilabel", new List, ITraversal>> {(g,p) =>g.V().Has("name", "tux").Labels()}}, {"g_addVXa_bX_labels_count", new List, ITraversal>> {(g,p) =>g.AddV((string) "a", (string) "b"), (g,p) =>g.V().Labels().Count()}}, {"g_addV_labels", new List, ITraversal>> {(g,p) =>g.AddV(), (g,p) =>g.V().Labels()}}, {"g_E_labels", new List, ITraversal>> {(g,p) =>g.E().HasLabel("knows").Labels()}}, @@ -1868,9 +1868,9 @@ private static IDictionary, ITraversal>> {(g,p) =>g.V().ValueMap("name", "age").By(__.Is("x")).By(__.Unfold())}}, {"g_withXmultilabelX_VXmarkoX_valueMap_withXtokensX", new List, ITraversal>> {(g,p) =>g.With("multilabel").V().Has("name", "marko").ValueMap().With(WithOptions.Tokens)}}, {"g_V_valueMap_withXtokensX_single_label_default", new List, ITraversal>> {(g,p) =>g.AddV((string) "person").AddLabel("employee").Property("name", "marko"), (g,p) =>g.V().ValueMap().With(WithOptions.Tokens)}}, - {"g_withXmultilabelX_V_valueMap_withXtokensX_multilabel", new List, ITraversal>> {(g,p) =>g.AddV((string) "person").AddLabel("employee").Property("name", "marko"), (g,p) =>g.With("multilabel").V().ValueMap().With(WithOptions.Tokens)}}, + {"g_withXmultilabelX_V_valueMap_withXtokensX_multilabel", new List, ITraversal>> {(g,p) =>g.With("multilabel").V().Has("name", "tux").ValueMap("name", "species").With(WithOptions.Tokens)}}, {"g_V_valueMap_withXtokensX_multi_label_default", new List, ITraversal>> {(g,p) =>g.AddV((string) "person").AddLabel("employee").Property("name", "marko"), (g,p) =>g.V().ValueMap().With(WithOptions.Tokens)}}, - {"g_withXsinglelabelX_V_valueMap_withXtokensX_multi_label_default", new List, ITraversal>> {(g,p) =>g.AddV((string) "person").AddLabel("employee").Property("name", "marko"), (g,p) =>g.With("singlelabel").V().ValueMap().With(WithOptions.Tokens)}}, + {"g_withXsinglelabelX_V_valueMap_withXtokensX_multi_label_default", new List, ITraversal>> {(g,p) =>g.With("singlelabel").V().Has("name", "lagoon").ValueMap("name", "biome").With(WithOptions.Tokens)}}, {"g_withXsinglelabelX_V_valueMapXtrueX_zero_label_vertex", new List, ITraversal>> {(g,p) =>g.AddV().Property("name", "nobody"), (g,p) =>g.With("singlelabel").V().ValueMap(true)}}, {"g_withXmultilabelX_V_valueMapXtrueX_zero_label_vertex", new List, ITraversal>> {(g,p) =>g.AddV().Property("name", "nobody"), (g,p) =>g.With("multilabel").V().ValueMap(true)}}, {"g_V_valueMapXtrueX_zero_label_vertex_multi_label_default", new List, ITraversal>> {(g,p) =>g.AddV().Property("name", "nobody"), (g,p) =>g.V().ValueMap(true)}}, diff --git a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/ScenarioData.cs b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/ScenarioData.cs index b337594415b..5cf427fe927 100644 --- a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/ScenarioData.cs +++ b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/ScenarioData.cs @@ -39,7 +39,7 @@ namespace Gremlin.Net.IntegrationTest.Gherkin { internal class ScenarioData : IDisposable { - private static readonly string[] GraphNames = {"modern", "classic", "crew", "grateful", "sink"}; + private static readonly string[] GraphNames = {"modern", "classic", "crew", "grateful", "sink", "zoo"}; private static readonly IDictionary EmptyVertices = new ReadOnlyDictionary(new Dictionary()); diff --git a/gremlin-go/driver/cucumber/cucumberWorld.go b/gremlin-go/driver/cucumber/cucumberWorld.go index 9e9b8e0b793..d26e4d9c19f 100644 --- a/gremlin-go/driver/cucumber/cucumberWorld.go +++ b/gremlin-go/driver/cucumber/cucumberWorld.go @@ -87,7 +87,7 @@ func NewCucumberWorld() *CucumberWorld { } } -var graphNames = []string{"modern", "classic", "crew", "grateful", "sink", "empty", "multilabel"} +var graphNames = []string{"modern", "classic", "crew", "grateful", "sink", "zoo", "empty", "multilabel"} func (t *CucumberWorld) getDataGraphFromMap(name string) *DataGraph { if val, ok := t.graphDataMap[name]; ok { diff --git a/gremlin-js/gremlin-javascript/test/cucumber/gremlin.js b/gremlin-js/gremlin-javascript/test/cucumber/gremlin.js index 2b3d499b9c9..3c7206f1974 100644 --- a/gremlin-js/gremlin-javascript/test/cucumber/gremlin.js +++ b/gremlin-js/gremlin-javascript/test/cucumber/gremlin.js @@ -1211,15 +1211,15 @@ const gremlins = { g_V_elementMapXname_age_nullX: [function({g}) { return g.with_("singlelabel").V().elementMap("name", "age", null) }], g_withXmultilabelX_VXmarkoX_elementMap: [function({g}) { return g.with_("multilabel").V().has("name", "marko").elementMap() }], g_V_elementMap_single_label_default: [function({g}) { return g.addV("person").addLabel("employee").property("name", "marko") }, function({g}) { return g.V().elementMap() }], - g_withXmultilabelX_V_elementMap_multilabel: [function({g}) { return g.addV("person").addLabel("employee").property("name", "marko") }, function({g}) { return g.with_("multilabel").V().elementMap() }], + g_withXmultilabelX_V_elementMap_multilabel: [function({g}) { return g.with_("multilabel").V().has("name", "tux").elementMap("name", "species") }], g_V_elementMap_multi_label_default: [function({g}) { return g.addV("person").addLabel("employee").property("name", "marko") }, function({g}) { return g.V().elementMap() }], - g_withXsinglelabelX_V_elementMap_multi_label_default: [function({g}) { return g.addV("person").addLabel("employee").property("name", "marko") }, function({g}) { return g.with_("singlelabel").V().elementMap() }], + g_withXsinglelabelX_V_elementMap_multi_label_default: [function({g}) { return g.with_("singlelabel").V().has("name", "lagoon").elementMap("name", "biome") }], g_V_elementMap_single_label_vertex_multi_label_default: [function({g}) { return g.addV("person").property("name", "marko") }, function({g}) { return g.V().elementMap() }], g_withXsinglelabelX_V_elementMap_zero_label_vertex: [function({g}) { return g.addV().property("name", "nobody") }, function({g}) { return g.with_("singlelabel").V().elementMap() }], g_withXmultilabelX_V_elementMap_zero_label_vertex: [function({g}) { return g.addV().property("name", "nobody") }, function({g}) { return g.with_("multilabel").V().elementMap() }], g_V_elementMap_zero_label_vertex_multi_label_default: [function({g}) { return g.addV().property("name", "nobody") }, function({g}) { return g.V().elementMap() }], g_V_elementMap_zero_label_vertex_single_label_default: [function({g}) { return g.addV().property("name", "nobody") }, function({g}) { return g.V().elementMap() }], - g_withXmultilabelX_E_elementMap: [function({g}) { return g.addV("person").property("name", "marko").as("a").addV("person").property("name", "josh").as("b").addE("knows").from_("a").to("b").property("weight", 0.5) }, function({g}) { return g.with_("multilabel").E().elementMap() }], + g_withXmultilabelX_E_elementMap: [function({g}) { return g.with_("multilabel").E().has("since", 2018).hasLabel("livesIn").elementMap() }], g_E_elementMap_multi_label_default: [function({g}) { return g.addV("person").property("name", "marko").as("a").addV("person").property("name", "josh").as("b").addE("knows").from_("a").to("b").property("weight", 0.5) }, function({g}) { return g.E().elementMap() }], g_E_elementMap_single_label_default: [function({g}) { return g.addV("person").property("name", "marko").as("a").addV("person").property("name", "josh").as("b").addE("knows").from_("a").to("b").property("weight", 0.5) }, function({g}) { return g.E().elementMap() }], g_withXsinglelabelX_E_elementMap: [function({g}) { return g.addV("person").property("name", "marko").as("a").addV("person").property("name", "josh").as("b").addE("knows").from_("a").to("b").property("weight", 0.5) }, function({g}) { return g.with_("singlelabel").E().elementMap() }], @@ -1274,11 +1274,11 @@ const gremlins = { g_V_valuesXnameX_lTrim: [function({g}) { return g.addV("person").property("name", " marko ").property("age", 29).as("marko").addV("person").property("name", " vadas ").property("age", 27).as("vadas").addV("software").property("name", " lop").property("lang", "java").as("lop").addV("person").property("name", "josh ").property("age", 32).as("josh").addV("software").property("name", " ripple ").property("lang", "java").as("ripple").addV("person").property("name", "peter").property("age", 35).as("peter").addE("knows").from_("marko").to("vadas").property("weight", 0.5).addE("knows").from_("marko").to("josh").property("weight", 1.0).addE("created").from_("marko").to("lop").property("weight", 0.4).addE("created").from_("josh").to("ripple").property("weight", 1.0).addE("created").from_("josh").to("lop").property("weight", 0.4).addE("created").from_("peter").to("lop").property("weight", 0.2) }, function({g}) { return g.V().values("name").lTrim() }], g_V_valuesXnameX_order_fold_lTrimXlocalX: [function({g}) { return g.addV("person").property("name", " marko ").property("age", 29).as("marko").addV("person").property("name", " vadas ").property("age", 27).as("vadas").addV("software").property("name", " lop").property("lang", "java").as("lop").addV("person").property("name", "josh ").property("age", 32).as("josh").addV("software").property("name", " ripple ").property("lang", "java").as("ripple").addV("person").property("name", "peter").property("age", 35).as("peter").addE("knows").from_("marko").to("vadas").property("weight", 0.5).addE("knows").from_("marko").to("josh").property("weight", 1.0).addE("created").from_("marko").to("lop").property("weight", 0.4).addE("created").from_("josh").to("ripple").property("weight", 1.0).addE("created").from_("josh").to("lop").property("weight", 0.4).addE("created").from_("peter").to("lop").property("weight", 0.2) }, function({g}) { return g.V().values("name").order().fold().lTrim(Scope.local) }], g_V_label_single_label_graph: [function({g}) { return g.V().hasLabel("person").label() }], - g_V_label_deprecated_multilabel: [function({g}) { return g.addV("a", "b").property("name", "test") }, function({g}) { return g.V().label().count() }], - g_V_label_deprecated_multilabel_value_is_one_of_labels: [function({g}) { return g.addV("a", "b").property("name", "test") }, function({g}) { return g.V().filter(__.label().is(P.within("a", "b"))) }], + g_V_label_deprecated_multilabel: [function({g}) { return g.V().has("name", "tux").label().count() }], + g_V_label_deprecated_multilabel_value_is_one_of_labels: [function({g}) { return g.V().has("name", "tux").filter(__.label().is(P.within("animal", "bird", "aquatic", "endangered"))) }], g_E_label_deprecated_multilabel_graph: [function({g}) { return g.addV("person").as("a").addV("person").as("b").addE("knows").from_("a").to("b") }, function({g}) { return g.E().label() }], g_V_hasLabelXpersonX_labels: [function({g}) { return g.V().hasLabel("person").labels() }], - g_V_labels_multilabel: [function({g}) { return g.addV("a", "b") }, function({g}) { return g.V().labels() }], + g_V_labels_multilabel: [function({g}) { return g.V().has("name", "tux").labels() }], g_addVXa_bX_labels_count: [function({g}) { return g.addV("a", "b") }, function({g}) { return g.V().labels().count() }], g_addV_labels: [function({g}) { return g.addV() }, function({g}) { return g.V().labels() }], g_E_labels: [function({g}) { return g.E().hasLabel("knows").labels() }], @@ -1869,9 +1869,9 @@ const gremlins = { g_V_valueMapXname_ageX_byXisXxXXbyXunfoldX: [function({g}) { return g.V().valueMap("name", "age").by(__.is("x")).by(__.unfold()) }], g_withXmultilabelX_VXmarkoX_valueMap_withXtokensX: [function({g}) { return g.with_("multilabel").V().has("name", "marko").valueMap().with_(WithOptions.tokens) }], g_V_valueMap_withXtokensX_single_label_default: [function({g}) { return g.addV("person").addLabel("employee").property("name", "marko") }, function({g}) { return g.V().valueMap().with_(WithOptions.tokens) }], - g_withXmultilabelX_V_valueMap_withXtokensX_multilabel: [function({g}) { return g.addV("person").addLabel("employee").property("name", "marko") }, function({g}) { return g.with_("multilabel").V().valueMap().with_(WithOptions.tokens) }], + g_withXmultilabelX_V_valueMap_withXtokensX_multilabel: [function({g}) { return g.with_("multilabel").V().has("name", "tux").valueMap("name", "species").with_(WithOptions.tokens) }], g_V_valueMap_withXtokensX_multi_label_default: [function({g}) { return g.addV("person").addLabel("employee").property("name", "marko") }, function({g}) { return g.V().valueMap().with_(WithOptions.tokens) }], - g_withXsinglelabelX_V_valueMap_withXtokensX_multi_label_default: [function({g}) { return g.addV("person").addLabel("employee").property("name", "marko") }, function({g}) { return g.with_("singlelabel").V().valueMap().with_(WithOptions.tokens) }], + g_withXsinglelabelX_V_valueMap_withXtokensX_multi_label_default: [function({g}) { return g.with_("singlelabel").V().has("name", "lagoon").valueMap("name", "biome").with_(WithOptions.tokens) }], g_withXsinglelabelX_V_valueMapXtrueX_zero_label_vertex: [function({g}) { return g.addV().property("name", "nobody") }, function({g}) { return g.with_("singlelabel").V().valueMap(true) }], g_withXmultilabelX_V_valueMapXtrueX_zero_label_vertex: [function({g}) { return g.addV().property("name", "nobody") }, function({g}) { return g.with_("multilabel").V().valueMap(true) }], g_V_valueMapXtrueX_zero_label_vertex_multi_label_default: [function({g}) { return g.addV().property("name", "nobody") }, function({g}) { return g.V().valueMap(true) }], diff --git a/gremlin-js/gremlin-javascript/test/cucumber/world.js b/gremlin-js/gremlin-javascript/test/cucumber/world.js index 7a52ea5a341..dff02721d49 100644 --- a/gremlin-js/gremlin-javascript/test/cucumber/world.js +++ b/gremlin-js/gremlin-javascript/test/cucumber/world.js @@ -84,7 +84,7 @@ setWorldConstructor(TinkerPopWorld); BeforeAll(function () { // load all traversals - const promises = ['modern', 'classic', 'crew', 'grateful', 'sink', 'empty', 'multilabel'].map(graphName => { + const promises = ['modern', 'classic', 'crew', 'grateful', 'sink', 'zoo', 'empty', 'multilabel'].map(graphName => { let connection = null; if (graphName === 'empty') { connection = getConnection('ggraph'); diff --git a/gremlin-python/src/main/python/tests/feature/terrain.py b/gremlin-python/src/main/python/tests/feature/terrain.py index 55489c7807c..a5035879ea8 100644 --- a/gremlin-python/src/main/python/tests/feature/terrain.py +++ b/gremlin-python/src/main/python/tests/feature/terrain.py @@ -42,7 +42,7 @@ def prepare_static_traversal_source(features, marker): # as the various traversal sources for testing do not change their data, there is no need to re-create remotes # and client side lookup data over and over. it can be created once for all tests and be reused. cache = {} - for graph_name in (("modern", "gmodern"), ("classic", "gclassic"), ("crew", "gcrew"), ("grateful", "ggrateful"), ("sink", "gsink")): + for graph_name in (("modern", "gmodern"), ("classic", "gclassic"), ("crew", "gcrew"), ("grateful", "ggrateful"), ("sink", "gsink"), ("zoo", "gzoo")): cache[graph_name[0]] = {} remote = __create_remote(graph_name[1]) cache[graph_name[0]]["remote_conn"] = __create_remote(graph_name[1]) @@ -62,7 +62,7 @@ def prepare_static_traversal_source(features, marker): scenario.context.lookup_e = {} scenario.context.lookup_vp = {} - for graph_name in ("modern", "classic", "crew", "grateful", "sink"): + for graph_name in ("modern", "classic", "crew", "grateful", "sink", "zoo"): scenario.context.remote_conn[graph_name] = cache[graph_name]["remote_conn"] scenario.context.lookup_v[graph_name] = cache[graph_name]["lookup_v"] scenario.context.lookup_e[graph_name] = cache[graph_name]["lookup_e"] diff --git a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/util/TinkerFactoryDataLoader.java b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/util/TinkerFactoryDataLoader.java index a211d814263..3e01a887f4e 100644 --- a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/util/TinkerFactoryDataLoader.java +++ b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/util/TinkerFactoryDataLoader.java @@ -31,7 +31,7 @@ *

Configuration parameters (via {@code config} in YAML): *

    *
  • {@code graph} — name of the graph (as defined in {@code graphs:} config) to load data into
  • - *
  • {@code dataset} — which dataset to load: modern, classic, crew, grateful, sink, airroutes
  • + *
  • {@code dataset} — which dataset to load: modern, classic, crew, grateful, sink, airroutes, zoo
  • *
* *

Example YAML usage: @@ -88,6 +88,9 @@ public void onStartUp(final Context c) { case "airroutes": TinkerFactory.generateAirRoutes(tinkerGraph); break; + case "zoo": + TinkerFactory.generateTheZoo(tinkerGraph); + break; default: c.getLogger().warn("TinkerFactoryDataLoader unknown dataset [{}]", dataset); return; diff --git a/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/driver/remote/RemoteWorld.java b/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/driver/remote/RemoteWorld.java index 432a84ad4c6..5586b600eb8 100644 --- a/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/driver/remote/RemoteWorld.java +++ b/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/driver/remote/RemoteWorld.java @@ -92,6 +92,9 @@ public GraphTraversalSource getGraphTraversalSource(final LoadGraphWith.GraphDat case GRATEFUL: remoteTraversalSource = "ggrateful"; break; + case ZOO: + remoteTraversalSource = "gzoo"; + break; default: throw new UnsupportedOperationException("GraphData not supported: " + graphData.name()); } diff --git a/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/util/CheckedGraphManagerTest.java b/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/util/CheckedGraphManagerTest.java index 490ead1423f..f440a3bd5a1 100644 --- a/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/util/CheckedGraphManagerTest.java +++ b/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/util/CheckedGraphManagerTest.java @@ -66,7 +66,7 @@ public void singleGraphFails() { public void justAGraphFails() { settings.graphs.put("invalid", "conf/invalidPath"); final GraphManager manager = new CheckedGraphManager(settings); - assertThat(manager.getGraphNames(), hasSize(8)); + assertThat(manager.getGraphNames(), hasSize(9)); } /** diff --git a/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/util/DefaultGraphManagerTest.java b/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/util/DefaultGraphManagerTest.java index 87af675779f..b401b12e792 100644 --- a/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/util/DefaultGraphManagerTest.java +++ b/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/util/DefaultGraphManagerTest.java @@ -48,7 +48,7 @@ public void shouldReturnGraphs() { final Set graphNames = graphManager.getGraphNames(); assertNotNull(graphNames); - assertEquals(8, graphNames.size()); + assertEquals(9, graphNames.size()); assertThat(graphNames.contains("graph"), is(true)); assertThat(graphNames.contains("classic"), is(true)); @@ -56,6 +56,7 @@ public void shouldReturnGraphs() { assertThat(graphNames.contains("crew"), is(true)); assertThat(graphNames.contains("sink"), is(true)); assertThat(graphNames.contains("grateful"), is(true)); + assertThat(graphNames.contains("zoo"), is(true)); assertThat(graphNames.contains("tx"), is(true)); assertThat(graphManager.getGraph("graph"), instanceOf(TinkerGraph.class)); assertThat(graphManager.getGraph("tx"), instanceOf(TinkerTransactionGraph.class)); @@ -68,13 +69,14 @@ public void shouldGetAsBindings() { final Bindings bindings = graphManager.getAsBindings(); assertNotNull(bindings); - assertEquals(8, bindings.size()); + assertEquals(9, bindings.size()); assertThat(bindings.containsKey("graph"), is(true)); assertThat(bindings.containsKey("classic"), is(true)); assertThat(bindings.containsKey("modern"), is(true)); assertThat(bindings.containsKey("crew"), is(true)); assertThat(bindings.containsKey("sink"), is(true)); assertThat(bindings.containsKey("grateful"), is(true)); + assertThat(bindings.containsKey("zoo"), is(true)); assertThat(bindings.containsKey("tx"), is(true)); assertThat(bindings.get("graph"), instanceOf(TinkerGraph.class)); assertThat(bindings.get("tx"), instanceOf(TinkerTransactionGraph.class)); @@ -99,7 +101,7 @@ public void shouldGetDynamicallyAddedGraph() { final Set graphNames = graphManager.getGraphNames(); assertNotNull(graphNames); - assertEquals(9, graphNames.size()); + assertEquals(10, graphNames.size()); assertThat(graphNames.contains("newGraph"), is(true)); assertThat(graphNames.contains("graph"), is(true)); assertThat(graphNames.contains("classic"), is(true)); @@ -107,6 +109,7 @@ public void shouldGetDynamicallyAddedGraph() { assertThat(graphNames.contains("crew"), is(true)); assertThat(graphNames.contains("sink"), is(true)); assertThat(graphNames.contains("grateful"), is(true)); + assertThat(graphNames.contains("zoo"), is(true)); assertThat(graphNames.contains("tx"), is(true)); assertThat(graphManager.getGraph("newGraph"), instanceOf(TinkerGraph.class)); assertThat(graphManager.getGraph("tx"), instanceOf(TinkerTransactionGraph.class)); @@ -120,14 +123,14 @@ public void shouldNotGetRemovedGraph() throws Exception { graphManager.putGraph("newGraph", graph); final Set graphNames = graphManager.getGraphNames(); assertNotNull(graphNames); - assertEquals(9, graphNames.size()); + assertEquals(10, graphNames.size()); assertThat(graphNames.contains("newGraph"), is(true)); assertThat(graphManager.getGraph("newGraph"), instanceOf(TinkerGraph.class)); graphManager.removeGraph("newGraph"); final Set graphNames2 = graphManager.getGraphNames(); - assertEquals(8, graphNames2.size()); + assertEquals(9, graphNames2.size()); assertThat(graphNames2.contains("newGraph"), is(false)); } diff --git a/gremlin-server/src/test/resources/org/apache/tinkerpop/gremlin/server/gremlin-server-integration.yaml b/gremlin-server/src/test/resources/org/apache/tinkerpop/gremlin/server/gremlin-server-integration.yaml index 5e9a753eedf..6e21e6a0370 100644 --- a/gremlin-server/src/test/resources/org/apache/tinkerpop/gremlin/server/gremlin-server-integration.yaml +++ b/gremlin-server/src/test/resources/org/apache/tinkerpop/gremlin/server/gremlin-server-integration.yaml @@ -53,6 +53,9 @@ graphs: { sink: { configuration: conf/tinkergraph-empty.properties, traversalSources: [{name: gsink}]}, + zoo: { + configuration: conf/tinkergraph-multilabel.properties, + traversalSources: [{name: gzoo}]}, tx: { configuration: conf/tinkertransactiongraph-empty.properties, traversalSources: [{name: gtx}]}} @@ -62,6 +65,7 @@ lifecycleHooks: - { className: org.apache.tinkerpop.gremlin.server.util.TinkerFactoryDataLoader, config: {graph: crew, dataset: crew}} - { className: org.apache.tinkerpop.gremlin.server.util.TinkerFactoryDataLoader, config: {graph: grateful, dataset: grateful}} - { className: org.apache.tinkerpop.gremlin.server.util.TinkerFactoryDataLoader, config: {graph: sink, dataset: sink}} + - { className: org.apache.tinkerpop.gremlin.server.util.TinkerFactoryDataLoader, config: {graph: zoo, dataset: zoo}} scriptEngines: { gremlin-lang : {}, groovy-test: {}, diff --git a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/LoadGraphWith.java b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/LoadGraphWith.java index b11de7411bc..dc774165141 100644 --- a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/LoadGraphWith.java +++ b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/LoadGraphWith.java @@ -94,7 +94,18 @@ public enum GraphData { * Loads the air-routes graph, a real-world dataset of ~3,500 airports and ~50,000 routes suitable for * benchmarking and integration testing of traversals that benefit from larger, denser graphs. */ - AIR_ROUTES; + AIR_ROUTES, + + /** + * Loads "The Zoo" TinkerPop4 toy graph which showcases multi-label vertices and a variety of property + * types. Requires a graph configured with {@code LabelCardinality.ZERO_OR_MORE} (or {@code ONE_OR_MORE}). + *

+ * Note that this {@code GraphData} does not currently support {@link #location()} or + * {@link #featuresRequired()} since none of the serialization formats used to produce a data file for it + * (Gryo, GraphSON) correctly round-trip multi-label vertices yet. It is only usable via a + * {@link GraphProvider} that builds the graph directly, e.g. with {@code TinkerFactory.createTheZoo()}. + */ + ZOO; private static final List featuresRequiredByClassic = new ArrayList() {{ add(FeatureRequirement.Factory.create(FEATURE_STRING_VALUES, VertexPropertyFeatures.class)); diff --git a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/features/TestFiles.java b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/features/TestFiles.java index ddf67a5fce2..865c7a7e8d7 100644 --- a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/features/TestFiles.java +++ b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/features/TestFiles.java @@ -42,7 +42,8 @@ public class TestFiles { "grateful-dead-v3.kryo", "tinkerpop-classic-v3.kryo", "tinkerpop-crew-v3.kryo", - "tinkerpop-sink-v3.kryo"); + "tinkerpop-sink-v3.kryo", + "tinkerpop-zoo-v3.kryo"); for (final String fileName : kryoResources) { PATHS.put(fileName, Storage.toPath(TestHelper.generateTempFileFromResource(GryoResourceAccess.class, fileName, ""))); @@ -93,7 +94,13 @@ else if (graphData.equals(LoadGraphWith.GraphData.CREW)) return PATHS.get("tinkerpop-crew" + type); else if (graphData.equals(LoadGraphWith.GraphData.SINK)) return PATHS.get("tinkerpop-sink" + type); - else + else if (graphData.equals(LoadGraphWith.GraphData.ZOO)) { + // the zoo graph is multi-label; GraphSON V1/V2/V3 (like Gryo prior to multi-label support) can only + // carry a single label per vertex, so there is no "-v3.json" resource for it - only Gryo is supported. + if (useGraphSON) + throw new RuntimeException("The zoo graph is multi-label and cannot be read from GraphSON V1/V2/V3"); + return PATHS.get("tinkerpop-zoo" + type); + } else throw new RuntimeException("Could not load graph with " + graphData); } diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/language/translator/translations.json b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/language/translator/translations.json index c3083bc2a18..00cace7cf2f 100644 --- a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/language/translator/translations.json +++ b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/language/translator/translations.json @@ -23744,28 +23744,16 @@ "scenario": "g_withXmultilabelX_V_elementMap_multilabel", "traversals": [ { - "original": "g.addV(\"person\").addLabel(\"employee\").property(\"name\", \"marko\")", - "language": "g.addV(\"person\").addLabel(\"employee\").property(\"name\", \"marko\")", - "canonical": "g.addV(\"person\").addLabel(\"employee\").property(\"name\", \"marko\")", - "anonymized": "g.addV(string0).addLabel(string1).property(string2, string3)", - "dotnet": "g.AddV((string) \"person\").AddLabel(\"employee\").Property(\"name\", \"marko\")", - "go": "g.AddV(\"person\").AddLabel(\"employee\").Property(\"name\", \"marko\")", - "groovy": "g.addV(\"person\").addLabel(\"employee\").property(\"name\", \"marko\")", - "java": "g.addV(\"person\").addLabel(\"employee\").property(\"name\", \"marko\")", - "javascript": "g.addV(\"person\").addLabel(\"employee\").property(\"name\", \"marko\")", - "python": "g.add_v('person').add_label('employee').property('name', 'marko')" - }, - { - "original": "g.with(\"multilabel\").V().elementMap()", - "language": "g.with(\"multilabel\").V().elementMap()", - "canonical": "g.with(\"multilabel\").V().elementMap()", - "anonymized": "g.with(string0).V().elementMap()", - "dotnet": "g.With(\"multilabel\").V().ElementMap()", - "go": "g.With(\"multilabel\").V().ElementMap()", - "groovy": "g.with(\"multilabel\").V().elementMap()", - "java": "g.with(\"multilabel\").V().elementMap()", - "javascript": "g.with_(\"multilabel\").V().elementMap()", - "python": "g.with_('multilabel').V().element_map()" + "original": "g.with(\"multilabel\").V().has(\"name\", \"tux\").elementMap(\"name\", \"species\")", + "language": "g.with(\"multilabel\").V().has(\"name\", \"tux\").elementMap(\"name\", \"species\")", + "canonical": "g.with(\"multilabel\").V().has(\"name\", \"tux\").elementMap(\"name\", \"species\")", + "anonymized": "g.with(string0).V().has(string1, string2).elementMap(string1, string3)", + "dotnet": "g.With(\"multilabel\").V().Has(\"name\", \"tux\").ElementMap(\"name\", \"species\")", + "go": "g.With(\"multilabel\").V().Has(\"name\", \"tux\").ElementMap(\"name\", \"species\")", + "groovy": "g.with(\"multilabel\").V().has(\"name\", \"tux\").elementMap(\"name\", \"species\")", + "java": "g.with(\"multilabel\").V().has(\"name\", \"tux\").elementMap(\"name\", \"species\")", + "javascript": "g.with_(\"multilabel\").V().has(\"name\", \"tux\").elementMap(\"name\", \"species\")", + "python": "g.with_('multilabel').V().has('name', 'tux').element_map('name', 'species')" } ] }, @@ -23802,28 +23790,16 @@ "scenario": "g_withXsinglelabelX_V_elementMap_multi_label_default", "traversals": [ { - "original": "g.addV(\"person\").addLabel(\"employee\").property(\"name\", \"marko\")", - "language": "g.addV(\"person\").addLabel(\"employee\").property(\"name\", \"marko\")", - "canonical": "g.addV(\"person\").addLabel(\"employee\").property(\"name\", \"marko\")", - "anonymized": "g.addV(string0).addLabel(string1).property(string2, string3)", - "dotnet": "g.AddV((string) \"person\").AddLabel(\"employee\").Property(\"name\", \"marko\")", - "go": "g.AddV(\"person\").AddLabel(\"employee\").Property(\"name\", \"marko\")", - "groovy": "g.addV(\"person\").addLabel(\"employee\").property(\"name\", \"marko\")", - "java": "g.addV(\"person\").addLabel(\"employee\").property(\"name\", \"marko\")", - "javascript": "g.addV(\"person\").addLabel(\"employee\").property(\"name\", \"marko\")", - "python": "g.add_v('person').add_label('employee').property('name', 'marko')" - }, - { - "original": "g.with(\"singlelabel\").V().elementMap()", - "language": "g.with(\"singlelabel\").V().elementMap()", - "canonical": "g.with(\"singlelabel\").V().elementMap()", - "anonymized": "g.with(string0).V().elementMap()", - "dotnet": "g.With(\"singlelabel\").V().ElementMap()", - "go": "g.With(\"singlelabel\").V().ElementMap()", - "groovy": "g.with(\"singlelabel\").V().elementMap()", - "java": "g.with(\"singlelabel\").V().elementMap()", - "javascript": "g.with_(\"singlelabel\").V().elementMap()", - "python": "g.with_('singlelabel').V().element_map()" + "original": "g.with(\"singlelabel\").V().has(\"name\", \"lagoon\").elementMap(\"name\", \"biome\")", + "language": "g.with(\"singlelabel\").V().has(\"name\", \"lagoon\").elementMap(\"name\", \"biome\")", + "canonical": "g.with(\"singlelabel\").V().has(\"name\", \"lagoon\").elementMap(\"name\", \"biome\")", + "anonymized": "g.with(string0).V().has(string1, string2).elementMap(string1, string3)", + "dotnet": "g.With(\"singlelabel\").V().Has(\"name\", \"lagoon\").ElementMap(\"name\", \"biome\")", + "go": "g.With(\"singlelabel\").V().Has(\"name\", \"lagoon\").ElementMap(\"name\", \"biome\")", + "groovy": "g.with(\"singlelabel\").V().has(\"name\", \"lagoon\").elementMap(\"name\", \"biome\")", + "java": "g.with(\"singlelabel\").V().has(\"name\", \"lagoon\").elementMap(\"name\", \"biome\")", + "javascript": "g.with_(\"singlelabel\").V().has(\"name\", \"lagoon\").elementMap(\"name\", \"biome\")", + "python": "g.with_('singlelabel').V().has('name', 'lagoon').element_map('name', 'biome')" } ] }, @@ -23976,28 +23952,16 @@ "scenario": "g_withXmultilabelX_E_elementMap", "traversals": [ { - "original": "g.addV(\"person\").property(\"name\", \"marko\").as(\"a\").addV(\"person\").property(\"name\", \"josh\").as(\"b\").addE(\"knows\").from(\"a\").to(\"b\").property(\"weight\", 0.5)", - "language": "g.addV(\"person\").property(\"name\", \"marko\").as(\"a\").addV(\"person\").property(\"name\", \"josh\").as(\"b\").addE(\"knows\").from(\"a\").to(\"b\").property(\"weight\", 0.5)", - "canonical": "g.addV(\"person\").property(\"name\", \"marko\").as(\"a\").addV(\"person\").property(\"name\", \"josh\").as(\"b\").addE(\"knows\").from(\"a\").to(\"b\").property(\"weight\", 0.5)", - "anonymized": "g.addV(string0).property(string1, string2).as(string3).addV(string0).property(string1, string4).as(string5).addE(string6).from(string3).to(string5).property(string7, number0)", - "dotnet": "g.AddV((string) \"person\").Property(\"name\", \"marko\").As(\"a\").AddV((string) \"person\").Property(\"name\", \"josh\").As(\"b\").AddE((string) \"knows\").From(\"a\").To(\"b\").Property(\"weight\", 0.5)", - "go": "g.AddV(\"person\").Property(\"name\", \"marko\").As(\"a\").AddV(\"person\").Property(\"name\", \"josh\").As(\"b\").AddE(\"knows\").From(\"a\").To(\"b\").Property(\"weight\", 0.5)", - "groovy": "g.addV(\"person\").property(\"name\", \"marko\").as(\"a\").addV(\"person\").property(\"name\", \"josh\").as(\"b\").addE(\"knows\").from(\"a\").to(\"b\").property(\"weight\", 0.5)", - "java": "g.addV(\"person\").property(\"name\", \"marko\").as(\"a\").addV(\"person\").property(\"name\", \"josh\").as(\"b\").addE(\"knows\").from(\"a\").to(\"b\").property(\"weight\", 0.5)", - "javascript": "g.addV(\"person\").property(\"name\", \"marko\").as(\"a\").addV(\"person\").property(\"name\", \"josh\").as(\"b\").addE(\"knows\").from_(\"a\").to(\"b\").property(\"weight\", 0.5)", - "python": "g.add_v('person').property('name', 'marko').as_('a').add_v('person').property('name', 'josh').as_('b').add_e('knows').from_('a').to('b').property('weight', 0.5)" - }, - { - "original": "g.with(\"multilabel\").E().elementMap()", - "language": "g.with(\"multilabel\").E().elementMap()", - "canonical": "g.with(\"multilabel\").E().elementMap()", - "anonymized": "g.with(string0).E().elementMap()", - "dotnet": "g.With(\"multilabel\").E().ElementMap()", - "go": "g.With(\"multilabel\").E().ElementMap()", - "groovy": "g.with(\"multilabel\").E().elementMap()", - "java": "g.with(\"multilabel\").E().elementMap()", - "javascript": "g.with_(\"multilabel\").E().elementMap()", - "python": "g.with_('multilabel').E().element_map()" + "original": "g.with(\"multilabel\").E().has(\"since\", 2018).hasLabel(\"livesIn\").elementMap()", + "language": "g.with(\"multilabel\").E().has(\"since\", 2018).hasLabel(\"livesIn\").elementMap()", + "canonical": "g.with(\"multilabel\").E().has(\"since\", 2018).hasLabel(\"livesIn\").elementMap()", + "anonymized": "g.with(string0).E().has(string1, number0).hasLabel(string2).elementMap()", + "dotnet": "g.With(\"multilabel\").E().Has(\"since\", 2018).HasLabel(\"livesIn\").ElementMap()", + "go": "g.With(\"multilabel\").E().Has(\"since\", 2018).HasLabel(\"livesIn\").ElementMap()", + "groovy": "g.with(\"multilabel\").E().has(\"since\", 2018).hasLabel(\"livesIn\").elementMap()", + "java": "g.with(\"multilabel\").E().has(\"since\", 2018).hasLabel(\"livesIn\").elementMap()", + "javascript": "g.with_(\"multilabel\").E().has(\"since\", 2018).hasLabel(\"livesIn\").elementMap()", + "python": "g.with_('multilabel').E().has('since', 2018).has_label('livesIn').element_map()" } ] }, @@ -24983,28 +24947,16 @@ "scenario": "g_V_label_deprecated_multilabel", "traversals": [ { - "original": "g.addV(\"a\", \"b\").property(\"name\", \"test\")", - "language": "g.addV(\"a\", \"b\").property(\"name\", \"test\")", - "canonical": "g.addV(\"a\", \"b\").property(\"name\", \"test\")", - "anonymized": "g.addV(string0, string1).property(string2, string3)", - "dotnet": "g.AddV((string) \"a\", (string) \"b\").Property(\"name\", \"test\")", - "go": "g.AddV(\"a\", \"b\").Property(\"name\", \"test\")", - "groovy": "g.addV(\"a\", \"b\").property(\"name\", \"test\")", - "java": "g.addV(\"a\", \"b\").property(\"name\", \"test\")", - "javascript": "g.addV(\"a\", \"b\").property(\"name\", \"test\")", - "python": "g.add_v('a', 'b').property('name', 'test')" - }, - { - "original": "g.V().label().count()", - "language": "g.V().label().count()", - "canonical": "g.V().label().count()", - "anonymized": "g.V().label().count()", - "dotnet": "g.V().Label().Count()", - "go": "g.V().Label().Count()", - "groovy": "g.V().label().count()", - "java": "g.V().label().count()", - "javascript": "g.V().label().count()", - "python": "g.V().label().count()" + "original": "g.V().has(\"name\", \"tux\").label().count()", + "language": "g.V().has(\"name\", \"tux\").label().count()", + "canonical": "g.V().has(\"name\", \"tux\").label().count()", + "anonymized": "g.V().has(string0, string1).label().count()", + "dotnet": "g.V().Has(\"name\", \"tux\").Label().Count()", + "go": "g.V().Has(\"name\", \"tux\").Label().Count()", + "groovy": "g.V().has(\"name\", \"tux\").label().count()", + "java": "g.V().has(\"name\", \"tux\").label().count()", + "javascript": "g.V().has(\"name\", \"tux\").label().count()", + "python": "g.V().has('name', 'tux').label().count()" } ] }, @@ -25012,28 +24964,16 @@ "scenario": "g_V_label_deprecated_multilabel_value_is_one_of_labels", "traversals": [ { - "original": "g.addV(\"a\", \"b\").property(\"name\", \"test\")", - "language": "g.addV(\"a\", \"b\").property(\"name\", \"test\")", - "canonical": "g.addV(\"a\", \"b\").property(\"name\", \"test\")", - "anonymized": "g.addV(string0, string1).property(string2, string3)", - "dotnet": "g.AddV((string) \"a\", (string) \"b\").Property(\"name\", \"test\")", - "go": "g.AddV(\"a\", \"b\").Property(\"name\", \"test\")", - "groovy": "g.addV(\"a\", \"b\").property(\"name\", \"test\")", - "java": "g.addV(\"a\", \"b\").property(\"name\", \"test\")", - "javascript": "g.addV(\"a\", \"b\").property(\"name\", \"test\")", - "python": "g.add_v('a', 'b').property('name', 'test')" - }, - { - "original": "g.V().filter(__.label().is(P.within(\"a\", \"b\")))", - "language": "g.V().filter(__.label().is(P.within(\"a\", \"b\")))", - "canonical": "g.V().filter(__.label().is(P.within(\"a\", \"b\")))", - "anonymized": "g.V().filter(__.label().is(P.within(string0, string1)))", - "dotnet": "g.V().Filter(__.Label().Is(P.Within(\"a\", \"b\")))", - "go": "g.V().Filter(gremlingo.T__.Label().Is(gremlingo.P.Within(\"a\", \"b\")))", - "groovy": "g.V().filter(__.label().is(P.within(\"a\", \"b\")))", - "java": "g.V().filter(__.label().is(P.within(\"a\", \"b\")))", - "javascript": "g.V().filter(__.label().is(P.within(\"a\", \"b\")))", - "python": "g.V().filter_(__.label().is_(P.within('a', 'b')))" + "original": "g.V().has(\"name\", \"tux\").filter(__.label().is(P.within(\"animal\", \"bird\", \"aquatic\", \"endangered\")))", + "language": "g.V().has(\"name\", \"tux\").filter(__.label().is(P.within(\"animal\", \"bird\", \"aquatic\", \"endangered\")))", + "canonical": "g.V().has(\"name\", \"tux\").filter(__.label().is(P.within(\"animal\", \"bird\", \"aquatic\", \"endangered\")))", + "anonymized": "g.V().has(string0, string1).filter(__.label().is(P.within(string2, string3, string4, string5)))", + "dotnet": "g.V().Has(\"name\", \"tux\").Filter(__.Label().Is(P.Within(\"animal\", \"bird\", \"aquatic\", \"endangered\")))", + "go": "g.V().Has(\"name\", \"tux\").Filter(gremlingo.T__.Label().Is(gremlingo.P.Within(\"animal\", \"bird\", \"aquatic\", \"endangered\")))", + "groovy": "g.V().has(\"name\", \"tux\").filter(__.label().is(P.within(\"animal\", \"bird\", \"aquatic\", \"endangered\")))", + "java": "g.V().has(\"name\", \"tux\").filter(__.label().is(P.within(\"animal\", \"bird\", \"aquatic\", \"endangered\")))", + "javascript": "g.V().has(\"name\", \"tux\").filter(__.label().is(P.within(\"animal\", \"bird\", \"aquatic\", \"endangered\")))", + "python": "g.V().has('name', 'tux').filter_(__.label().is_(P.within('animal', 'bird', 'aquatic', 'endangered')))" } ] }, @@ -25087,28 +25027,16 @@ "scenario": "g_V_labels_multilabel", "traversals": [ { - "original": "g.addV(\"a\", \"b\")", - "language": "g.addV(\"a\", \"b\")", - "canonical": "g.addV(\"a\", \"b\")", - "anonymized": "g.addV(string0, string1)", - "dotnet": "g.AddV((string) \"a\", (string) \"b\")", - "go": "g.AddV(\"a\", \"b\")", - "groovy": "g.addV(\"a\", \"b\")", - "java": "g.addV(\"a\", \"b\")", - "javascript": "g.addV(\"a\", \"b\")", - "python": "g.add_v('a', 'b')" - }, - { - "original": "g.V().labels()", - "language": "g.V().labels()", - "canonical": "g.V().labels()", - "anonymized": "g.V().labels()", - "dotnet": "g.V().Labels()", - "go": "g.V().Labels()", - "groovy": "g.V().labels()", - "java": "g.V().labels()", - "javascript": "g.V().labels()", - "python": "g.V().labels()" + "original": "g.V().has(\"name\", \"tux\").labels()", + "language": "g.V().has(\"name\", \"tux\").labels()", + "canonical": "g.V().has(\"name\", \"tux\").labels()", + "anonymized": "g.V().has(string0, string1).labels()", + "dotnet": "g.V().Has(\"name\", \"tux\").Labels()", + "go": "g.V().Has(\"name\", \"tux\").Labels()", + "groovy": "g.V().has(\"name\", \"tux\").labels()", + "java": "g.V().has(\"name\", \"tux\").labels()", + "javascript": "g.V().has(\"name\", \"tux\").labels()", + "python": "g.V().has('name', 'tux').labels()" } ] }, @@ -39430,28 +39358,16 @@ "scenario": "g_withXmultilabelX_V_valueMap_withXtokensX_multilabel", "traversals": [ { - "original": "g.addV(\"person\").addLabel(\"employee\").property(\"name\", \"marko\")", - "language": "g.addV(\"person\").addLabel(\"employee\").property(\"name\", \"marko\")", - "canonical": "g.addV(\"person\").addLabel(\"employee\").property(\"name\", \"marko\")", - "anonymized": "g.addV(string0).addLabel(string1).property(string2, string3)", - "dotnet": "g.AddV((string) \"person\").AddLabel(\"employee\").Property(\"name\", \"marko\")", - "go": "g.AddV(\"person\").AddLabel(\"employee\").Property(\"name\", \"marko\")", - "groovy": "g.addV(\"person\").addLabel(\"employee\").property(\"name\", \"marko\")", - "java": "g.addV(\"person\").addLabel(\"employee\").property(\"name\", \"marko\")", - "javascript": "g.addV(\"person\").addLabel(\"employee\").property(\"name\", \"marko\")", - "python": "g.add_v('person').add_label('employee').property('name', 'marko')" - }, - { - "original": "g.with(\"multilabel\").V().valueMap().with(WithOptions.tokens)", - "language": "g.with(\"multilabel\").V().valueMap().with(WithOptions.tokens)", - "canonical": "g.with(\"multilabel\").V().valueMap().with(WithOptions.tokens)", - "anonymized": "g.with(string0).V().valueMap().with(WithOptions.tokens)", - "dotnet": "g.With(\"multilabel\").V().ValueMap().With(WithOptions.Tokens)", - "go": "g.With(\"multilabel\").V().ValueMap().With(gremlingo.WithOptions.Tokens)", - "groovy": "g.with(\"multilabel\").V().valueMap().with(WithOptions.tokens)", - "java": "g.with(\"multilabel\").V().valueMap().with(WithOptions.tokens)", - "javascript": "g.with_(\"multilabel\").V().valueMap().with_(WithOptions.tokens)", - "python": "g.with_('multilabel').V().value_map().with_(WithOptions.tokens)" + "original": "g.with(\"multilabel\").V().has(\"name\", \"tux\").valueMap(\"name\", \"species\").with(WithOptions.tokens)", + "language": "g.with(\"multilabel\").V().has(\"name\", \"tux\").valueMap(\"name\", \"species\").with(WithOptions.tokens)", + "canonical": "g.with(\"multilabel\").V().has(\"name\", \"tux\").valueMap(\"name\", \"species\").with(WithOptions.tokens)", + "anonymized": "g.with(string0).V().has(string1, string2).valueMap(string1, string3).with(WithOptions.tokens)", + "dotnet": "g.With(\"multilabel\").V().Has(\"name\", \"tux\").ValueMap(\"name\", \"species\").With(WithOptions.Tokens)", + "go": "g.With(\"multilabel\").V().Has(\"name\", \"tux\").ValueMap(\"name\", \"species\").With(gremlingo.WithOptions.Tokens)", + "groovy": "g.with(\"multilabel\").V().has(\"name\", \"tux\").valueMap(\"name\", \"species\").with(WithOptions.tokens)", + "java": "g.with(\"multilabel\").V().has(\"name\", \"tux\").valueMap(\"name\", \"species\").with(WithOptions.tokens)", + "javascript": "g.with_(\"multilabel\").V().has(\"name\", \"tux\").valueMap(\"name\", \"species\").with_(WithOptions.tokens)", + "python": "g.with_('multilabel').V().has('name', 'tux').value_map('name', 'species').with_(WithOptions.tokens)" } ] }, @@ -39488,28 +39404,16 @@ "scenario": "g_withXsinglelabelX_V_valueMap_withXtokensX_multi_label_default", "traversals": [ { - "original": "g.addV(\"person\").addLabel(\"employee\").property(\"name\", \"marko\")", - "language": "g.addV(\"person\").addLabel(\"employee\").property(\"name\", \"marko\")", - "canonical": "g.addV(\"person\").addLabel(\"employee\").property(\"name\", \"marko\")", - "anonymized": "g.addV(string0).addLabel(string1).property(string2, string3)", - "dotnet": "g.AddV((string) \"person\").AddLabel(\"employee\").Property(\"name\", \"marko\")", - "go": "g.AddV(\"person\").AddLabel(\"employee\").Property(\"name\", \"marko\")", - "groovy": "g.addV(\"person\").addLabel(\"employee\").property(\"name\", \"marko\")", - "java": "g.addV(\"person\").addLabel(\"employee\").property(\"name\", \"marko\")", - "javascript": "g.addV(\"person\").addLabel(\"employee\").property(\"name\", \"marko\")", - "python": "g.add_v('person').add_label('employee').property('name', 'marko')" - }, - { - "original": "g.with(\"singlelabel\").V().valueMap().with(WithOptions.tokens)", - "language": "g.with(\"singlelabel\").V().valueMap().with(WithOptions.tokens)", - "canonical": "g.with(\"singlelabel\").V().valueMap().with(WithOptions.tokens)", - "anonymized": "g.with(string0).V().valueMap().with(WithOptions.tokens)", - "dotnet": "g.With(\"singlelabel\").V().ValueMap().With(WithOptions.Tokens)", - "go": "g.With(\"singlelabel\").V().ValueMap().With(gremlingo.WithOptions.Tokens)", - "groovy": "g.with(\"singlelabel\").V().valueMap().with(WithOptions.tokens)", - "java": "g.with(\"singlelabel\").V().valueMap().with(WithOptions.tokens)", - "javascript": "g.with_(\"singlelabel\").V().valueMap().with_(WithOptions.tokens)", - "python": "g.with_('singlelabel').V().value_map().with_(WithOptions.tokens)" + "original": "g.with(\"singlelabel\").V().has(\"name\", \"lagoon\").valueMap(\"name\", \"biome\").with(WithOptions.tokens)", + "language": "g.with(\"singlelabel\").V().has(\"name\", \"lagoon\").valueMap(\"name\", \"biome\").with(WithOptions.tokens)", + "canonical": "g.with(\"singlelabel\").V().has(\"name\", \"lagoon\").valueMap(\"name\", \"biome\").with(WithOptions.tokens)", + "anonymized": "g.with(string0).V().has(string1, string2).valueMap(string1, string3).with(WithOptions.tokens)", + "dotnet": "g.With(\"singlelabel\").V().Has(\"name\", \"lagoon\").ValueMap(\"name\", \"biome\").With(WithOptions.Tokens)", + "go": "g.With(\"singlelabel\").V().Has(\"name\", \"lagoon\").ValueMap(\"name\", \"biome\").With(gremlingo.WithOptions.Tokens)", + "groovy": "g.with(\"singlelabel\").V().has(\"name\", \"lagoon\").valueMap(\"name\", \"biome\").with(WithOptions.tokens)", + "java": "g.with(\"singlelabel\").V().has(\"name\", \"lagoon\").valueMap(\"name\", \"biome\").with(WithOptions.tokens)", + "javascript": "g.with_(\"singlelabel\").V().has(\"name\", \"lagoon\").valueMap(\"name\", \"biome\").with_(WithOptions.tokens)", + "python": "g.with_('singlelabel').V().has('name', 'lagoon').value_map('name', 'biome').with_(WithOptions.tokens)" } ] }, diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/gryo/tinkerpop-zoo-v3.kryo b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/gryo/tinkerpop-zoo-v3.kryo new file mode 100644 index 00000000000..57af38c990d Binary files /dev/null and b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/gryo/tinkerpop-zoo-v3.kryo differ diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/ElementMap.feature b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/ElementMap.feature index b4a827271af..ea7e30297b0 100644 --- a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/ElementMap.feature +++ b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/ElementMap.feature @@ -144,19 +144,15 @@ Feature: Step - elementMap() @MultiLabel Scenario: g_withXmultilabelX_V_elementMap_multilabel - Given the empty graph - And the graph initializer of - """ - g.addV("person").addLabel("employee").property("name", "marko") - """ + Given the zoo graph And the traversal of """ - g.with("multilabel").V().elementMap() + g.with("multilabel").V().has("name", "tux").elementMap("name", "species") """ When iterated to list Then the result should be unordered | result | - | m[{"t[id]": "v[marko].id", "t[label]": "s[person,employee]", "name": "marko"}] | + | m[{"t[id]": "v[tux].id", "t[label]": "s[animal,bird,aquatic,endangered]", "name": "tux", "species": "african penguin"}] | @MultiLabel @MultiLabelDefault Scenario: g_V_elementMap_multi_label_default @@ -176,21 +172,17 @@ Feature: Step - elementMap() @MultiLabel @MultiLabelDefault Scenario: g_withXsinglelabelX_V_elementMap_multi_label_default - Given the empty graph - And the graph initializer of - """ - g.addV("person").addLabel("employee").property("name", "marko") - """ + Given the zoo graph And the traversal of """ - g.with("singlelabel").V().elementMap() + g.with("singlelabel").V().has("name", "lagoon").elementMap("name", "biome") """ When iterated to list Then the result should have a count of 1 And the result should be of | result | - | m[{"t[id]": "v[marko].id", "t[label]": "person", "name": "marko"}] | - | m[{"t[id]": "v[marko].id", "t[label]": "employee", "name": "marko"}] | + | m[{"t[id]": "v[lagoon].id", "t[label]": "habitat", "name": "lagoon", "biome": "marine"}] | + | m[{"t[id]": "v[lagoon].id", "t[label]": "aquatic", "name": "lagoon", "biome": "marine"}] | @MultiLabel @MultiLabelDefault Scenario: g_V_elementMap_single_label_vertex_multi_label_default @@ -272,21 +264,17 @@ Feature: Step - elementMap() | result | | m[{"t[id]": "v[nobody].id", "name": "nobody"}] | - @MultiLabel + @GraphComputerVerificationReferenceOnly @MultiLabel Scenario: g_withXmultilabelX_E_elementMap - Given the empty graph - And the graph initializer of - """ - g.addV("person").property("name", "marko").as("a").addV("person").property("name", "josh").as("b").addE("knows").from("a").to("b").property("weight", 0.5) - """ + Given the zoo graph And the traversal of """ - g.with("multilabel").E().elementMap() + g.with("multilabel").E().has("since", 2018).hasLabel("livesIn").elementMap() """ When iterated to list Then the result should be unordered | result | - | m[{"t[id]": "e[marko-knows->josh].id", "t[label]": "s[knows]", "weight": "d[0.5].d", "D[OUT]": "m[{\\"t[id]\\": \\"v[marko].id\\", \\"t[label]\\": \\"s[person]\\"}]", "D[IN]": "m[{\\"t[id]\\": \\"v[josh].id\\", \\"t[label]\\": \\"s[person]\\"}]"}] | + | m[{"t[id]": "e[atlas-livesIn->lagoon].id", "t[label]": "s[livesIn]", "since": 2018, "D[OUT]": "m[{\\"t[id]\\": \\"v[atlas].id\\", \\"t[label]\\": \\"s[animal,reptile,aquatic,endangered]\\"}]", "D[IN]": "m[{\\"t[id]\\": \\"v[lagoon].id\\", \\"t[label]\\": \\"s[habitat,aquatic]\\"}]"}] | @GraphComputerVerificationReferenceOnly @MultiLabel @MultiLabelDefault Scenario: g_E_elementMap_multi_label_default diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/Label.feature b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/Label.feature index 276c967fcb8..c93d7ebe44d 100644 --- a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/Label.feature +++ b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/Label.feature @@ -34,14 +34,10 @@ Feature: Step - label() @MultiLabel Scenario: g_V_label_deprecated_multilabel - Given the empty graph - And the graph initializer of - """ - g.addV("a", "b").property("name", "test") - """ + Given the zoo graph And the traversal of """ - g.V().label().count() + g.V().has("name", "tux").label().count() """ When iterated to list Then the result should be unordered @@ -50,14 +46,10 @@ Feature: Step - label() @MultiLabel Scenario: g_V_label_deprecated_multilabel_value_is_one_of_labels - Given the empty graph - And the graph initializer of - """ - g.addV("a", "b").property("name", "test") - """ + Given the zoo graph And the traversal of """ - g.V().filter(__.label().is(P.within("a", "b"))) + g.V().has("name", "tux").filter(__.label().is(P.within("animal", "bird", "aquatic", "endangered"))) """ When iterated to list Then the result should have a count of 1 diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/Labels.feature b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/Labels.feature index 60869bdbcad..26fb1db50da 100644 --- a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/Labels.feature +++ b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/Labels.feature @@ -35,20 +35,18 @@ Feature: Step - labels() @MultiLabel Scenario: g_V_labels_multilabel - Given the empty graph - And the graph initializer of - """ - g.addV("a", "b") - """ + Given the zoo graph And the traversal of """ - g.V().labels() + g.V().has("name", "tux").labels() """ When iterated to list Then the result should be unordered | result | - | a | - | b | + | animal | + | bird | + | aquatic | + | endangered | @MultiLabel Scenario: g_addVXa_bX_labels_count @@ -133,4 +131,4 @@ Feature: Step - labels() Then the result should be unordered | result | | knows | - | knows | \ No newline at end of file + | knows | diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/ValueMap.feature b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/ValueMap.feature index c339aae4e9f..791d6333e8c 100644 --- a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/ValueMap.feature +++ b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/ValueMap.feature @@ -286,19 +286,15 @@ Feature: Step - valueMap() @MultiLabel Scenario: g_withXmultilabelX_V_valueMap_withXtokensX_multilabel - Given the empty graph - And the graph initializer of - """ - g.addV("person").addLabel("employee").property("name", "marko") - """ + Given the zoo graph And the traversal of """ - g.with("multilabel").V().valueMap().with(WithOptions.tokens) + g.with("multilabel").V().has("name", "tux").valueMap("name", "species").with(WithOptions.tokens) """ When iterated to list Then the result should be unordered | result | - | m[{"t[id]": "v[marko].id", "t[label]": "s[person,employee]", "name": ["marko"]}] | + | m[{"t[id]": "v[tux].id", "t[label]": "s[animal,bird,aquatic,endangered]", "name": ["tux"], "species": ["african penguin"]}] | @MultiLabel @MultiLabelDefault Scenario: g_V_valueMap_withXtokensX_multi_label_default @@ -318,21 +314,17 @@ Feature: Step - valueMap() @MultiLabel @MultiLabelDefault Scenario: g_withXsinglelabelX_V_valueMap_withXtokensX_multi_label_default - Given the empty graph - And the graph initializer of - """ - g.addV("person").addLabel("employee").property("name", "marko") - """ + Given the zoo graph And the traversal of """ - g.with("singlelabel").V().valueMap().with(WithOptions.tokens) + g.with("singlelabel").V().has("name", "lagoon").valueMap("name", "biome").with(WithOptions.tokens) """ When iterated to list Then the result should have a count of 1 And the result should be of | result | - | m[{"t[id]": "v[marko].id", "t[label]": "person", "name": ["marko"]}] | - | m[{"t[id]": "v[marko].id", "t[label]": "employee", "name": ["marko"]}] | + | m[{"t[id]": "v[lagoon].id", "t[label]": "habitat", "name": ["lagoon"], "biome": ["marine"]}] | + | m[{"t[id]": "v[lagoon].id", "t[label]": "aquatic", "name": ["lagoon"], "biome": ["marine"]}] | @MultiLabel Scenario: g_withXsinglelabelX_V_valueMapXtrueX_zero_label_vertex diff --git a/hadoop-gremlin/src/main/java/org/apache/tinkerpop/gremlin/hadoop/structure/HadoopElement.java b/hadoop-gremlin/src/main/java/org/apache/tinkerpop/gremlin/hadoop/structure/HadoopElement.java index 64221509d5d..cbde3aeeb92 100644 --- a/hadoop-gremlin/src/main/java/org/apache/tinkerpop/gremlin/hadoop/structure/HadoopElement.java +++ b/hadoop-gremlin/src/main/java/org/apache/tinkerpop/gremlin/hadoop/structure/HadoopElement.java @@ -24,6 +24,8 @@ import org.apache.tinkerpop.gremlin.structure.Property; import org.apache.tinkerpop.gremlin.structure.Vertex; +import java.util.Set; + /** * @author Marko A. Rodriguez (http://markorodriguez.com) */ @@ -55,6 +57,11 @@ public String label() { return this.baseElement.label(); } + @Override + public Set labels() { + return this.baseElement.labels(); + } + @Override public void remove() { if (this.baseElement instanceof Vertex) diff --git a/hadoop-gremlin/src/test/java/org/apache/tinkerpop/gremlin/hadoop/HadoopGraphFeatureIntegrateTest.java b/hadoop-gremlin/src/test/java/org/apache/tinkerpop/gremlin/hadoop/HadoopGraphFeatureIntegrateTest.java index 03132751de5..21bfd5bbc44 100644 --- a/hadoop-gremlin/src/test/java/org/apache/tinkerpop/gremlin/hadoop/HadoopGraphFeatureIntegrateTest.java +++ b/hadoop-gremlin/src/test/java/org/apache/tinkerpop/gremlin/hadoop/HadoopGraphFeatureIntegrateTest.java @@ -42,12 +42,10 @@ import org.junit.runner.RunWith; import java.util.ArrayList; -import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; -import java.util.stream.Collectors; import static org.apache.tinkerpop.gremlin.LoadGraphWith.GraphData; @@ -91,6 +89,7 @@ public static class HadoopGraphWorld implements World { private static final HadoopGraph crew = HadoopGraph.open(new MapConfiguration(getBaseConfiguration(GraphData.CREW))); private static final HadoopGraph sink = HadoopGraph.open(new MapConfiguration(getBaseConfiguration(GraphData.SINK))); private static final HadoopGraph grateful = HadoopGraph.open(new MapConfiguration(getBaseConfiguration(GraphData.GRATEFUL))); + private static final HadoopGraph zoo = HadoopGraph.open(new MapConfiguration(getBaseConfiguration(GraphData.ZOO))); static { readIntoGraph(modern, GraphData.MODERN); @@ -98,6 +97,7 @@ public static class HadoopGraphWorld implements World { readIntoGraph(crew, GraphData.CREW); readIntoGraph(sink, GraphData.SINK); readIntoGraph(grateful, GraphData.GRATEFUL); + readIntoGraph(zoo, GraphData.ZOO); } @Override @@ -114,6 +114,8 @@ else if (graphData == GraphData.SINK) return sink.traversal(); else if (graphData == GraphData.GRATEFUL) return grateful.traversal(); + else if (graphData == GraphData.ZOO) + return zoo.traversal(); else throw new UnsupportedOperationException("GraphData not supported: " + graphData.name()); } diff --git a/spark-gremlin/src/test/java/org/apache/tinkerpop/gremlin/spark/SparkGraphFeatureIntegrateTest.java b/spark-gremlin/src/test/java/org/apache/tinkerpop/gremlin/spark/SparkGraphFeatureIntegrateTest.java index 884b27ac8d8..3b5903d6f1c 100644 --- a/spark-gremlin/src/test/java/org/apache/tinkerpop/gremlin/spark/SparkGraphFeatureIntegrateTest.java +++ b/spark-gremlin/src/test/java/org/apache/tinkerpop/gremlin/spark/SparkGraphFeatureIntegrateTest.java @@ -115,6 +115,7 @@ public static class SparkGraphWorld implements World { private static final HadoopGraph crew = HadoopGraph.open(new MapConfiguration(getBaseConfiguration(GraphData.CREW))); private static final HadoopGraph sink = HadoopGraph.open(new MapConfiguration(getBaseConfiguration(GraphData.SINK))); private static final HadoopGraph grateful = HadoopGraph.open(new MapConfiguration(getBaseConfiguration(GraphData.GRATEFUL))); + private static final HadoopGraph zoo = HadoopGraph.open(new MapConfiguration(getBaseConfiguration(GraphData.ZOO))); static { readIntoGraph(modern, GraphData.MODERN); @@ -122,6 +123,7 @@ public static class SparkGraphWorld implements World { readIntoGraph(crew, GraphData.CREW); readIntoGraph(sink, GraphData.SINK); readIntoGraph(grateful, GraphData.GRATEFUL); + readIntoGraph(zoo, GraphData.ZOO); } @Override @@ -138,6 +140,8 @@ else if (graphData == GraphData.SINK) return sink.traversal().withComputer(Computer.compute(SparkGraphComputer.class).workers(AVAILABLE_PROCESSORS)); else if (graphData == GraphData.GRATEFUL) return grateful.traversal().withComputer(Computer.compute(SparkGraphComputer.class).workers(AVAILABLE_PROCESSORS)); + else if (graphData == GraphData.ZOO) + return zoo.traversal().withComputer(Computer.compute(SparkGraphComputer.class).workers(AVAILABLE_PROCESSORS)); else throw new UnsupportedOperationException("GraphData not supported: " + graphData.name()); } diff --git a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerFactory.java b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerFactory.java index fc6105e9f81..15b1c99322b 100644 --- a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerFactory.java +++ b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerFactory.java @@ -21,6 +21,7 @@ import org.apache.commons.configuration2.BaseConfiguration; import org.apache.commons.configuration2.Configuration; import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource; +import org.apache.tinkerpop.gremlin.structure.LabelCardinality; import org.apache.tinkerpop.gremlin.structure.T; import org.apache.tinkerpop.gremlin.structure.Vertex; import org.apache.tinkerpop.gremlin.structure.VertexProperty; @@ -252,6 +253,191 @@ public static void generateAirRoutes(final AbstractTinkerGraph graph) { } } + /** + * Create "the zoo" graph — a TinkerPop 4.x toy graph showcasing multi-label vertex support + * and diverse property types. Contains 13 vertices, 20 edges with animals classified across + * taxonomy, behavior, and conservation axes. + */ + public static TinkerGraph createTheZoo() { + final Configuration conf = getConfigurationWithCurrentNumberManager(); + conf.setProperty(AbstractTinkerGraph.GREMLIN_TINKERGRAPH_VERTEX_LABEL_CARDINALITY, LabelCardinality.ZERO_OR_MORE.name()); + final TinkerGraph g = TinkerGraph.open(conf); + generateTheZoo(g); + return g; + } + + /** + * Generate the graph in {@link #createTheZoo()} into an existing graph. The graph must support + * multi-label vertices (LabelCardinality of ONE_OR_MORE or ZERO_OR_MORE). + */ + public static void generateTheZoo(final AbstractTinkerGraph g) { + // Animals + final Vertex tux = g.addVertex(T.id, 1, T.label, "animal"); + tux.addLabel("bird", "aquatic", "endangered"); + tux.property("name", "tux"); + tux.property("species", "african penguin"); + tux.property("weight", 3.7d); + tux.property("age", 5); + tux.property("captiveBorn", true); + tux.property(VertexProperty.Cardinality.list, "diet", "fish"); + tux.property(VertexProperty.Cardinality.list, "diet", "krill"); + tux.property(VertexProperty.Cardinality.list, "diet", "squid"); + + final Vertex atlas = g.addVertex(T.id, 2, T.label, "animal"); + atlas.addLabel("reptile", "aquatic", "endangered"); + atlas.property("name", "atlas"); + atlas.property("species", "green sea turtle"); + atlas.property("weight", 180.5d); + atlas.property("age", 30); + atlas.property("captiveBorn", false); + atlas.property(VertexProperty.Cardinality.list, "diet", "seagrass"); + atlas.property(VertexProperty.Cardinality.list, "diet", "algae"); + + final Vertex ripple = g.addVertex(T.id, 3, T.label, "animal"); + ripple.addLabel("mammal", "aquatic"); + ripple.property("name", "ripple"); + ripple.property("species", "bottlenose dolphin"); + ripple.property("weight", 220.0d); + ripple.property("age", 12); + ripple.property("captiveBorn", true); + ripple.property(VertexProperty.Cardinality.list, "diet", "fish"); + ripple.property(VertexProperty.Cardinality.list, "diet", "squid"); + + final Vertex monty = g.addVertex(T.id, 4, T.label, "animal"); + monty.addLabel("reptile", "nocturnal"); + monty.property("name", "monty"); + monty.property("species", "ball python"); + monty.property("weight", 1.8d); + monty.property("age", 8); + monty.property("captiveBorn", true); + monty.property("venomous", false); + monty.property(VertexProperty.Cardinality.list, "diet", "mice"); + monty.property(VertexProperty.Cardinality.list, "diet", "rats"); + + final Vertex echo = g.addVertex(T.id, 5, T.label, "animal"); + echo.addLabel("mammal", "flying", "nocturnal"); + echo.property("name", "echo"); + echo.property("species", "fruit bat"); + echo.property("weight", 0.3d); + echo.property("age", 3); + echo.property("captiveBorn", true); + echo.property(VertexProperty.Cardinality.list, "diet", "fruit"); + echo.property(VertexProperty.Cardinality.list, "diet", "nectar"); + + final Vertex blaze = g.addVertex(T.id, 6, T.label, "animal"); + blaze.addLabel("mammal", "endangered", "nocturnal"); + blaze.property("name", "blaze"); + blaze.property("species", "red panda"); + blaze.property("weight", 5.4d); + blaze.property("age", 4); + blaze.property("captiveBorn", false); + blaze.property(VertexProperty.Cardinality.list, "diet", "bamboo"); + blaze.property(VertexProperty.Cardinality.list, "diet", "fruit"); + blaze.property(VertexProperty.Cardinality.list, "diet", "insects"); + + final Vertex titan = g.addVertex(T.id, 7, T.label, "animal"); + titan.addLabel("mammal", "endangered"); + titan.property("name", "titan"); + titan.property("species", "african elephant"); + titan.property("weight", 4000.0d); + titan.property("age", 15); + titan.property("captiveBorn", false); + titan.property(VertexProperty.Cardinality.list, "diet", "grass"); + titan.property(VertexProperty.Cardinality.list, "diet", "leaves"); + titan.property(VertexProperty.Cardinality.list, "diet", "bark"); + + final Vertex bitsy = g.addVertex(T.id, 8, T.label, "animal"); + bitsy.addLabel("mammal", "nocturnal"); + bitsy.property("name", "bitsy"); + bitsy.property("species", "harvest mouse"); + bitsy.property("weight", 0.006d); + bitsy.property("age", 1); + bitsy.property("captiveBorn", true); + bitsy.property(VertexProperty.Cardinality.list, "diet", "seeds"); + bitsy.property(VertexProperty.Cardinality.list, "diet", "insects"); + + final Vertex splash = g.addVertex(T.id, 9, T.label, "animal"); + splash.addLabel("mammal", "aquatic", "nocturnal", "endangered"); + splash.property("name", "splash"); + splash.property("species", "fishing cat"); + splash.property("weight", 8.2d); + splash.property("age", 2); + splash.property("captiveBorn", false); + splash.property(VertexProperty.Cardinality.list, "diet", "fish"); + splash.property(VertexProperty.Cardinality.list, "diet", "frogs"); + splash.property(VertexProperty.Cardinality.list, "diet", "crustaceans"); + + final Vertex tinker = g.addVertex(T.id, 10, T.label, "animal"); + tinker.addLabel("mammal", "nocturnal", "endangered"); + tinker.property("name", "tinker"); + tinker.property("species", "bengal tiger"); + tinker.property("weight", 220.0d); + tinker.property("age", 5); + tinker.property("captiveBorn", false); + tinker.property(VertexProperty.Cardinality.list, "diet", "deer"); + tinker.property(VertexProperty.Cardinality.list, "diet", "boar"); + tinker.property(VertexProperty.Cardinality.list, "diet", "snakes"); + + // Habitats + final Vertex lagoon = g.addVertex(T.id, 11, T.label, "habitat"); + lagoon.addLabel("aquatic"); + lagoon.property("name", "lagoon"); + lagoon.property("biome", "marine"); + lagoon.property("capacity", 8); + lagoon.property("openAir", true); + + final Vertex canopy = g.addVertex(T.id, 12, T.label, "habitat"); + canopy.property("name", "canopy"); + canopy.property("biome", "tropical"); + canopy.property("capacity", 8); + canopy.property("openAir", false); + + // People + final Vertex drGremlin = g.addVertex(T.id, 13, T.label, "person"); + drGremlin.addLabel("veterinarian", "keeper"); + drGremlin.property("name", "dr_gremlin"); + drGremlin.property("since", 2015); + drGremlin.property(VertexProperty.Cardinality.list, "specialties", "conservation"); + drGremlin.property(VertexProperty.Cardinality.list, "specialties", "surgery"); + drGremlin.property(VertexProperty.Cardinality.list, "specialties", "nutrition"); + + // Edges: livesIn + tux.addEdge("livesIn", lagoon, T.id, 14, "since", 2020); + atlas.addEdge("livesIn", lagoon, T.id, 15, "since", 2018); + ripple.addEdge("livesIn", lagoon, T.id, 16, "since", 2019); + splash.addEdge("livesIn", lagoon, T.id, 17, "since", 2023); + monty.addEdge("livesIn", canopy, T.id, 18, "since", 2021); + echo.addEdge("livesIn", canopy, T.id, 19, "since", 2022); + blaze.addEdge("livesIn", canopy, T.id, 20, "since", 2023); + bitsy.addEdge("livesIn", canopy, T.id, 21, "since", 2024); + tinker.addEdge("livesIn", canopy, T.id, 22, "since", 2020); + titan.addEdge("livesIn", canopy, T.id, 23, "since", 2019); + + // Edges: careFor + drGremlin.addEdge("careFor", atlas, T.id, 24, "specialty", "conservation"); + drGremlin.addEdge("careFor", blaze, T.id, 25, "specialty", "conservation"); + drGremlin.addEdge("careFor", splash, T.id, 26, "specialty", "conservation"); + drGremlin.addEdge("careFor", tinker, T.id, 27, "specialty", "conservation"); + + // Edges: friendsWith + tux.addEdge("friendsWith", atlas, T.id, 28, "since", 2020); + ripple.addEdge("friendsWith", tux, T.id, 29, "since", 2020); + titan.addEdge("friendsWith", blaze, T.id, 30, "since", 2022); + + // Edges: eats (food chain: tinker → monty → bitsy) + tinker.addEdge("eats", monty, T.id, 31); + monty.addEdge("eats", bitsy, T.id, 32); + + // Edges: avoids + echo.addEdge("avoids", bitsy, T.id, 33); + + g.variables().set("name", "the-zoo"); + g.variables().set("creator", "tinkerpop"); + g.variables().set("comment", + "this graph showcases multi-label vertex support and diverse " + + "property types introduced in tinkerpop 4.x"); + } + private static TinkerGraph getTinkerGraphWithCurrentNumberManager() { return TinkerGraph.open(getConfigurationWithCurrentNumberManager()); } diff --git a/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/TinkerWorld.java b/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/TinkerWorld.java index d28caa3c580..4377303292f 100644 --- a/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/TinkerWorld.java +++ b/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/TinkerWorld.java @@ -96,6 +96,7 @@ public static class TinkerGraphWorld extends TinkerWorld { private static final AbstractTinkerGraph crew = registerTestServices(TinkerFactory.createTheCrew()); private static final AbstractTinkerGraph sink = registerTestServices(TinkerFactory.createKitchenSink()); private static final AbstractTinkerGraph grateful = registerTestServices(TinkerFactory.createGratefulDead()); + private static final AbstractTinkerGraph zoo = registerTestServices(TinkerFactory.createTheZoo()); @Override public GraphTraversalSource getGraphTraversalSource(final LoadGraphWith.GraphData graphData) { @@ -111,6 +112,8 @@ else if (graphData == LoadGraphWith.GraphData.SINK) return sink.traversal(); else if (graphData == LoadGraphWith.GraphData.GRATEFUL) return grateful.traversal(); + else if (graphData == LoadGraphWith.GraphData.ZOO) + return zoo.traversal(); else throw new UnsupportedOperationException("GraphData not supported: " + graphData.name()); } @@ -163,6 +166,7 @@ public static class TinkerTransactionGraphWorld extends TinkerWorld { private static final AbstractTinkerGraph crew; private static final AbstractTinkerGraph sink; private static final AbstractTinkerGraph grateful; + private static final AbstractTinkerGraph zoo; static { modern = TinkerTransactionGraph.open(getNumberIdManagerConfiguration()); @@ -189,6 +193,11 @@ public static class TinkerTransactionGraphWorld extends TinkerWorld { TinkerFactory.generateGratefulDead(grateful); grateful.tx().commit(); registerTestServices(grateful); + + zoo = TinkerTransactionGraph.open(getMultiLabelConfiguration()); + TinkerFactory.generateTheZoo(zoo); + zoo.tx().commit(); + registerTestServices(zoo); } @Override @@ -205,6 +214,8 @@ else if (graphData == LoadGraphWith.GraphData.SINK) return sink.traversal(); else if (graphData == LoadGraphWith.GraphData.GRATEFUL) return grateful.traversal(); + else if (graphData == LoadGraphWith.GraphData.ZOO) + return zoo.traversal(); else throw new UnsupportedOperationException("GraphData not supported: " + graphData.name()); } @@ -232,6 +243,7 @@ public static class TinkerShuffleGraphWorld extends TinkerWorld { private static final TinkerGraph crew; private static final TinkerGraph sink; private static final TinkerGraph grateful; + private static final TinkerGraph zoo; static { modern = TinkerShuffleGraph.open(); @@ -249,6 +261,9 @@ public static class TinkerShuffleGraphWorld extends TinkerWorld { grateful = TinkerShuffleGraph.open(); TinkerFactory.generateGratefulDead(grateful); registerTestServices(grateful); + zoo = TinkerShuffleGraph.open(getMultiLabelConfiguration()); + TinkerFactory.generateTheZoo(zoo); + registerTestServices(zoo); } @Override @@ -265,6 +280,8 @@ else if (graphData == LoadGraphWith.GraphData.SINK) return sink.traversal(); else if (graphData == LoadGraphWith.GraphData.GRATEFUL) return grateful.traversal(); + else if (graphData == LoadGraphWith.GraphData.ZOO) + return zoo.traversal(); else throw new UnsupportedOperationException("GraphData not supported: " + graphData.name()); } diff --git a/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/IoDataGenerationTest.java b/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/IoDataGenerationTest.java index dad35c9ca6e..a30caa07172 100644 --- a/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/IoDataGenerationTest.java +++ b/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/IoDataGenerationTest.java @@ -143,6 +143,16 @@ public void shouldWriteSinkGraphAsGryoV3() throws IOException { os.close(); } + /** + * No assertions. Just write out the graph for convenience. + */ + @Test + public void shouldWriteTheZooGraphAsGryoV3() throws IOException { + final OutputStream os = new FileOutputStream(new File(tempPath, "tinkerpop-zoo-v3.kryo")); + GryoWriter.build().mapper(GryoMapper.build().version(GryoVersion.V3_0).create()).create().writeGraph(os, TinkerFactory.createTheZoo()); + os.close(); + } + /** * No assertions. Just write out the graph for convenience. */