Repository: tinkerpop Updated Branches: refs/heads/master d642c5d35 -> f98b57019
TINKERPOP-1685 Added supportUpsert() feature Added to both VertexFeatures and EdgeFeatures. Gives graph providers a more explicit option for providing this type of capability. Project: http://git-wip-us.apache.org/repos/asf/tinkerpop/repo Commit: http://git-wip-us.apache.org/repos/asf/tinkerpop/commit/384718cd Tree: http://git-wip-us.apache.org/repos/asf/tinkerpop/tree/384718cd Diff: http://git-wip-us.apache.org/repos/asf/tinkerpop/diff/384718cd Branch: refs/heads/master Commit: 384718cdc93330cad8b2b78a3021220be3c9bebc Parents: 31d2063 Author: Stephen Mallette <sp...@genoprime.com> Authored: Wed Apr 11 07:02:43 2018 -0400 Committer: Stephen Mallette <sp...@genoprime.com> Committed: Thu May 10 09:16:19 2018 -0400 ---------------------------------------------------------------------- CHANGELOG.asciidoc | 1 + docs/src/upgrade/release-3.4.x.asciidoc | 19 ++++++ .../tinkerpop/gremlin/structure/Graph.java | 31 ++++++++++ .../tinkerpop/gremlin/structure/GraphTest.java | 35 ++++++++++- .../tinkerpop/gremlin/structure/VertexTest.java | 62 ++++++++++++++++---- 5 files changed, 132 insertions(+), 16 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/384718cd/CHANGELOG.asciidoc ---------------------------------------------------------------------- diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index 0c350c8..b66df78 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -25,6 +25,7 @@ image::https://raw.githubusercontent.com/apache/tinkerpop/master/docs/static/ima This release also includes changes from <<release-3-3-3, 3.3.3>>. +* Added `supportsUpsert()` option to `VertexFeatures` and `EdgeFeatures`. * `min()` and `max()` now support all types implementing `Comparable`. * Change the `toString()` of `Path` to be standardized as other graph elements are. * `hadoop-gremlin` no longer generates a test artifact. http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/384718cd/docs/src/upgrade/release-3.4.x.asciidoc ---------------------------------------------------------------------- diff --git a/docs/src/upgrade/release-3.4.x.asciidoc b/docs/src/upgrade/release-3.4.x.asciidoc index a3de9e7..3c881c3 100644 --- a/docs/src/upgrade/release-3.4.x.asciidoc +++ b/docs/src/upgrade/release-3.4.x.asciidoc @@ -230,3 +230,22 @@ long ago moved to the `Computer` infrastructure as methods for constructing a `T `TraversalEngine` were long ago removed. See: link:https://issues.apache.org/jira/browse/TINKERPOP-1143[TINKERPOP-1143] + +==== Upsert Graph Feature + +Some `Graph` implementations may be able to offer upsert functionality for vertices and edges, which can help improve +usability and performance. To help make it clear to users that a graph operates in this fashion, the `supportsUpsert()` +feature has been added to both `Graph.VertexFeatures` and `Graph.EdgeFeatures`. By default, both of these methods will +return `false`. + +Should a provider wish to support this feature, the behavior of `addV()` and/or `addE()` should change such that when +a vertex or edge with the same identifier is provided, the respective step will insert the new element if that value +is not present or update an existing element if it is found. The method by which the provider "identifies" an element +is completely up to the capabilities of that provider. In the most simple fashion, a graph could simply check the +value of the supplied `T.id`, however graphs that support some form of schema will likely have other methods for +determining whether or not an existing element is present. + +The extent to which TinkerPop tests "upsert" is fairly narrow. Graph providers that choose to support this feature +should consider their own test suites carefully to ensure appropriate coverage. + +See: link:https://issues.apache.org/jira/browse/TINKERPOP-1685[TINKERPOP-1685] http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/384718cd/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/Graph.java ---------------------------------------------------------------------- diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/Graph.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/Graph.java index b142a9a..494ca8c 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/Graph.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/Graph.java @@ -504,6 +504,7 @@ public interface Graph extends AutoCloseable, Host { public static final String FEATURE_DUPLICATE_MULTI_PROPERTIES = "DuplicateMultiProperties"; public static final String FEATURE_META_PROPERTIES = "MetaProperties"; public static final String FEATURE_REMOVE_VERTICES = "RemoveVertices"; + public static final String FEATURE_UPSERT = "Upsert"; /** * Gets the {@link VertexProperty.Cardinality} for a key. By default, this method will return @@ -565,6 +566,20 @@ public interface Graph extends AutoCloseable, Host { } /** + * Determines if the {@code Graph} implementation uses upsert functionality as opposed to insert + * functionality for {@link #addVertex(String)}. This feature gives graph providers some flexibility as + * to how graph mutations are treated. For graph providers, testing of this feature (as far as TinkerPop + * is concerned) only covers graphs that can support user supplied identifiers as there is no other way + * for TinkerPop to know what aspect of a vertex is unique to appropriately apply assertions. Graph + * providers, especially those who support schema features, may have other methods for uniquely identifying + * a vertex and should therefore resort to their own body of tests to validate this feature. + */ + @FeatureDescriptor(name = FEATURE_UPSERT) + public default boolean supportsUpsert() { + return false; + } + + /** * Gets features related to "properties" on a {@link Vertex}. */ public default VertexPropertyFeatures properties() { @@ -579,6 +594,7 @@ public interface Graph extends AutoCloseable, Host { public interface EdgeFeatures extends ElementFeatures { public static final String FEATURE_ADD_EDGES = "AddEdges"; public static final String FEATURE_REMOVE_EDGES = "RemoveEdges"; + public static final String FEATURE_UPSERT = "Upsert"; /** * Determines if an {@link Edge} can be added to a {@code Vertex}. @@ -597,6 +613,21 @@ public interface Graph extends AutoCloseable, Host { } /** + * Determines if the {@code Graph} implementation uses upsert functionality as opposed to insert + * functionality for {@link Vertex#addEdge(String, Vertex, Object...)}. This feature gives graph providers + * some flexibility as to how graph mutations are treated. For graph providers, testing of this feature + * (as far as TinkerPop is concerned) only covers graphs that can support user supplied identifiers as + * there is no other way for TinkerPop to know what aspect of a edge is unique to appropriately apply + * assertions. Graph providers, especially those who support schema features, may have other methods for + * uniquely identifying a edge and should therefore resort to their own body of tests to validate this + * feature. + */ + @FeatureDescriptor(name = FEATURE_UPSERT) + public default boolean supportsUpsert() { + return false; + } + + /** * Gets features related to "properties" on an {@link Edge}. */ public default EdgePropertyFeatures properties() { http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/384718cd/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/structure/GraphTest.java ---------------------------------------------------------------------- diff --git a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/structure/GraphTest.java b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/structure/GraphTest.java index a667e6f..d8f0817 100644 --- a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/structure/GraphTest.java +++ b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/structure/GraphTest.java @@ -49,7 +49,12 @@ import java.util.stream.IntStream; import static org.hamcrest.CoreMatchers.instanceOf; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.not; -import static org.junit.Assert.*; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; /** * @author Stephen Mallette (http://stephen.genoprime.com) @@ -118,11 +123,12 @@ public class GraphTest extends AbstractGremlinTest { @Test @FeatureRequirement(featureClass = Graph.Features.VertexFeatures.class, feature = Graph.Features.VertexFeatures.FEATURE_ADD_VERTICES) @FeatureRequirement(featureClass = Graph.Features.VertexFeatures.class, feature = Graph.Features.VertexFeatures.FEATURE_USER_SUPPLIED_IDS) + @FeatureRequirement(featureClass = Graph.Features.VertexFeatures.class, feature = Graph.Features.VertexFeatures.FEATURE_UPSERT, supported = false) public void shouldHaveExceptionConsistencyWhenAssigningSameIdOnVertex() { final Object o = graphProvider.convertId("1", Vertex.class); - graph.addVertex(T.id, o); + graph.addVertex(T.id, o, "name", "marko"); try { - graph.addVertex(T.id, o); + graph.addVertex(T.id, o, "name", "stephen"); fail("Assigning the same ID to an Element should throw an exception"); } catch (Exception ex) { assertThat(ex, instanceOf(Graph.Exceptions.vertexWithIdAlreadyExists(0).getClass())); @@ -132,6 +138,29 @@ public class GraphTest extends AbstractGremlinTest { @Test @FeatureRequirement(featureClass = Graph.Features.VertexFeatures.class, feature = Graph.Features.VertexFeatures.FEATURE_ADD_VERTICES) @FeatureRequirement(featureClass = Graph.Features.VertexFeatures.class, feature = Graph.Features.VertexFeatures.FEATURE_USER_SUPPLIED_IDS) + @FeatureRequirement(featureClass = Graph.Features.VertexFeatures.class, feature = Graph.Features.VertexFeatures.FEATURE_UPSERT) + public void shouldUpsertWhenAssigningSameIdOnVertex() { + final Object o = graphProvider.convertId("1", Vertex.class); + graph.addVertex(T.id, o, "name", "marko"); + tryCommit(graph, graph -> { + final Vertex v = graph.vertices(o).next(); + assertEquals(o, v.id()); + assertEquals("marko", v.value("name")); + assertVertexEdgeCounts(graph, 1, 0); + }); + + graph.addVertex(T.id, o, "name", "stephen"); + tryCommit(graph, graph -> { + final Vertex v = graph.vertices(o).next(); + assertEquals(o, v.id()); + assertEquals("stephen", v.value("name")); + assertVertexEdgeCounts(graph, 1, 0); + }); + } + + @Test + @FeatureRequirement(featureClass = Graph.Features.VertexFeatures.class, feature = Graph.Features.VertexFeatures.FEATURE_ADD_VERTICES) + @FeatureRequirement(featureClass = Graph.Features.VertexFeatures.class, feature = Graph.Features.VertexFeatures.FEATURE_USER_SUPPLIED_IDS) @FeatureRequirement(featureClass = Graph.Features.VertexFeatures.class, feature = Graph.Features.VertexFeatures.FEATURE_NUMERIC_IDS) public void shouldAddVertexWithUserSuppliedNumericId() { graph.addVertex(T.id, 1000l); http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/384718cd/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/structure/VertexTest.java ---------------------------------------------------------------------- diff --git a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/structure/VertexTest.java b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/structure/VertexTest.java index 8062fd1..6108456 100644 --- a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/structure/VertexTest.java +++ b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/structure/VertexTest.java @@ -37,9 +37,20 @@ import java.util.List; import java.util.Set; import java.util.UUID; -import static org.apache.tinkerpop.gremlin.structure.Graph.Features.PropertyFeatures.*; +import static org.apache.tinkerpop.gremlin.structure.Graph.Features.DataTypeFeatures.FEATURE_BOOLEAN_VALUES; +import static org.apache.tinkerpop.gremlin.structure.Graph.Features.DataTypeFeatures.FEATURE_DOUBLE_VALUES; +import static org.apache.tinkerpop.gremlin.structure.Graph.Features.DataTypeFeatures.FEATURE_FLOAT_VALUES; +import static org.apache.tinkerpop.gremlin.structure.Graph.Features.DataTypeFeatures.FEATURE_INTEGER_VALUES; +import static org.apache.tinkerpop.gremlin.structure.Graph.Features.DataTypeFeatures.FEATURE_LONG_VALUES; +import static org.apache.tinkerpop.gremlin.structure.Graph.Features.DataTypeFeatures.FEATURE_STRING_VALUES; import static org.apache.tinkerpop.gremlin.structure.Graph.Features.VertexFeatures.FEATURE_USER_SUPPLIED_IDS; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + /** * @author Marko A. Rodriguez (http://markorodriguez.com) @@ -56,10 +67,10 @@ public class VertexTest { @FeatureRequirement(featureClass = Graph.Features.EdgeFeatures.class, feature = Graph.Features.EdgeFeatures.FEATURE_NUMERIC_IDS) public void shouldAddEdgeWithUserSuppliedNumericId() { final Vertex v = graph.addVertex(); - v.addEdge("self", v, T.id, 1000l); + v.addEdge("self", v, T.id, 1000L); tryCommit(graph, graph -> { - final Edge e = graph.edges(1000l).next(); - assertEquals(1000l, e.id()); + final Edge e = graph.edges(1000L).next(); + assertEquals(1000L, e.id()); }); } @@ -251,18 +262,43 @@ public class VertexTest { @FeatureRequirement(featureClass = Graph.Features.EdgeFeatures.class, feature = Graph.Features.EdgeFeatures.FEATURE_ADD_EDGES) @FeatureRequirement(featureClass = Graph.Features.VertexFeatures.class, feature = Graph.Features.VertexFeatures.FEATURE_ADD_VERTICES) @FeatureRequirement(featureClass = Graph.Features.EdgeFeatures.class, feature = Graph.Features.EdgeFeatures.FEATURE_USER_SUPPLIED_IDS) + @FeatureRequirement(featureClass = Graph.Features.EdgeFeatures.class, feature = Graph.Features.EdgeFeatures.FEATURE_UPSERT, supported = false) public void shouldHaveExceptionConsistencyWhenAssigningSameIdOnEdge() { final Vertex v = graph.addVertex(); final Object o = graphProvider.convertId("1", Edge.class); - v.addEdge("self", v, T.id, o); + v.addEdge("self", v, T.id, o, "weight", 1); try { - v.addEdge("self", v, T.id, o); + v.addEdge("self", v, T.id, o, "weight", 1); fail("Assigning the same ID to an Element should throw an exception"); } catch (Exception ex) { validateException(Graph.Exceptions.edgeWithIdAlreadyExists(o), ex); } + } + @Test + @FeatureRequirement(featureClass = Graph.Features.EdgeFeatures.class, feature = Graph.Features.EdgeFeatures.FEATURE_ADD_EDGES) + @FeatureRequirement(featureClass = Graph.Features.VertexFeatures.class, feature = Graph.Features.VertexFeatures.FEATURE_ADD_VERTICES) + @FeatureRequirement(featureClass = Graph.Features.EdgeFeatures.class, feature = Graph.Features.EdgeFeatures.FEATURE_USER_SUPPLIED_IDS) + @FeatureRequirement(featureClass = Graph.Features.EdgeFeatures.class, feature = Graph.Features.EdgeFeatures.FEATURE_UPSERT) + public void shouldUpsertWhenAssigningSameIdOnEdge() { + final Vertex v = graph.addVertex(); + final Object o = graphProvider.convertId("1", Edge.class); + v.addEdge("self", v, T.id, o, "weight", 1); + tryCommit(graph, graph -> { + final Edge e = graph.edges(o).next(); + assertEquals(o, e.id()); + assertEquals(1, (int) e.value("weight")); + assertVertexEdgeCounts(graph, 1, 1); + }); + + v.addEdge("self", v, T.id, o, "weight", 2); + tryCommit(graph, graph -> { + final Edge e = graph.edges(o).next(); + assertEquals(o, e.id()); + assertEquals(2, (int) e.value("weight")); + assertVertexEdgeCounts(graph, 1, 1); + }); } @Test @@ -289,8 +325,8 @@ public class VertexTest { @Test @FeatureRequirement(featureClass = Graph.Features.VertexFeatures.class, feature = Graph.Features.VertexFeatures.FEATURE_ADD_VERTICES) @FeatureRequirement(featureClass = Graph.Features.VertexFeatures.class, feature = Graph.Features.VertexFeatures.FEATURE_ADD_PROPERTY) - @FeatureRequirement(featureClass = Graph.Features.VertexPropertyFeatures.class, feature = Graph.Features.VertexPropertyFeatures.FEATURE_STRING_VALUES) - @FeatureRequirement(featureClass = Graph.Features.VertexPropertyFeatures.class, feature = Graph.Features.VertexPropertyFeatures.FEATURE_INTEGER_VALUES) + @FeatureRequirement(featureClass = Graph.Features.VertexPropertyFeatures.class, feature = FEATURE_STRING_VALUES) + @FeatureRequirement(featureClass = Graph.Features.VertexPropertyFeatures.class, feature = FEATURE_INTEGER_VALUES) public void shouldHaveStandardStringRepresentationWithProperties() { final Vertex v = graph.addVertex("name", "marko", "age", 34); assertEquals(StringFactory.vertexString(v), v.toString()); @@ -462,9 +498,9 @@ public class VertexTest { @FeatureRequirement(featureClass = VertexPropertyFeatures.class, feature = FEATURE_LONG_VALUES) public void shouldAutotypeLongProperties() { final Vertex v = graph.addVertex(); - v.property(VertexProperty.Cardinality.single, "long", 1l); + v.property(VertexProperty.Cardinality.single, "long", 1L); final Long best = v.value("long"); - assertEquals(best, Long.valueOf(1l)); + assertEquals(best, Long.valueOf(1L)); } @Test @@ -516,9 +552,9 @@ public class VertexTest { @FeatureRequirement(featureClass = Graph.Features.VertexFeatures.class, feature = Graph.Features.VertexFeatures.FEATURE_ADD_VERTICES) @FeatureRequirement(featureClass = Graph.Features.EdgeFeatures.class, feature = Graph.Features.EdgeFeatures.FEATURE_ADD_EDGES) @FeatureRequirement(featureClass = Graph.Features.VertexFeatures.class, feature = Graph.Features.VertexFeatures.FEATURE_ADD_PROPERTY) - @FeatureRequirement(featureClass = Graph.Features.VertexPropertyFeatures.class, feature = Graph.Features.VertexPropertyFeatures.FEATURE_INTEGER_VALUES) + @FeatureRequirement(featureClass = Graph.Features.VertexPropertyFeatures.class, feature = FEATURE_INTEGER_VALUES) @FeatureRequirement(featureClass = Graph.Features.EdgeFeatures.class, feature = Graph.Features.EdgeFeatures.FEATURE_ADD_PROPERTY) - @FeatureRequirement(featureClass = Graph.Features.EdgePropertyFeatures.class, feature = Graph.Features.EdgePropertyFeatures.FEATURE_INTEGER_VALUES) + @FeatureRequirement(featureClass = Graph.Features.EdgePropertyFeatures.class, feature = FEATURE_INTEGER_VALUES) @FeatureRequirement(featureClass = Graph.Features.VertexFeatures.class, feature = Graph.Features.VertexFeatures.FEATURE_REMOVE_VERTICES) public void shouldNotGetConcurrentModificationException() { for (int i = 0; i < 25; i++) {