From 5b9255f9da72b96a25d53c92147116d5f61aee10 Mon Sep 17 00:00:00 2001 From: xiazcy Date: Sat, 27 Jun 2026 12:07:41 -0700 Subject: [PATCH 1/8] Add an example zoo graph --- .../tinkergraph/structure/TinkerFactory.java | 186 ++++++++++++++++++ 1 file changed, 186 insertions(+) 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()); } From 9268f23399b9dce2e25012ed0a9bf53ec2524f11 Mon Sep 17 00:00:00 2001 From: Cole Greer Date: Mon, 6 Jul 2026 13:08:05 -0700 Subject: [PATCH 2/8] Wire up the zoo graph and convert read-only multi-label scenarios Adds ZOO to the GraphData enum and wires it into every test harness that already supports the other named toy graphs: TinkerWorld (embedded, transaction, and shuffle graph variants), RemoteWorld/TinkerFactoryDataLoader for server-backed tests, both gremlin-server-integration.yaml configs, and the Python, JavaScript, Go, and .NET GLV Gherkin runners. The zoo graph uses tinkergraph-multilabel.properties since it requires ZERO_OR_MORE vertex label cardinality. Updated the two graph-manager tests whose assertions hardcoded the total configured graph count, which shifted by one with the new entry. Converts scenarios in ElementMap.feature, ValueMap.feature, and Labels.feature from a bespoke empty-graph-plus-initializer fixture to the zoo graph wherever the traversal under test is read-only and the zoo's pre-populated multi-label vertices/edges (tux, lagoon, canopy, atlas, etc.) can express the same shape deterministically. Scenarios that mutate the graph, need a vertex with zero or exactly-one label, or depend on a provider's implicit default label-rendering mode (which is a per-World configuration switch independent of graph data) remain on the empty graph. Assisted-by: Claude Code:claude-sonnet-5 --- .../gremlin-server-integration.yaml | 4 + .../Gherkin/ScenarioData.cs | 2 +- gremlin-go/driver/cucumber/cucumberWorld.go | 2 +- .../test/cucumber/gremlin.js | 16 +- .../gremlin-javascript/test/cucumber/world.js | 2 +- .../src/main/python/tests/feature/terrain.py | 4 +- .../server/util/TinkerFactoryDataLoader.java | 5 +- .../gremlin/driver/remote/RemoteWorld.java | 3 + .../server/util/CheckedGraphManagerTest.java | 2 +- .../server/util/DefaultGraphManagerTest.java | 13 +- .../server/gremlin-server-integration.yaml | 4 + .../tinkerpop/gremlin/LoadGraphWith.java | 20 +- .../language/translator/translations.json | 256 ++++++------------ .../test/features/map/ElementMap.feature | 34 +-- .../gremlin/test/features/map/Label.feature | 16 +- .../gremlin/test/features/map/Labels.feature | 16 +- .../test/features/map/ValueMap.feature | 22 +- .../gremlin/tinkergraph/TinkerWorld.java | 17 ++ 18 files changed, 182 insertions(+), 256 deletions(-) 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/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..5418dff87a8 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,13 @@ 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}). + */ + ZOO; private static final List featuresRequiredByClassic = new ArrayList() {{ add(FeatureRequirement.Factory.create(FEATURE_STRING_VALUES, VertexPropertyFeatures.class)); @@ -132,6 +138,14 @@ public enum GraphData { add(FeatureRequirement.Factory.create(FEATURE_INTEGER_VALUES, EdgePropertyFeatures.class)); }}; + private static final List featuresRequiredByZoo = new ArrayList() {{ + add(FeatureRequirement.Factory.create(FEATURE_STRING_VALUES, VertexPropertyFeatures.class)); + add(FeatureRequirement.Factory.create(FEATURE_INTEGER_VALUES, VertexPropertyFeatures.class)); + add(FeatureRequirement.Factory.create(FEATURE_DOUBLE_VALUES, VertexPropertyFeatures.class)); + add(FeatureRequirement.Factory.create(FEATURE_BOOLEAN_VALUES, VertexPropertyFeatures.class)); + add(FeatureRequirement.Factory.create(FEATURE_MULTI_PROPERTIES, Graph.Features.VertexFeatures.class)); + }}; + public String location() { switch (this) { case CLASSIC: @@ -146,6 +160,8 @@ public String location() { return RESOURCE_PATH_PREFIX + "tinkerpop-sink-v3.kryo"; case AIR_ROUTES: return RESOURCE_PATH_PREFIX + "air-routes-v3.kryo"; + case ZOO: + return RESOURCE_PATH_PREFIX + "tinkerpop-zoo-v3.kryo"; } throw new RuntimeException("No file for this GraphData type"); @@ -165,6 +181,8 @@ public List featuresRequired() { return featuresRequiredBySink; case AIR_ROUTES: return featuresRequiredByAirRoutes; + case ZOO: + return featuresRequiredByZoo; } throw new RuntimeException("No features for this GraphData type"); 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/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/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()); } From b2a2f43098150b1232658d55deb3051979d6dab4 Mon Sep 17 00:00:00 2001 From: Cole Greer Date: Tue, 7 Jul 2026 19:44:15 -0700 Subject: [PATCH 3/8] Convert read-only multi-label doc examples to use the zoo graph Converts 7 of the 14 executable gremlin-groovy examples added for multi-label support in the-traversal.asciidoc to use [gremlin-groovy,theZoo] instead of hand-building a single-vertex TinkerGraph inline: - By Step: group().by(label) vs by(labels().fold()) - Choose Step: choose(T.label) non-determinism - ElementMap: with("multilabel") example - Has Step: chained hasLabel() AND/OR semantics - Label Step - Labels Step - ValueMap: with("multilabel") example Each converted example is read-only after the block's own setup, so there is no risk of mutation leaking across examples: the docs preprocessor opens a fresh TinkerFactory.createTheZoo() graph for every [gremlin-groovy,theZoo] block. Kept the AddLabel, DropLabel, and mergeV examples on hand-built empty graphs since they depend on precise, narrative-driven mutation sequences or a LabelCardinality.ONE case the zoo graph can't represent. Assisted-by: Kiro:claude-sonnet-5 --- docs/src/reference/the-traversal.asciidoc | 104 +++++++--------------- 1 file changed, 34 insertions(+), 70 deletions(-) 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. From 1df9ea8221d97c998453d80b637c4f77dc438dd3 Mon Sep 17 00:00:00 2001 From: Cole Greer Date: Wed, 8 Jul 2026 10:01:47 -0700 Subject: [PATCH 4/8] Fix order-sensitive set comparison in .NET Gherkin DeepEqual DeepEqualityExtensions.DeepEqual compared IEnumerable values with an order-sensitive SequenceEqual, including HashSet values nested inside a result Dictionary (e.g. the t[label] entry of an elementMap()/valueMap() result under with("multilabel")). Single-element label sets never surfaced this, but the zoo graph's multi-label vertices (e.g. tux with 4 labels) exposed it: the expected HashSet built from the s[...] Gherkin token and the actual HashSet deserialized by GraphBinary4's SetSerializer can differ in iteration order despite containing identical elements, causing SequenceEqual to spuriously fail. Adds an ISet-aware branch that uses SetEquals before falling back to SequenceEqual for ordered collections. Fixes 3 failing scenarios under the GraphBinary4MessageSerializer variant of RunGherkinBasedTests: g_withXmultilabelX_V_elementMap_multilabel, g_withXmultilabelX_V_valueMap_withXtokensX_multilabel, and g_withXmultilabelX_E_elementMap. Also includes the regenerated Gremlin.cs reflecting the zoo-graph feature file conversions already committed to this branch. Assisted-by: Kiro:claude-sonnet-5 --- .../Gherkin/DeepEqualityExtensions.cs | 7 +++++++ .../Gherkin/Gremlin.cs | 16 ++++++++-------- 2 files changed, 15 insertions(+), 8 deletions(-) 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)}}, From 255e9613277723310ea25b76888890c907f4f5ba Mon Sep 17 00:00:00 2001 From: Cole Greer Date: Wed, 8 Jul 2026 11:21:42 -0700 Subject: [PATCH 5/8] Document the zoo toy graph with a diagram Introduces the zoo graph to readers in the reference docs, following the same TIP + diagram + example pattern used for "The Crew" (createTheZoo(), data/tinkerpop-zoo*, LabelCardinality requirements), and adds it to the Gremlin Console tutorial's toy graph catalogue alongside classic, modern, and the crew. The diagram (the-zoo-graph.png) is generated with Graphviz's fdp layout engine, styled to match the existing modern/crew diagrams: filled circles labeled with vertex id + labels, rounded property callouts, and curved edges labeled with the edge label plus its id. It reflects all 13 vertices and 20 edges from TinkerFactory.generateTheZoo(). Assisted-by: Kiro:claude-sonnet-5 --- docs/src/reference/the-graph.asciidoc | 28 ++++++++++++++++++ .../the-gremlin-console/index.asciidoc | 3 ++ docs/static/images/the-zoo-graph.png | Bin 0 -> 169045 bytes 3 files changed, 31 insertions(+) create mode 100644 docs/static/images/the-zoo-graph.png 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/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 0000000000000000000000000000000000000000..7f09a860e7adc41501d6ddc250dcbada62449aec GIT binary patch literal 169045 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⩔6Ccv86tVH=OEsj`qH=J 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 Date: Wed, 8 Jul 2026 11:21:56 -0700 Subject: [PATCH 6/8] Disable ZOO location()/featuresRequired() wiring Removes the tinkerpop-zoo-v3.kryo file mapping and feature-requirement list for LoadGraphWith.GraphData.ZOO. Neither the current Gryo nor GraphSON writers preserve multi-label vertices when serializing a whole graph (both StarGraphGraphSONSerializer and the Gryo VertexSerializer write a single label string, not the full label set), so a generated data file for the zoo graph would silently drop the multi-label vertices it exists to showcase. ZOO remains in the GraphData enum since it's still used successfully via the Gherkin 'Given the zoo graph' step, which builds the graph directly in memory through TinkerFactory.createTheZoo()/generateTheZoo() rather than deserializing a file, and is unaffected by this gap. Calling location() or featuresRequired() on ZOO now falls through to the existing 'No file/features for this GraphData type' exceptions rather than pointing at a nonexistent resource. Assisted-by: Kiro:claude-sonnet-5 --- .../apache/tinkerpop/gremlin/LoadGraphWith.java | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) 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 5418dff87a8..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 @@ -99,6 +99,11 @@ public enum GraphData { /** * 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; @@ -138,14 +143,6 @@ public enum GraphData { add(FeatureRequirement.Factory.create(FEATURE_INTEGER_VALUES, EdgePropertyFeatures.class)); }}; - private static final List featuresRequiredByZoo = new ArrayList() {{ - add(FeatureRequirement.Factory.create(FEATURE_STRING_VALUES, VertexPropertyFeatures.class)); - add(FeatureRequirement.Factory.create(FEATURE_INTEGER_VALUES, VertexPropertyFeatures.class)); - add(FeatureRequirement.Factory.create(FEATURE_DOUBLE_VALUES, VertexPropertyFeatures.class)); - add(FeatureRequirement.Factory.create(FEATURE_BOOLEAN_VALUES, VertexPropertyFeatures.class)); - add(FeatureRequirement.Factory.create(FEATURE_MULTI_PROPERTIES, Graph.Features.VertexFeatures.class)); - }}; - public String location() { switch (this) { case CLASSIC: @@ -160,8 +157,6 @@ public String location() { return RESOURCE_PATH_PREFIX + "tinkerpop-sink-v3.kryo"; case AIR_ROUTES: return RESOURCE_PATH_PREFIX + "air-routes-v3.kryo"; - case ZOO: - return RESOURCE_PATH_PREFIX + "tinkerpop-zoo-v3.kryo"; } throw new RuntimeException("No file for this GraphData type"); @@ -181,8 +176,6 @@ public List featuresRequired() { return featuresRequiredBySink; case AIR_ROUTES: return featuresRequiredByAirRoutes; - case ZOO: - return featuresRequiredByZoo; } throw new RuntimeException("No features for this GraphData type"); From bb9b31d65394c31cada867b9413a200f9db3127f Mon Sep 17 00:00:00 2001 From: Cole Greer Date: Wed, 8 Jul 2026 15:36:46 -0700 Subject: [PATCH 7/8] Wire up the zoo graph for Hadoop via multi-label GraphSON V4 StarGraphGraphSONSerializerV4/StarGraphGraphSONDeserializer only ever wrote/read a vertex's label as a single string, even under GraphSON V4, so a multi-label vertex silently lost all but one of its labels when serialized through the StarGraph codec. Fixes the V4 serializer to write the full label set as an array (matching the convention already used by the element-level V4 serializers for the driver protocol) and the shared deserializer to read either a single string (V1/V2/V3) or an array (V4) back into a StarVertex's label set. Adds a tinkerpop-zoo-v4.json GraphSON V4 fixture (generated from TinkerFactory.createTheZoo()) and registers it in TestFiles so HadoopGraphWorld can load the zoo graph for @MultiLabel Gherkin scenarios, using GraphSONInputFormat/V4_0 instead of Gryo since Gryo's StarGraph codec (StarGraphSerializer) has the same single-label limitation and is being left as-is for a later follow-up. Also fixes HadoopElement, which delegated label() to its wrapped element but never overrode labels(), silently falling back to Element's single-label default (Collections.singleton(label())) regardless of how many labels the underlying vertex actually had. Spark's equivalent SparkGraphWorld wiring is left for a follow-up; it shares the same HadoopGraph/StarGraph machinery as Hadoop but hasn't been updated yet. Assisted-by: Kiro:claude-sonnet-5 --- .../star/StarGraphGraphSONDeserializer.java | 9 +- .../star/StarGraphGraphSONSerializerV4.java | 14 ++- .../StarGraphGraphSONV4MultiLabelTest.java | 86 +++++++++++++++++++ .../tinkerpop/gremlin/features/TestFiles.java | 9 +- .../io/graphson/tinkerpop-zoo-v4.json | 13 +++ .../hadoop/structure/HadoopElement.java | 7 ++ .../HadoopGraphFeatureIntegrateTest.java | 24 +++++- 7 files changed, 157 insertions(+), 5 deletions(-) create mode 100644 gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/structure/util/star/StarGraphGraphSONV4MultiLabelTest.java create mode 100644 gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphson/tinkerpop-zoo-v4.json 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/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-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..b5de9855449 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 @@ -59,7 +59,8 @@ public class TestFiles { "tinkerpop-classic-byteid-typed-v2.json", "tinkerpop-crew-typed-v2.json", "tinkerpop-crew-v3.json", - "tinkerpop-sink-v3.json"); + "tinkerpop-sink-v3.json", + "tinkerpop-zoo-v4.json"); for (final String fileName : graphsonResources) { PATHS.put(fileName, Storage.toPath(TestHelper.generateTempFileFromResource(GraphSONResourceAccess.class, fileName, ""))); @@ -81,6 +82,12 @@ public class TestFiles { } public static String getInputLocation(final LoadGraphWith.GraphData graphData, final boolean useGraphSON) { + // the zoo graph is multi-label and only GraphSON V4 preserves the full label set for a vertex - Gryo (and + // GraphSON V1/V2/V3) can only carry a single label, so the zoo graph is always read from its V4 GraphSON + // resource regardless of which format the caller requested. + if (graphData.equals(LoadGraphWith.GraphData.ZOO)) + return PATHS.get("tinkerpop-zoo-v4.json"); + final String type = useGraphSON ? "-v3.json" : "-v3.kryo"; if (graphData.equals(LoadGraphWith.GraphData.GRATEFUL)) diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphson/tinkerpop-zoo-v4.json b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphson/tinkerpop-zoo-v4.json new file mode 100644 index 00000000000..b86a868375a --- /dev/null +++ b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphson/tinkerpop-zoo-v4.json @@ -0,0 +1,13 @@ +{"id":{"@type":"g:Int32","@value":1},"label":["bird","endangered","animal","aquatic"],"inE":{"friendsWith":[{"id":{"@type":"g:Int32","@value":29},"outV":{"@type":"g:Int32","@value":3},"properties":{"since":{"@type":"g:Int32","@value":2020}}}]},"outE":{"livesIn":[{"id":{"@type":"g:Int32","@value":14},"inV":{"@type":"g:Int32","@value":11},"properties":{"since":{"@type":"g:Int32","@value":2020}}}],"friendsWith":[{"id":{"@type":"g:Int32","@value":28},"inV":{"@type":"g:Int32","@value":2},"properties":{"since":{"@type":"g:Int32","@value":2020}}}]},"properties":{"species":[{"id":{"@type":"g:Int64","@value":1},"value":"african penguin"}],"name":[{"id":{"@type":"g:Int64","@value":0},"value":"tux"}],"weight":[{"id":{"@type":"g:Int64","@value":2},"value":{"@type":"g:Double","@value":3.7}}],"captiveBorn":[{"id":{"@type":"g:Int64","@value":4},"value":true}],"diet":[{"id":{"@type":"g:Int64","@value":5},"value":"fish"},{"id":{"@type":"g:Int64","@value":6},"value":"krill"},{"id":{"@type":"g:Int64","@value":7},"value":"squid"}],"age":[{"id":{"@type":"g:Int64","@value":3},"value":{"@type":"g:Int32","@value":5}}]}} +{"id":{"@type":"g:Int32","@value":2},"label":["endangered","animal","reptile","aquatic"],"inE":{"friendsWith":[{"id":{"@type":"g:Int32","@value":28},"outV":{"@type":"g:Int32","@value":1},"properties":{"since":{"@type":"g:Int32","@value":2020}}}],"careFor":[{"id":{"@type":"g:Int32","@value":24},"outV":{"@type":"g:Int32","@value":13},"properties":{"specialty":"conservation"}}]},"outE":{"livesIn":[{"id":{"@type":"g:Int32","@value":15},"inV":{"@type":"g:Int32","@value":11},"properties":{"since":{"@type":"g:Int32","@value":2018}}}]},"properties":{"species":[{"id":{"@type":"g:Int64","@value":9},"value":"green sea turtle"}],"name":[{"id":{"@type":"g:Int64","@value":8},"value":"atlas"}],"weight":[{"id":{"@type":"g:Int64","@value":10},"value":{"@type":"g:Double","@value":180.5}}],"captiveBorn":[{"id":{"@type":"g:Int64","@value":12},"value":false}],"diet":[{"id":{"@type":"g:Int64","@value":13},"value":"seagrass"},{"id":{"@type":"g:Int64","@value":14},"value":"algae"}],"age":[{"id":{"@type":"g:Int64","@value":11},"value":{"@type":"g:Int32","@value":30}}]}} +{"id":{"@type":"g:Int32","@value":3},"label":["mammal","animal","aquatic"],"outE":{"livesIn":[{"id":{"@type":"g:Int32","@value":16},"inV":{"@type":"g:Int32","@value":11},"properties":{"since":{"@type":"g:Int32","@value":2019}}}],"friendsWith":[{"id":{"@type":"g:Int32","@value":29},"inV":{"@type":"g:Int32","@value":1},"properties":{"since":{"@type":"g:Int32","@value":2020}}}]},"properties":{"species":[{"id":{"@type":"g:Int64","@value":16},"value":"bottlenose dolphin"}],"name":[{"id":{"@type":"g:Int64","@value":15},"value":"ripple"}],"weight":[{"id":{"@type":"g:Int64","@value":17},"value":{"@type":"g:Double","@value":220.0}}],"captiveBorn":[{"id":{"@type":"g:Int64","@value":19},"value":true}],"diet":[{"id":{"@type":"g:Int64","@value":20},"value":"fish"},{"id":{"@type":"g:Int64","@value":21},"value":"squid"}],"age":[{"id":{"@type":"g:Int64","@value":18},"value":{"@type":"g:Int32","@value":12}}]}} +{"id":{"@type":"g:Int32","@value":4},"label":["nocturnal","animal","reptile"],"inE":{"eats":[{"id":{"@type":"g:Int32","@value":31},"outV":{"@type":"g:Int32","@value":10}}]},"outE":{"livesIn":[{"id":{"@type":"g:Int32","@value":18},"inV":{"@type":"g:Int32","@value":12},"properties":{"since":{"@type":"g:Int32","@value":2021}}}],"eats":[{"id":{"@type":"g:Int32","@value":32},"inV":{"@type":"g:Int32","@value":8}}]},"properties":{"venomous":[{"id":{"@type":"g:Int64","@value":27},"value":false}],"species":[{"id":{"@type":"g:Int64","@value":23},"value":"ball python"}],"name":[{"id":{"@type":"g:Int64","@value":22},"value":"monty"}],"weight":[{"id":{"@type":"g:Int64","@value":24},"value":{"@type":"g:Double","@value":1.8}}],"captiveBorn":[{"id":{"@type":"g:Int64","@value":26},"value":true}],"diet":[{"id":{"@type":"g:Int64","@value":28},"value":"mice"},{"id":{"@type":"g:Int64","@value":29},"value":"rats"}],"age":[{"id":{"@type":"g:Int64","@value":25},"value":{"@type":"g:Int32","@value":8}}]}} +{"id":{"@type":"g:Int32","@value":5},"label":["nocturnal","mammal","flying","animal"],"outE":{"livesIn":[{"id":{"@type":"g:Int32","@value":19},"inV":{"@type":"g:Int32","@value":12},"properties":{"since":{"@type":"g:Int32","@value":2022}}}],"avoids":[{"id":{"@type":"g:Int32","@value":33},"inV":{"@type":"g:Int32","@value":8}}]},"properties":{"species":[{"id":{"@type":"g:Int64","@value":31},"value":"fruit bat"}],"name":[{"id":{"@type":"g:Int64","@value":30},"value":"echo"}],"weight":[{"id":{"@type":"g:Int64","@value":32},"value":{"@type":"g:Double","@value":0.3}}],"captiveBorn":[{"id":{"@type":"g:Int64","@value":34},"value":true}],"diet":[{"id":{"@type":"g:Int64","@value":35},"value":"fruit"},{"id":{"@type":"g:Int64","@value":36},"value":"nectar"}],"age":[{"id":{"@type":"g:Int64","@value":33},"value":{"@type":"g:Int32","@value":3}}]}} +{"id":{"@type":"g:Int32","@value":6},"label":["nocturnal","mammal","endangered","animal"],"inE":{"friendsWith":[{"id":{"@type":"g:Int32","@value":30},"outV":{"@type":"g:Int32","@value":7},"properties":{"since":{"@type":"g:Int32","@value":2022}}}],"careFor":[{"id":{"@type":"g:Int32","@value":25},"outV":{"@type":"g:Int32","@value":13},"properties":{"specialty":"conservation"}}]},"outE":{"livesIn":[{"id":{"@type":"g:Int32","@value":20},"inV":{"@type":"g:Int32","@value":12},"properties":{"since":{"@type":"g:Int32","@value":2023}}}]},"properties":{"species":[{"id":{"@type":"g:Int64","@value":38},"value":"red panda"}],"name":[{"id":{"@type":"g:Int64","@value":37},"value":"blaze"}],"weight":[{"id":{"@type":"g:Int64","@value":39},"value":{"@type":"g:Double","@value":5.4}}],"captiveBorn":[{"id":{"@type":"g:Int64","@value":41},"value":false}],"diet":[{"id":{"@type":"g:Int64","@value":42},"value":"bamboo"},{"id":{"@type":"g:Int64","@value":43},"value":"fruit"},{"id":{"@type":"g:Int64","@value":44},"value":"insects"}],"age":[{"id":{"@type":"g:Int64","@value":40},"value":{"@type":"g:Int32","@value":4}}]}} +{"id":{"@type":"g:Int32","@value":7},"label":["mammal","endangered","animal"],"outE":{"livesIn":[{"id":{"@type":"g:Int32","@value":23},"inV":{"@type":"g:Int32","@value":12},"properties":{"since":{"@type":"g:Int32","@value":2019}}}],"friendsWith":[{"id":{"@type":"g:Int32","@value":30},"inV":{"@type":"g:Int32","@value":6},"properties":{"since":{"@type":"g:Int32","@value":2022}}}]},"properties":{"species":[{"id":{"@type":"g:Int64","@value":46},"value":"african elephant"}],"name":[{"id":{"@type":"g:Int64","@value":45},"value":"titan"}],"weight":[{"id":{"@type":"g:Int64","@value":47},"value":{"@type":"g:Double","@value":4000.0}}],"captiveBorn":[{"id":{"@type":"g:Int64","@value":49},"value":false}],"diet":[{"id":{"@type":"g:Int64","@value":50},"value":"grass"},{"id":{"@type":"g:Int64","@value":51},"value":"leaves"},{"id":{"@type":"g:Int64","@value":52},"value":"bark"}],"age":[{"id":{"@type":"g:Int64","@value":48},"value":{"@type":"g:Int32","@value":15}}]}} +{"id":{"@type":"g:Int32","@value":8},"label":["nocturnal","mammal","animal"],"inE":{"eats":[{"id":{"@type":"g:Int32","@value":32},"outV":{"@type":"g:Int32","@value":4}}],"avoids":[{"id":{"@type":"g:Int32","@value":33},"outV":{"@type":"g:Int32","@value":5}}]},"outE":{"livesIn":[{"id":{"@type":"g:Int32","@value":21},"inV":{"@type":"g:Int32","@value":12},"properties":{"since":{"@type":"g:Int32","@value":2024}}}]},"properties":{"species":[{"id":{"@type":"g:Int64","@value":54},"value":"harvest mouse"}],"name":[{"id":{"@type":"g:Int64","@value":53},"value":"bitsy"}],"weight":[{"id":{"@type":"g:Int64","@value":55},"value":{"@type":"g:Double","@value":0.006}}],"captiveBorn":[{"id":{"@type":"g:Int64","@value":57},"value":true}],"diet":[{"id":{"@type":"g:Int64","@value":58},"value":"seeds"},{"id":{"@type":"g:Int64","@value":59},"value":"insects"}],"age":[{"id":{"@type":"g:Int64","@value":56},"value":{"@type":"g:Int32","@value":1}}]}} +{"id":{"@type":"g:Int32","@value":9},"label":["nocturnal","mammal","endangered","animal","aquatic"],"inE":{"careFor":[{"id":{"@type":"g:Int32","@value":26},"outV":{"@type":"g:Int32","@value":13},"properties":{"specialty":"conservation"}}]},"outE":{"livesIn":[{"id":{"@type":"g:Int32","@value":17},"inV":{"@type":"g:Int32","@value":11},"properties":{"since":{"@type":"g:Int32","@value":2023}}}]},"properties":{"species":[{"id":{"@type":"g:Int64","@value":61},"value":"fishing cat"}],"name":[{"id":{"@type":"g:Int64","@value":60},"value":"splash"}],"weight":[{"id":{"@type":"g:Int64","@value":62},"value":{"@type":"g:Double","@value":8.2}}],"captiveBorn":[{"id":{"@type":"g:Int64","@value":64},"value":false}],"diet":[{"id":{"@type":"g:Int64","@value":65},"value":"fish"},{"id":{"@type":"g:Int64","@value":66},"value":"frogs"},{"id":{"@type":"g:Int64","@value":67},"value":"crustaceans"}],"age":[{"id":{"@type":"g:Int64","@value":63},"value":{"@type":"g:Int32","@value":2}}]}} +{"id":{"@type":"g:Int32","@value":10},"label":["nocturnal","mammal","endangered","animal"],"inE":{"careFor":[{"id":{"@type":"g:Int32","@value":27},"outV":{"@type":"g:Int32","@value":13},"properties":{"specialty":"conservation"}}]},"outE":{"livesIn":[{"id":{"@type":"g:Int32","@value":22},"inV":{"@type":"g:Int32","@value":12},"properties":{"since":{"@type":"g:Int32","@value":2020}}}],"eats":[{"id":{"@type":"g:Int32","@value":31},"inV":{"@type":"g:Int32","@value":4}}]},"properties":{"species":[{"id":{"@type":"g:Int64","@value":69},"value":"bengal tiger"}],"name":[{"id":{"@type":"g:Int64","@value":68},"value":"tinker"}],"weight":[{"id":{"@type":"g:Int64","@value":70},"value":{"@type":"g:Double","@value":220.0}}],"captiveBorn":[{"id":{"@type":"g:Int64","@value":72},"value":false}],"diet":[{"id":{"@type":"g:Int64","@value":73},"value":"deer"},{"id":{"@type":"g:Int64","@value":74},"value":"boar"},{"id":{"@type":"g:Int64","@value":75},"value":"snakes"}],"age":[{"id":{"@type":"g:Int64","@value":71},"value":{"@type":"g:Int32","@value":5}}]}} +{"id":{"@type":"g:Int32","@value":11},"label":["habitat","aquatic"],"inE":{"livesIn":[{"id":{"@type":"g:Int32","@value":16},"outV":{"@type":"g:Int32","@value":3},"properties":{"since":{"@type":"g:Int32","@value":2019}}},{"id":{"@type":"g:Int32","@value":17},"outV":{"@type":"g:Int32","@value":9},"properties":{"since":{"@type":"g:Int32","@value":2023}}},{"id":{"@type":"g:Int32","@value":14},"outV":{"@type":"g:Int32","@value":1},"properties":{"since":{"@type":"g:Int32","@value":2020}}},{"id":{"@type":"g:Int32","@value":15},"outV":{"@type":"g:Int32","@value":2},"properties":{"since":{"@type":"g:Int32","@value":2018}}}]},"properties":{"openAir":[{"id":{"@type":"g:Int64","@value":79},"value":true}],"biome":[{"id":{"@type":"g:Int64","@value":77},"value":"marine"}],"name":[{"id":{"@type":"g:Int64","@value":76},"value":"lagoon"}],"capacity":[{"id":{"@type":"g:Int64","@value":78},"value":{"@type":"g:Int32","@value":8}}]}} +{"id":{"@type":"g:Int32","@value":12},"label":["habitat"],"inE":{"livesIn":[{"id":{"@type":"g:Int32","@value":18},"outV":{"@type":"g:Int32","@value":4},"properties":{"since":{"@type":"g:Int32","@value":2021}}},{"id":{"@type":"g:Int32","@value":19},"outV":{"@type":"g:Int32","@value":5},"properties":{"since":{"@type":"g:Int32","@value":2022}}},{"id":{"@type":"g:Int32","@value":20},"outV":{"@type":"g:Int32","@value":6},"properties":{"since":{"@type":"g:Int32","@value":2023}}},{"id":{"@type":"g:Int32","@value":21},"outV":{"@type":"g:Int32","@value":8},"properties":{"since":{"@type":"g:Int32","@value":2024}}},{"id":{"@type":"g:Int32","@value":22},"outV":{"@type":"g:Int32","@value":10},"properties":{"since":{"@type":"g:Int32","@value":2020}}},{"id":{"@type":"g:Int32","@value":23},"outV":{"@type":"g:Int32","@value":7},"properties":{"since":{"@type":"g:Int32","@value":2019}}}]},"properties":{"openAir":[{"id":{"@type":"g:Int64","@value":83},"value":false}],"biome":[{"id":{"@type":"g:Int64","@value":81},"value":"tropical"}],"name":[{"id":{"@type":"g:Int64","@value":80},"value":"canopy"}],"capacity":[{"id":{"@type":"g:Int64","@value":82},"value":{"@type":"g:Int32","@value":8}}]}} +{"id":{"@type":"g:Int32","@value":13},"label":["person","veterinarian","keeper"],"outE":{"careFor":[{"id":{"@type":"g:Int32","@value":24},"inV":{"@type":"g:Int32","@value":2},"properties":{"specialty":"conservation"}},{"id":{"@type":"g:Int32","@value":25},"inV":{"@type":"g:Int32","@value":6},"properties":{"specialty":"conservation"}},{"id":{"@type":"g:Int32","@value":26},"inV":{"@type":"g:Int32","@value":9},"properties":{"specialty":"conservation"}},{"id":{"@type":"g:Int32","@value":27},"inV":{"@type":"g:Int32","@value":10},"properties":{"specialty":"conservation"}}]},"properties":{"specialties":[{"id":{"@type":"g:Int64","@value":86},"value":"conservation"},{"id":{"@type":"g:Int64","@value":87},"value":"surgery"},{"id":{"@type":"g:Int64","@value":88},"value":"nutrition"}],"name":[{"id":{"@type":"g:Int64","@value":84},"value":"dr_gremlin"}],"since":[{"id":{"@type":"g:Int64","@value":85},"value":{"@type":"g:Int32","@value":2015}}]}} 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..91320b3a205 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 @@ -33,21 +33,21 @@ import org.apache.tinkerpop.gremlin.features.TestFiles; import org.apache.tinkerpop.gremlin.features.World; import org.apache.tinkerpop.gremlin.hadoop.structure.HadoopGraph; +import org.apache.tinkerpop.gremlin.hadoop.structure.io.graphson.GraphSONInputFormat; import org.apache.tinkerpop.gremlin.hadoop.structure.io.gryo.GryoInputFormat; import org.apache.tinkerpop.gremlin.hadoop.structure.io.gryo.GryoOutputFormat; import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource; import org.apache.tinkerpop.gremlin.structure.Graph; +import org.apache.tinkerpop.gremlin.structure.io.graphson.GraphSONVersion; import org.javatuples.Pair; import org.junit.AssumptionViolatedException; 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 +91,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(getZooConfiguration())); static { readIntoGraph(modern, GraphData.MODERN); @@ -98,6 +99,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 +116,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()); } @@ -144,6 +148,22 @@ private static Map getBaseConfiguration(final GraphData graphDat }}; } + /** + * The zoo graph is multi-label and only GraphSON V4 preserves the full label set for a vertex - Gryo (and + * GraphSON V1/V2/V3) can only carry a single label - so, unlike the other reference graphs, it is read via + * {@link GraphSONInputFormat} configured for {@link GraphSONVersion#V4_0} rather than Gryo. + */ + private static Map getZooConfiguration() { + return new HashMap() {{ + put(Graph.GRAPH, HadoopGraph.class.getName()); + put(Constants.GREMLIN_HADOOP_GRAPH_READER, GraphSONInputFormat.class.getCanonicalName()); + put(Constants.GREMLIN_HADOOP_GRAPHSON_VERSION, GraphSONVersion.V4_0.name()); + put(Constants.GREMLIN_HADOOP_GRAPH_WRITER, GryoOutputFormat.class.getCanonicalName()); + put(Constants.GREMLIN_HADOOP_OUTPUT_LOCATION, getWorkingDirectory()); + put(Constants.GREMLIN_HADOOP_JARS_IN_DISTRIBUTED_CACHE, false); + }}; + } + @Override public boolean useParametersLiterally() { return false; From ba4de85272b5898e792dbb7613b1b400689427dd Mon Sep 17 00:00:00 2001 From: Cole Greer Date: Thu, 9 Jul 2026 09:08:10 -0700 Subject: [PATCH 8/8] Support multi-label vertices in Gryo; wire up zoo graph for Hadoop and Spark StarGraphSerializer's Kryo format only ever wrote/read a vertex's label as a single string, so a multi-label vertex silently lost all but one label when serialized. Introduces a VERSION_2 format byte that writes the full label set as a list; VERSION_1 (single label) data written by prior versions of this serializer remains readable via a version-branched reader, so existing .kryo fixtures are unaffected. Attachable.Method.createVertex (used by GryoReader.readGraph and other providers' attach-to-host-graph logic) had the same gap - it read a multi-label source vertex correctly but only ever passed a single label to the new vertex. Fixed to call addLabel() for any labels beyond the first. Adds a tinkerpop-zoo-v3.kryo fixture (generated via the existing IoDataGenerationTest convention) and registers it in TestFiles, replacing the GraphSON V4-only workaround from the prior commit. HadoopGraphWorld and SparkGraphWorld now both load the zoo graph the same way as every other reference graph, via plain Gryo. GraphSON V1/V2/V3 remain single-label-only, so requesting the zoo graph via useGraphSON=true now fails with a clear error rather than silently truncating labels. The StarGraphGraphSONSerializerV4/StarGraphGraphSONDeserializer fix from the prior commit is unchanged and still exercised by its own test; it remains a legitimate improvement to the V4 GraphSON wire format independent of how Hadoop/Spark load their reference graphs. Verified against HadoopGraphFeatureIntegrateTest (2129 scenarios), HadoopGraphProcessStandardIntegrateTest (206 tests), and SparkGraphFeatureIntegrateTest (1448 scenarios) - all passing with zero failures/errors. Assisted-by: Kiro:claude-sonnet-5 --- .../gremlin/structure/util/Attachable.java | 8 ++ .../util/star/StarGraphSerializer.java | 26 +++- .../StarGraphSerializerMultiLabelTest.java | 116 ++++++++++++++++++ .../tinkerpop/gremlin/features/TestFiles.java | 20 +-- .../io/graphson/tinkerpop-zoo-v4.json | 13 -- .../structure/io/gryo/tinkerpop-zoo-v3.kryo | Bin 0 -> 3253 bytes .../HadoopGraphFeatureIntegrateTest.java | 20 +-- .../spark/SparkGraphFeatureIntegrateTest.java | 4 + .../structure/IoDataGenerationTest.java | 10 ++ 9 files changed, 171 insertions(+), 46 deletions(-) create mode 100644 gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/structure/util/star/StarGraphSerializerMultiLabelTest.java delete mode 100644 gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphson/tinkerpop-zoo-v4.json create mode 100644 gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/gryo/tinkerpop-zoo-v3.kryo 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/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/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-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 b5de9855449..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, ""))); @@ -59,8 +60,7 @@ public class TestFiles { "tinkerpop-classic-byteid-typed-v2.json", "tinkerpop-crew-typed-v2.json", "tinkerpop-crew-v3.json", - "tinkerpop-sink-v3.json", - "tinkerpop-zoo-v4.json"); + "tinkerpop-sink-v3.json"); for (final String fileName : graphsonResources) { PATHS.put(fileName, Storage.toPath(TestHelper.generateTempFileFromResource(GraphSONResourceAccess.class, fileName, ""))); @@ -82,12 +82,6 @@ public class TestFiles { } public static String getInputLocation(final LoadGraphWith.GraphData graphData, final boolean useGraphSON) { - // the zoo graph is multi-label and only GraphSON V4 preserves the full label set for a vertex - Gryo (and - // GraphSON V1/V2/V3) can only carry a single label, so the zoo graph is always read from its V4 GraphSON - // resource regardless of which format the caller requested. - if (graphData.equals(LoadGraphWith.GraphData.ZOO)) - return PATHS.get("tinkerpop-zoo-v4.json"); - final String type = useGraphSON ? "-v3.json" : "-v3.kryo"; if (graphData.equals(LoadGraphWith.GraphData.GRATEFUL)) @@ -100,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/structure/io/graphson/tinkerpop-zoo-v4.json b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphson/tinkerpop-zoo-v4.json deleted file mode 100644 index b86a868375a..00000000000 --- a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphson/tinkerpop-zoo-v4.json +++ /dev/null @@ -1,13 +0,0 @@ -{"id":{"@type":"g:Int32","@value":1},"label":["bird","endangered","animal","aquatic"],"inE":{"friendsWith":[{"id":{"@type":"g:Int32","@value":29},"outV":{"@type":"g:Int32","@value":3},"properties":{"since":{"@type":"g:Int32","@value":2020}}}]},"outE":{"livesIn":[{"id":{"@type":"g:Int32","@value":14},"inV":{"@type":"g:Int32","@value":11},"properties":{"since":{"@type":"g:Int32","@value":2020}}}],"friendsWith":[{"id":{"@type":"g:Int32","@value":28},"inV":{"@type":"g:Int32","@value":2},"properties":{"since":{"@type":"g:Int32","@value":2020}}}]},"properties":{"species":[{"id":{"@type":"g:Int64","@value":1},"value":"african penguin"}],"name":[{"id":{"@type":"g:Int64","@value":0},"value":"tux"}],"weight":[{"id":{"@type":"g:Int64","@value":2},"value":{"@type":"g:Double","@value":3.7}}],"captiveBorn":[{"id":{"@type":"g:Int64","@value":4},"value":true}],"diet":[{"id":{"@type":"g:Int64","@value":5},"value":"fish"},{"id":{"@type":"g:Int64","@value":6},"value":"krill"},{"id":{"@type":"g:Int64","@value":7},"value":"squid"}],"age":[{"id":{"@type":"g:Int64","@value":3},"value":{"@type":"g:Int32","@value":5}}]}} -{"id":{"@type":"g:Int32","@value":2},"label":["endangered","animal","reptile","aquatic"],"inE":{"friendsWith":[{"id":{"@type":"g:Int32","@value":28},"outV":{"@type":"g:Int32","@value":1},"properties":{"since":{"@type":"g:Int32","@value":2020}}}],"careFor":[{"id":{"@type":"g:Int32","@value":24},"outV":{"@type":"g:Int32","@value":13},"properties":{"specialty":"conservation"}}]},"outE":{"livesIn":[{"id":{"@type":"g:Int32","@value":15},"inV":{"@type":"g:Int32","@value":11},"properties":{"since":{"@type":"g:Int32","@value":2018}}}]},"properties":{"species":[{"id":{"@type":"g:Int64","@value":9},"value":"green sea turtle"}],"name":[{"id":{"@type":"g:Int64","@value":8},"value":"atlas"}],"weight":[{"id":{"@type":"g:Int64","@value":10},"value":{"@type":"g:Double","@value":180.5}}],"captiveBorn":[{"id":{"@type":"g:Int64","@value":12},"value":false}],"diet":[{"id":{"@type":"g:Int64","@value":13},"value":"seagrass"},{"id":{"@type":"g:Int64","@value":14},"value":"algae"}],"age":[{"id":{"@type":"g:Int64","@value":11},"value":{"@type":"g:Int32","@value":30}}]}} -{"id":{"@type":"g:Int32","@value":3},"label":["mammal","animal","aquatic"],"outE":{"livesIn":[{"id":{"@type":"g:Int32","@value":16},"inV":{"@type":"g:Int32","@value":11},"properties":{"since":{"@type":"g:Int32","@value":2019}}}],"friendsWith":[{"id":{"@type":"g:Int32","@value":29},"inV":{"@type":"g:Int32","@value":1},"properties":{"since":{"@type":"g:Int32","@value":2020}}}]},"properties":{"species":[{"id":{"@type":"g:Int64","@value":16},"value":"bottlenose dolphin"}],"name":[{"id":{"@type":"g:Int64","@value":15},"value":"ripple"}],"weight":[{"id":{"@type":"g:Int64","@value":17},"value":{"@type":"g:Double","@value":220.0}}],"captiveBorn":[{"id":{"@type":"g:Int64","@value":19},"value":true}],"diet":[{"id":{"@type":"g:Int64","@value":20},"value":"fish"},{"id":{"@type":"g:Int64","@value":21},"value":"squid"}],"age":[{"id":{"@type":"g:Int64","@value":18},"value":{"@type":"g:Int32","@value":12}}]}} -{"id":{"@type":"g:Int32","@value":4},"label":["nocturnal","animal","reptile"],"inE":{"eats":[{"id":{"@type":"g:Int32","@value":31},"outV":{"@type":"g:Int32","@value":10}}]},"outE":{"livesIn":[{"id":{"@type":"g:Int32","@value":18},"inV":{"@type":"g:Int32","@value":12},"properties":{"since":{"@type":"g:Int32","@value":2021}}}],"eats":[{"id":{"@type":"g:Int32","@value":32},"inV":{"@type":"g:Int32","@value":8}}]},"properties":{"venomous":[{"id":{"@type":"g:Int64","@value":27},"value":false}],"species":[{"id":{"@type":"g:Int64","@value":23},"value":"ball python"}],"name":[{"id":{"@type":"g:Int64","@value":22},"value":"monty"}],"weight":[{"id":{"@type":"g:Int64","@value":24},"value":{"@type":"g:Double","@value":1.8}}],"captiveBorn":[{"id":{"@type":"g:Int64","@value":26},"value":true}],"diet":[{"id":{"@type":"g:Int64","@value":28},"value":"mice"},{"id":{"@type":"g:Int64","@value":29},"value":"rats"}],"age":[{"id":{"@type":"g:Int64","@value":25},"value":{"@type":"g:Int32","@value":8}}]}} -{"id":{"@type":"g:Int32","@value":5},"label":["nocturnal","mammal","flying","animal"],"outE":{"livesIn":[{"id":{"@type":"g:Int32","@value":19},"inV":{"@type":"g:Int32","@value":12},"properties":{"since":{"@type":"g:Int32","@value":2022}}}],"avoids":[{"id":{"@type":"g:Int32","@value":33},"inV":{"@type":"g:Int32","@value":8}}]},"properties":{"species":[{"id":{"@type":"g:Int64","@value":31},"value":"fruit bat"}],"name":[{"id":{"@type":"g:Int64","@value":30},"value":"echo"}],"weight":[{"id":{"@type":"g:Int64","@value":32},"value":{"@type":"g:Double","@value":0.3}}],"captiveBorn":[{"id":{"@type":"g:Int64","@value":34},"value":true}],"diet":[{"id":{"@type":"g:Int64","@value":35},"value":"fruit"},{"id":{"@type":"g:Int64","@value":36},"value":"nectar"}],"age":[{"id":{"@type":"g:Int64","@value":33},"value":{"@type":"g:Int32","@value":3}}]}} -{"id":{"@type":"g:Int32","@value":6},"label":["nocturnal","mammal","endangered","animal"],"inE":{"friendsWith":[{"id":{"@type":"g:Int32","@value":30},"outV":{"@type":"g:Int32","@value":7},"properties":{"since":{"@type":"g:Int32","@value":2022}}}],"careFor":[{"id":{"@type":"g:Int32","@value":25},"outV":{"@type":"g:Int32","@value":13},"properties":{"specialty":"conservation"}}]},"outE":{"livesIn":[{"id":{"@type":"g:Int32","@value":20},"inV":{"@type":"g:Int32","@value":12},"properties":{"since":{"@type":"g:Int32","@value":2023}}}]},"properties":{"species":[{"id":{"@type":"g:Int64","@value":38},"value":"red panda"}],"name":[{"id":{"@type":"g:Int64","@value":37},"value":"blaze"}],"weight":[{"id":{"@type":"g:Int64","@value":39},"value":{"@type":"g:Double","@value":5.4}}],"captiveBorn":[{"id":{"@type":"g:Int64","@value":41},"value":false}],"diet":[{"id":{"@type":"g:Int64","@value":42},"value":"bamboo"},{"id":{"@type":"g:Int64","@value":43},"value":"fruit"},{"id":{"@type":"g:Int64","@value":44},"value":"insects"}],"age":[{"id":{"@type":"g:Int64","@value":40},"value":{"@type":"g:Int32","@value":4}}]}} -{"id":{"@type":"g:Int32","@value":7},"label":["mammal","endangered","animal"],"outE":{"livesIn":[{"id":{"@type":"g:Int32","@value":23},"inV":{"@type":"g:Int32","@value":12},"properties":{"since":{"@type":"g:Int32","@value":2019}}}],"friendsWith":[{"id":{"@type":"g:Int32","@value":30},"inV":{"@type":"g:Int32","@value":6},"properties":{"since":{"@type":"g:Int32","@value":2022}}}]},"properties":{"species":[{"id":{"@type":"g:Int64","@value":46},"value":"african elephant"}],"name":[{"id":{"@type":"g:Int64","@value":45},"value":"titan"}],"weight":[{"id":{"@type":"g:Int64","@value":47},"value":{"@type":"g:Double","@value":4000.0}}],"captiveBorn":[{"id":{"@type":"g:Int64","@value":49},"value":false}],"diet":[{"id":{"@type":"g:Int64","@value":50},"value":"grass"},{"id":{"@type":"g:Int64","@value":51},"value":"leaves"},{"id":{"@type":"g:Int64","@value":52},"value":"bark"}],"age":[{"id":{"@type":"g:Int64","@value":48},"value":{"@type":"g:Int32","@value":15}}]}} -{"id":{"@type":"g:Int32","@value":8},"label":["nocturnal","mammal","animal"],"inE":{"eats":[{"id":{"@type":"g:Int32","@value":32},"outV":{"@type":"g:Int32","@value":4}}],"avoids":[{"id":{"@type":"g:Int32","@value":33},"outV":{"@type":"g:Int32","@value":5}}]},"outE":{"livesIn":[{"id":{"@type":"g:Int32","@value":21},"inV":{"@type":"g:Int32","@value":12},"properties":{"since":{"@type":"g:Int32","@value":2024}}}]},"properties":{"species":[{"id":{"@type":"g:Int64","@value":54},"value":"harvest mouse"}],"name":[{"id":{"@type":"g:Int64","@value":53},"value":"bitsy"}],"weight":[{"id":{"@type":"g:Int64","@value":55},"value":{"@type":"g:Double","@value":0.006}}],"captiveBorn":[{"id":{"@type":"g:Int64","@value":57},"value":true}],"diet":[{"id":{"@type":"g:Int64","@value":58},"value":"seeds"},{"id":{"@type":"g:Int64","@value":59},"value":"insects"}],"age":[{"id":{"@type":"g:Int64","@value":56},"value":{"@type":"g:Int32","@value":1}}]}} -{"id":{"@type":"g:Int32","@value":9},"label":["nocturnal","mammal","endangered","animal","aquatic"],"inE":{"careFor":[{"id":{"@type":"g:Int32","@value":26},"outV":{"@type":"g:Int32","@value":13},"properties":{"specialty":"conservation"}}]},"outE":{"livesIn":[{"id":{"@type":"g:Int32","@value":17},"inV":{"@type":"g:Int32","@value":11},"properties":{"since":{"@type":"g:Int32","@value":2023}}}]},"properties":{"species":[{"id":{"@type":"g:Int64","@value":61},"value":"fishing cat"}],"name":[{"id":{"@type":"g:Int64","@value":60},"value":"splash"}],"weight":[{"id":{"@type":"g:Int64","@value":62},"value":{"@type":"g:Double","@value":8.2}}],"captiveBorn":[{"id":{"@type":"g:Int64","@value":64},"value":false}],"diet":[{"id":{"@type":"g:Int64","@value":65},"value":"fish"},{"id":{"@type":"g:Int64","@value":66},"value":"frogs"},{"id":{"@type":"g:Int64","@value":67},"value":"crustaceans"}],"age":[{"id":{"@type":"g:Int64","@value":63},"value":{"@type":"g:Int32","@value":2}}]}} -{"id":{"@type":"g:Int32","@value":10},"label":["nocturnal","mammal","endangered","animal"],"inE":{"careFor":[{"id":{"@type":"g:Int32","@value":27},"outV":{"@type":"g:Int32","@value":13},"properties":{"specialty":"conservation"}}]},"outE":{"livesIn":[{"id":{"@type":"g:Int32","@value":22},"inV":{"@type":"g:Int32","@value":12},"properties":{"since":{"@type":"g:Int32","@value":2020}}}],"eats":[{"id":{"@type":"g:Int32","@value":31},"inV":{"@type":"g:Int32","@value":4}}]},"properties":{"species":[{"id":{"@type":"g:Int64","@value":69},"value":"bengal tiger"}],"name":[{"id":{"@type":"g:Int64","@value":68},"value":"tinker"}],"weight":[{"id":{"@type":"g:Int64","@value":70},"value":{"@type":"g:Double","@value":220.0}}],"captiveBorn":[{"id":{"@type":"g:Int64","@value":72},"value":false}],"diet":[{"id":{"@type":"g:Int64","@value":73},"value":"deer"},{"id":{"@type":"g:Int64","@value":74},"value":"boar"},{"id":{"@type":"g:Int64","@value":75},"value":"snakes"}],"age":[{"id":{"@type":"g:Int64","@value":71},"value":{"@type":"g:Int32","@value":5}}]}} -{"id":{"@type":"g:Int32","@value":11},"label":["habitat","aquatic"],"inE":{"livesIn":[{"id":{"@type":"g:Int32","@value":16},"outV":{"@type":"g:Int32","@value":3},"properties":{"since":{"@type":"g:Int32","@value":2019}}},{"id":{"@type":"g:Int32","@value":17},"outV":{"@type":"g:Int32","@value":9},"properties":{"since":{"@type":"g:Int32","@value":2023}}},{"id":{"@type":"g:Int32","@value":14},"outV":{"@type":"g:Int32","@value":1},"properties":{"since":{"@type":"g:Int32","@value":2020}}},{"id":{"@type":"g:Int32","@value":15},"outV":{"@type":"g:Int32","@value":2},"properties":{"since":{"@type":"g:Int32","@value":2018}}}]},"properties":{"openAir":[{"id":{"@type":"g:Int64","@value":79},"value":true}],"biome":[{"id":{"@type":"g:Int64","@value":77},"value":"marine"}],"name":[{"id":{"@type":"g:Int64","@value":76},"value":"lagoon"}],"capacity":[{"id":{"@type":"g:Int64","@value":78},"value":{"@type":"g:Int32","@value":8}}]}} -{"id":{"@type":"g:Int32","@value":12},"label":["habitat"],"inE":{"livesIn":[{"id":{"@type":"g:Int32","@value":18},"outV":{"@type":"g:Int32","@value":4},"properties":{"since":{"@type":"g:Int32","@value":2021}}},{"id":{"@type":"g:Int32","@value":19},"outV":{"@type":"g:Int32","@value":5},"properties":{"since":{"@type":"g:Int32","@value":2022}}},{"id":{"@type":"g:Int32","@value":20},"outV":{"@type":"g:Int32","@value":6},"properties":{"since":{"@type":"g:Int32","@value":2023}}},{"id":{"@type":"g:Int32","@value":21},"outV":{"@type":"g:Int32","@value":8},"properties":{"since":{"@type":"g:Int32","@value":2024}}},{"id":{"@type":"g:Int32","@value":22},"outV":{"@type":"g:Int32","@value":10},"properties":{"since":{"@type":"g:Int32","@value":2020}}},{"id":{"@type":"g:Int32","@value":23},"outV":{"@type":"g:Int32","@value":7},"properties":{"since":{"@type":"g:Int32","@value":2019}}}]},"properties":{"openAir":[{"id":{"@type":"g:Int64","@value":83},"value":false}],"biome":[{"id":{"@type":"g:Int64","@value":81},"value":"tropical"}],"name":[{"id":{"@type":"g:Int64","@value":80},"value":"canopy"}],"capacity":[{"id":{"@type":"g:Int64","@value":82},"value":{"@type":"g:Int32","@value":8}}]}} -{"id":{"@type":"g:Int32","@value":13},"label":["person","veterinarian","keeper"],"outE":{"careFor":[{"id":{"@type":"g:Int32","@value":24},"inV":{"@type":"g:Int32","@value":2},"properties":{"specialty":"conservation"}},{"id":{"@type":"g:Int32","@value":25},"inV":{"@type":"g:Int32","@value":6},"properties":{"specialty":"conservation"}},{"id":{"@type":"g:Int32","@value":26},"inV":{"@type":"g:Int32","@value":9},"properties":{"specialty":"conservation"}},{"id":{"@type":"g:Int32","@value":27},"inV":{"@type":"g:Int32","@value":10},"properties":{"specialty":"conservation"}}]},"properties":{"specialties":[{"id":{"@type":"g:Int64","@value":86},"value":"conservation"},{"id":{"@type":"g:Int64","@value":87},"value":"surgery"},{"id":{"@type":"g:Int64","@value":88},"value":"nutrition"}],"name":[{"id":{"@type":"g:Int64","@value":84},"value":"dr_gremlin"}],"since":[{"id":{"@type":"g:Int64","@value":85},"value":{"@type":"g:Int32","@value":2015}}]}} 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 0000000000000000000000000000000000000000..57af38c990d43c2dfd1a2bea6d5c33c31f6ebc75 GIT binary patch literal 3253 zcma)9-)kII7~T8L?y@taF_t!jQj#H*5JCtgh7dw%*CItlL_}0X)SKCx-P_F0OlR)K zwn(WWB1NRA6kA0^L_|dW1H>mOB2^0h1!98*OA5PrFyez}e(dbbcH4Dca%Zx0_nz~e z^L=OQ(k)~!_BzvOWglZ23#4Oh(@#^h8s3(|)#2`{K*Wj~TbKS#jR~j5ox1P^*4&Xz zej9&Jx44pjFk&0N#0P@sq`F14N@Z4Lw%iiIV>?8LX?!2i3KiK13p`;-(IK7iHeRy0 zV|v1=x8x3UcypWdg2q(qZ}zH?^~NsgMWed!N_bsm7WWkPJmLB~q*qFe)uh-h>xRZQ zWN=F#(b%Rh?c4e&P@Qhcn~c}7Uzv;t7}Hk|UVPB|=*s2#AS#Z=RD@^aw%XH}*4iFzPL*%))l-Wt!;f3JmDyDT^DI=DAbt@)a!t1gtE>+?G|>!J}aF+A)5$# zuC6mQ!T6RdYhd66z-F9&3?=YeQkm;~H&N~yTw^uvq}Zy)@!uYbt~|C!Qfw#VO?LH1 zaWY{k-j3m&pz;uS8qH?l{W?X9@$Eg#z83zsL?I0$_n_f!!=vCiQLoWx&)$o9d(VQY z3_DMtSfXZn+^PMZu=6qPba=E>(7J|YOcF?^k=(7J+Co|CT4Yo z@Dx>sFh#VGVG0J%a50)S_}v8Lw6Fk#eRPauA-@O(C9PlB!fSA+tBV)V`=#PLsW4tL zs^3-z7<@fNUJKh}*v>XIW(&S0I(j1{!~ZK5$SX8C1U93&4D1^y{8*pWyZVoHk7CD8 zuc9aPG;d^k_*}low#FLV$2e3b{2z21_$eGzs-Wj_$2flGef9mR^Z(?1;HN%NVFMSn zj=t4bUXN%9PNL}yP8eY+z8!3Y0_|s|fnDJ4>Z6`%5J;!mQeO7c`Kfj;>dOu3)J-dG z3uge%vw3hUt^aFA)!c}#JRuiYDu6c-Vxsx~X1MU(dPjc?E6Z;Mid%v^Nh3T@#i0bv zX9z+NTE0I)==1-RAV?$SWf{C=!qGgG(j6R?tV=Ee#5$4@w@oGMqMbB{xA9m>XA=*) zQ}7*jk1=?u$I^RvttLcU{~+Af8FC_s(KHJR-)p8g%yPD}YROwtL;FY&DAzl>Ddv=Q#& OUm*HdjDDEPI`ltD$$Jj~ literal 0 HcmV?d00001 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 91320b3a205..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 @@ -33,12 +33,10 @@ import org.apache.tinkerpop.gremlin.features.TestFiles; import org.apache.tinkerpop.gremlin.features.World; import org.apache.tinkerpop.gremlin.hadoop.structure.HadoopGraph; -import org.apache.tinkerpop.gremlin.hadoop.structure.io.graphson.GraphSONInputFormat; import org.apache.tinkerpop.gremlin.hadoop.structure.io.gryo.GryoInputFormat; import org.apache.tinkerpop.gremlin.hadoop.structure.io.gryo.GryoOutputFormat; import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource; import org.apache.tinkerpop.gremlin.structure.Graph; -import org.apache.tinkerpop.gremlin.structure.io.graphson.GraphSONVersion; import org.javatuples.Pair; import org.junit.AssumptionViolatedException; import org.junit.runner.RunWith; @@ -91,7 +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(getZooConfiguration())); + private static final HadoopGraph zoo = HadoopGraph.open(new MapConfiguration(getBaseConfiguration(GraphData.ZOO))); static { readIntoGraph(modern, GraphData.MODERN); @@ -148,22 +146,6 @@ private static Map getBaseConfiguration(final GraphData graphDat }}; } - /** - * The zoo graph is multi-label and only GraphSON V4 preserves the full label set for a vertex - Gryo (and - * GraphSON V1/V2/V3) can only carry a single label - so, unlike the other reference graphs, it is read via - * {@link GraphSONInputFormat} configured for {@link GraphSONVersion#V4_0} rather than Gryo. - */ - private static Map getZooConfiguration() { - return new HashMap() {{ - put(Graph.GRAPH, HadoopGraph.class.getName()); - put(Constants.GREMLIN_HADOOP_GRAPH_READER, GraphSONInputFormat.class.getCanonicalName()); - put(Constants.GREMLIN_HADOOP_GRAPHSON_VERSION, GraphSONVersion.V4_0.name()); - put(Constants.GREMLIN_HADOOP_GRAPH_WRITER, GryoOutputFormat.class.getCanonicalName()); - put(Constants.GREMLIN_HADOOP_OUTPUT_LOCATION, getWorkingDirectory()); - put(Constants.GREMLIN_HADOOP_JARS_IN_DISTRIBUTED_CACHE, false); - }}; - } - @Override public boolean useParametersLiterally() { return false; 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/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. */