This is an automated email from the ASF dual-hosted git repository. spmallette pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/tinkerpop.git
commit 97c777c9848f66f6ccd573f7c7d90e8496b17347 Author: Stephen Mallette <[email protected]> AuthorDate: Wed Jul 15 12:12:00 2026 -0400 Preserve multi/zero-label vertices in TinkerGraphComputer result graph Fixes 'Label can not be empty' when an OLAP job (e.g. connectedComponent) persists a new result graph over a TinkerGraph with a multi-label vertex cardinality. The result graph now keeps the source cardinality and copies each vertex's full label set instead of a single label. Assisted-by: Claude Code:claude-opus-4-8 --- .../process/computer/TinkerGraphComputerView.java | 52 +++++++----- .../TinkerGraphComputerMultiLabelTest.java | 96 ++++++++++++++++++++++ 2 files changed, 127 insertions(+), 21 deletions(-) diff --git a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/process/computer/TinkerGraphComputerView.java b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/process/computer/TinkerGraphComputerView.java index e3d94b2cf9..41a0b74df2 100644 --- a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/process/computer/TinkerGraphComputerView.java +++ b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/process/computer/TinkerGraphComputerView.java @@ -18,11 +18,12 @@ */ package org.apache.tinkerpop.gremlin.tinkergraph.process.computer; +import org.apache.commons.configuration2.BaseConfiguration; +import org.apache.commons.configuration2.Configuration; import org.apache.tinkerpop.gremlin.process.computer.GraphComputer; import org.apache.tinkerpop.gremlin.process.computer.GraphFilter; import org.apache.tinkerpop.gremlin.process.computer.VertexComputeKey; import org.apache.tinkerpop.gremlin.process.traversal.step.map.PropertiesStep; -import org.apache.tinkerpop.gremlin.process.traversal.util.TraversalUtil; import org.apache.tinkerpop.gremlin.structure.Edge; import org.apache.tinkerpop.gremlin.structure.Element; import org.apache.tinkerpop.gremlin.structure.Graph; @@ -171,16 +172,8 @@ public final class TinkerGraphComputerView { this.addPropertiesToOriginalGraph(); return this.graph; } else { - final TinkerGraph newGraph = TinkerGraph.open(); - this.graph.vertices().forEachRemaining(vertex -> { - final Vertex newVertex = newGraph.addVertex(T.id, vertex.id(), T.label, vertex.label()); - vertex.properties().forEachRemaining(vertexProperty -> { - final VertexProperty<?> newVertexProperty = newVertex.property(VertexProperty.Cardinality.list, vertexProperty.key(), vertexProperty.value(), T.id, vertexProperty.id()); - vertexProperty.properties().forEachRemaining(property -> { - newVertexProperty.property(property.key(), property.value()); - }); - }); - }); + final TinkerGraph newGraph = createResultGraph(); + this.graph.vertices().forEachRemaining(vertex -> copyVertexToResultGraph(newGraph, vertex)); return newGraph; } } else { // Persist.EDGES @@ -188,16 +181,8 @@ public final class TinkerGraphComputerView { this.addPropertiesToOriginalGraph(); return this.graph; } else { - final TinkerGraph newGraph = TinkerGraph.open(); - this.graph.vertices().forEachRemaining(vertex -> { - final Vertex newVertex = newGraph.addVertex(T.id, vertex.id(), T.label, vertex.label()); - vertex.properties().forEachRemaining(vertexProperty -> { - final VertexProperty<?> newVertexProperty = newVertex.property(VertexProperty.Cardinality.list, vertexProperty.key(), vertexProperty.value(), T.id, vertexProperty.id()); - vertexProperty.properties().forEachRemaining(property -> { - newVertexProperty.property(property.key(), property.value()); - }); - }); - }); + final TinkerGraph newGraph = createResultGraph(); + this.graph.vertices().forEachRemaining(vertex -> copyVertexToResultGraph(newGraph, vertex)); this.graph.edges().forEachRemaining(edge -> { final Vertex outVertex = newGraph.vertices(edge.outVertex().id()).next(); final Vertex inVertex = newGraph.vertices(edge.inVertex().id()).next(); @@ -209,6 +194,31 @@ public final class TinkerGraphComputerView { } } + /** + * Opens a new result graph that preserves the source graph's vertex {@code LabelCardinality} so that + * multi-label and zero-label vertices can be persisted without violating the default cardinality. + */ + private TinkerGraph createResultGraph() { + final Configuration conf = new BaseConfiguration(); + conf.setProperty(AbstractTinkerGraph.GREMLIN_TINKERGRAPH_VERTEX_LABEL_CARDINALITY, + this.graph.features().vertex().getLabelCardinality().name()); + return TinkerGraph.open(conf); + } + + /** + * Copies a vertex and its (meta-)properties into the result graph, carrying over all of its labels so + * that vertices with zero or multiple labels round-trip correctly. + */ + private static void copyVertexToResultGraph(final TinkerGraph newGraph, final Vertex vertex) { + final Vertex newVertex = newGraph.addVertex(T.id, vertex.id(), T.label, vertex.labels()); + vertex.properties().forEachRemaining(vertexProperty -> { + final VertexProperty<?> newVertexProperty = newVertex.property(VertexProperty.Cardinality.list, vertexProperty.key(), vertexProperty.value(), T.id, vertexProperty.id()); + vertexProperty.properties().forEachRemaining(property -> { + newVertexProperty.property(property.key(), property.value()); + }); + }); + } + private void addPropertiesToOriginalGraph() { TinkerHelper.dropGraphComputerView(this.graph); this.computeProperties.forEach((element, properties) -> { diff --git a/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/process/computer/TinkerGraphComputerMultiLabelTest.java b/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/process/computer/TinkerGraphComputerMultiLabelTest.java new file mode 100644 index 0000000000..dd835089c2 --- /dev/null +++ b/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/process/computer/TinkerGraphComputerMultiLabelTest.java @@ -0,0 +1,96 @@ +/* + * 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.tinkergraph.process.computer; + +import org.apache.commons.configuration2.BaseConfiguration; +import org.apache.commons.configuration2.Configuration; +import org.apache.tinkerpop.gremlin.process.computer.ComputerResult; +import org.apache.tinkerpop.gremlin.process.computer.GraphComputer; +import org.apache.tinkerpop.gremlin.process.computer.clustering.connected.ConnectedComponentVertexProgram; +import org.apache.tinkerpop.gremlin.structure.Graph; +import org.apache.tinkerpop.gremlin.structure.T; +import org.apache.tinkerpop.gremlin.structure.Vertex; +import org.apache.tinkerpop.gremlin.tinkergraph.structure.AbstractTinkerGraph; +import org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerGraph; +import org.junit.Test; + +import java.util.Arrays; +import java.util.Collections; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.hasSize; +import static org.junit.Assert.assertEquals; + +/** + * Verifies that a {@code ResultGraph.NEW} produced by {@link org.apache.tinkerpop.gremlin.tinkergraph.process.computer.TinkerGraphComputer} + * preserves multi-label and zero-label vertices rather than collapsing them to a single label (which previously + * threw {@code "Label can not be empty"} for zero-label vertices). + */ +public class TinkerGraphComputerMultiLabelTest { + + private static TinkerGraph openMultiLabelGraph() { + final Configuration config = new BaseConfiguration(); + config.setProperty(AbstractTinkerGraph.GREMLIN_TINKERGRAPH_VERTEX_LABEL_CARDINALITY, "ZERO_OR_MORE"); + return TinkerGraph.open(config); + } + + private static Graph computeNewGraph(final TinkerGraph graph, final GraphComputer.Persist persist) throws Exception { + final ComputerResult result = graph.compute() + .program(ConnectedComponentVertexProgram.build().create(graph)) + .result(GraphComputer.ResultGraph.NEW) + .persist(persist) + .submit().get(); + return result.graph(); + } + + @Test + public void shouldPreserveVertexLabelsInNewResultGraphWithPersistEdges() throws Exception { + try (final TinkerGraph graph = openMultiLabelGraph()) { + final Vertex a = graph.addVertex(T.id, "a", T.label, Arrays.asList("person", "employee")); + final Vertex b = graph.addVertex(T.id, "b", T.label, "person"); + graph.addVertex(T.id, "c", T.label, Collections.emptyList()); + a.addEdge("knows", b); + + final Graph result = computeNewGraph(graph, GraphComputer.Persist.EDGES); + + assertThat(result.vertices("a").next().labels(), containsInAnyOrder("person", "employee")); + assertThat(result.vertices("b").next().labels(), containsInAnyOrder("person")); + assertThat(result.vertices("c").next().labels(), hasSize(0)); + assertEquals(1L, result.traversal().E().count().next().longValue()); + } + } + + @Test + public void shouldPreserveVertexLabelsInNewResultGraphWithPersistVertexProperties() throws Exception { + try (final TinkerGraph graph = openMultiLabelGraph()) { + final Vertex a = graph.addVertex(T.id, "a", T.label, Arrays.asList("person", "employee")); + final Vertex b = graph.addVertex(T.id, "b", T.label, "person"); + graph.addVertex(T.id, "c", T.label, Collections.emptyList()); + a.addEdge("knows", b); + + final Graph result = computeNewGraph(graph, GraphComputer.Persist.VERTEX_PROPERTIES); + + assertThat(result.vertices("a").next().labels(), containsInAnyOrder("person", "employee")); + assertThat(result.vertices("b").next().labels(), containsInAnyOrder("person")); + assertThat(result.vertices("c").next().labels(), hasSize(0)); + assertEquals(0L, result.traversal().E().count().next().longValue()); + } + } +}
