Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions docker/gremlin-server/gremlin-server-integration.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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}]}}
Expand All @@ -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: {
Expand Down
28 changes: 28 additions & 0 deletions docs/src/reference/the-graph.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,34 @@ link:https://tinkerpop.apache.org/docs/x.y.z/dev/provider/#_label_cardinality[pr
details and the <<label-step,label()>>, <<labels-step,labels()>>, <<addlabel-step,addLabel()>>, and
<<droplabel-step,dropLabel()>> 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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

doesn't this sentence read strangely?

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.

We don't "configure" graphs for multi/meta-properties. A graph "supports" those things. You might say that you "configure" label cardinality for "TinkerGraph" but not every graph will have a "configuration" for that (i.e. some graphs will just support it or not) so you'd have to be specific.

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]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't help wondering if this graph carries too much to display nicely for folks to follow as an image. I won't let it block the PR but maybe we just need do showcase the multilabels for the graph visually or a core subgraph...


"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

Expand Down
104 changes: 34 additions & 70 deletions docs/src/reference/the-traversal.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -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
<<labels-step,`labels()`>> 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 <<labels-step,`labels()`>> step over `by(label)` for graphs that support multiple
labels per vertex.

*Additional References*

Expand Down Expand Up @@ -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
<<has-step,`hasLabel()`>> 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 <<has-step,`hasLabel()`>> 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()`:
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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*

Expand Down Expand Up @@ -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 <<labels-step,`labels()`>> step instead to reliably retrieve every label a vertex carries.

*Additional References*
Expand All @@ -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()
----
Expand Down Expand Up @@ -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.
Expand Down
3 changes: 3 additions & 0 deletions docs/src/tutorials/the-gremlin-console/index.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -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]
----
Expand Down
Binary file added docs/static/images/the-zoo-graph.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -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}.
Expand Down Expand Up @@ -304,6 +306,12 @@ public static Vertex createVertex(final Attachable<Vertex> 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<String> labels = baseVertex.labels();
if (labels.size() > 1) {
final List<String> 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<String, List<VertexProperty<Object>>> propertyMap = new HashMap<>();
baseVertex.properties().forEachRemaining(vp -> propertyMap.computeIfAbsent(vp.key(), k -> new ArrayList<>()).add(vp));
for (Map.Entry<String, List<VertexProperty<Object>>> entry : propertyMap.entrySet()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -69,7 +71,12 @@ public static void readStarGraphEdges(final Function<Attachable<Edge>, Edge> edg
*/
public static StarGraph readStarGraphVertex(final Map<String, Object> 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<String> labels = labelData instanceof List ? (List<String>) 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<String, List<Map<String, Object>>> properties = (Map<String, List<Map<String, Object>>>) vertexData.get(GraphSONTokens.PROPERTIES);
for (Map.Entry<String, List<Map<String, Object>>> property : properties.entrySet()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()) {
Expand Down Expand Up @@ -162,4 +162,16 @@ private static <S> List<S> sort(final List<S> 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<String> labels) throws IOException {
jsonGenerator.writeFieldName(GraphSONTokens.LABEL);
jsonGenerator.writeStartArray();
for (final String label : labels) {
jsonGenerator.writeString(label);
}
jsonGenerator.writeEndArray();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -49,18 +51,25 @@ public class StarGraphSerializer implements SerializerShim<StarGraph> {

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;
}

@Override
public <O extends OutputShim> void write(final KryoShim<?, O> 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);
Expand All @@ -83,10 +92,19 @@ public <O extends OutputShim> void write(final KryoShim<?, O> kryo, final O outp
@Override
public <I extends InputShim> StarGraph read(final KryoShim<I, ?> kryo, final I input, final Class<StarGraph> 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<String> labels = (List<String>) 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)) {
Expand Down
Loading
Loading