This is an automated email from the ASF dual-hosted git repository. kenhuuu pushed a commit to branch model-upgrade in repository https://gitbox.apache.org/repos/asf/tinkerpop.git
commit 922162513686ac2ccd745e9103a5abe1fc3174b7 Author: Ken Hu <[email protected]> AuthorDate: Fri Jul 17 21:14:39 2026 -0700 Update GraphBinary/GraphSON model round-trip coverage. Assisted-by: Codex:gpt-5.5 --- docs/src/reference/gremlin-variants.asciidoc | 22 +- .../io/graphson/PdtGraphSONSerializersV4.java | 14 +- .../io/graphson/PdtGraphSONSerializersV4Test.java | 27 +- .../Structure/IO/GraphBinary4/Model.cs | 162 ++++++++++- .../Structure/IO/GraphBinary4/RoundTripTests.cs | 262 +++++++++++++++++- .../structure/io/binary/internals/MapSerializer.js | 2 +- .../test/unit/graphbinary/model-test.js | 157 ++++++++--- .../test/unit/graphbinary/model.js | 140 +++++++++- .../gremlin_python/structure/io/graphbinaryV4.py | 2 +- .../main/python/tests/unit/structure/io/model.py | 134 ++++++++- .../unit/structure/io/test_graphbinaryv4model.py | 188 +++++++++++-- .../GremlinServerSerializationIntegrateTest.java | 7 +- .../io/graphbinary/empty-label-vertex-v4.gbin | Bin 0 -> 18 bytes .../structure/io/graphbinary/empty-string-v4.gbin | Bin 0 -> 6 bytes .../structure/io/graphbinary/empty-tree-v4.gbin | Bin 0 -> 6 bytes .../io/graphbinary/four-byte-char-v4.gbin | Bin 0 -> 6 bytes .../structure/io/graphbinary/merge-in-v-v4.gbin | Bin 0 -> 11 bytes .../io/graphbinary/merge-on-create-v4.gbin | Bin 0 -> 16 bytes .../io/graphbinary/merge-on-match-v4.gbin | Bin 0 -> 15 bytes .../structure/io/graphbinary/merge-out-v-v4.gbin | Bin 0 -> 12 bytes .../io/graphbinary/multi-label-vertex-v4.gbin | Bin 0 -> 44 bytes .../io/graphbinary/nanos-duration-v4.gbin | Bin 0 -> 14 bytes .../io/graphbinary/negative-duration-v4.gbin | Bin 0 -> 14 bytes .../graphbinary/negative-scale-bigdecimal-v4.gbin | Bin 0 -> 12 bytes .../structure/io/graphbinary/null-int-v4.gbin | 1 + .../structure/io/graphbinary/null-list-v4.gbin | 1 + .../structure/io/graphbinary/null-long-v4.gbin | 1 + .../structure/io/graphbinary/null-map-v4.gbin | 2 + .../structure/io/graphbinary/null-set-v4.gbin | 1 + .../structure/io/graphbinary/null-string-v4.gbin | 1 + .../io/graphbinary/ordered-string-int-map-v4.gbin | Bin 0 -> 111 bytes .../io/graphbinary/path-multiple-labels-v4.gbin | Bin 0 -> 45 bytes .../io/graphbinary/path-zero-labels-v4.gbin | Bin 0 -> 31 bytes .../io/graphbinary/point-composite-pdt-v4.gbin | Bin 0 -> 45 bytes .../io/graphbinary/positive-duration-v4.gbin | Bin 0 -> 14 bytes .../io/graphbinary/scale-zero-bigdecimal-v4.gbin | Bin 0 -> 12 bytes .../sign-boundary-neg-biginteger-v4.gbin | Bin 0 -> 8 bytes .../sign-boundary-pos-biginteger-v4.gbin | Bin 0 -> 8 bytes .../graphbinary/small-decimal-bigdecimal-v4.gbin | Bin 0 -> 12 bytes .../io/graphbinary/three-byte-char-v4.gbin | Bin 0 -> 5 bytes .../io/graphbinary/tree-deep-nesting-v4.gbin | Bin 0 -> 50 bytes .../io/graphbinary/tree-mixed-key-types-v4.gbin | Bin 0 -> 30 bytes .../structure/io/graphbinary/tree-null-key-v4.gbin | Bin 0 -> 12 bytes ...lti-byte-char-v4.gbin => two-byte-char-v4.gbin} | Bin .../io/graphbinary/uint8-primitive-pdt-v4.gbin | Bin 0 -> 21 bytes .../io/graphbinary/zero-bigdecimal-v4.gbin | Bin 0 -> 11 bytes .../io/graphbinary/zero-biginteger-v4.gbin | Bin 0 -> 7 bytes .../io/graphson/empty-label-vertex-v4.json | 10 + .../structure/io/graphson/empty-string-v4.json | 1 + .../structure/io/graphson/merge-in-v-v4.json | 4 + .../structure/io/graphson/merge-on-create-v4.json | 4 + .../structure/io/graphson/merge-on-match-v4.json | 4 + .../structure/io/graphson/merge-out-v-v4.json | 4 + .../io/graphson/multi-label-vertex-v4.json | 10 + .../structure/io/graphson/nanos-duration-v4.json | 4 + .../io/graphson/negative-duration-v4.json | 4 + .../io/graphson/negative-scale-bigdecimal-v4.json | 4 + .../io/graphson/ordered-string-int-map-v4.json | 22 ++ .../io/graphson/path-multiple-labels-v4.json | 16 ++ .../structure/io/graphson/path-zero-labels-v4.json | 16 ++ .../io/graphson/point-composite-pdt-v4.json | 16 ++ .../io/graphson/positive-duration-v4.json | 4 + .../io/graphson/scale-zero-bigdecimal-v4.json | 4 + .../graphson/sign-boundary-neg-biginteger-v4.json | 4 + .../graphson/sign-boundary-pos-biginteger-v4.json | 4 + .../io/graphson/small-decimal-bigdecimal-v4.json | 4 + ...i-byte-char-v4.json => three-byte-char-v4.json} | 2 +- ...lti-byte-char-v4.json => two-byte-char-v4.json} | 0 .../io/graphson/uint8-primitive-pdt-v4.json | 7 + .../structure/io/graphson/zero-bigdecimal-v4.json | 4 + .../structure/io/graphson/zero-biginteger-v4.json | 4 + .../io/AbstractTypedCompatibilityTest.java | 303 ++++++++++++++++++++- .../tinkerpop/gremlin/structure/io/Model.java | 76 +++++- .../graphbinary/GraphBinaryCompatibilityTest.java | 104 +++++++ 74 files changed, 1648 insertions(+), 115 deletions(-) diff --git a/docs/src/reference/gremlin-variants.asciidoc b/docs/src/reference/gremlin-variants.asciidoc index 3bc95a6fb7..0df31daa3d 100644 --- a/docs/src/reference/gremlin-variants.asciidoc +++ b/docs/src/reference/gremlin-variants.asciidoc @@ -737,6 +737,11 @@ that can be used to fulfill the `gremlingo.Set` interface if desired. * Go does not support ordered maps natively as the built-in `map` type does not guarantee iteration order. Traversal results which contain maps may not preserve original ordering when deserialized into Go's native map types. +* GraphBinary `Char` values are not supported. + +* GraphBinary permits map keys of any supported type, including composite values such as lists. Go's native `map` type +only supports comparable keys, so GraphBinary maps with non-comparable keys are not supported. + * The `subgraph()`-step returns a detached `*Graph` data container exposing `Vertices map[interface{}]*Vertex` and `Edges map[interface{}]*Edge`. The result is not a live `Graph` instance: mutating the maps has no effect on the source graph, and it cannot be passed to `traversal().with(...)`. To @@ -1260,6 +1265,12 @@ The `IoRegistry` tells the serializer what classes from the graph provider to au Gremlin Server roughly uses this same approach when it configures its serializers, so using this same model will ensure compatibility when making requests. +[[gremlin-java-limitations]] +=== Limitations + +* Java `Character` values do not support values outside the Basic Multilingual Plane, which are mainly 4-byte UTF-8 +characters. Those GraphBinary `Char` values are not supported. + [[gremlin-java-gvalue]] === GValue Parameterization @@ -2500,12 +2511,15 @@ unwrap('hello'); // 'hello' * JavaScript's `Number` type is an IEEE 754 double-precision float. `Float`, `Byte`, and `Short` values from the server are deserialized as `Number` and lose their original type information. Use `preciseNumbers: true` to preserve the -original types — see <<gremlin-javascript-numeric-types>>. +original types. See <<gremlin-javascript-numeric-types>>. * `Long` values outside the safe integer range (|n| > 2^53 - 1) are deserialized as `BigInt` to preserve precision. Values within the safe range are deserialized as `Number`. The same server type may produce different JavaScript types. * `Number.isInteger(1.0)` is `true` in JavaScript, so the driver cannot distinguish integer values from whole-number -doubles. `BigDecimal` is not implemented. Typed wrappers (e.g. `toInt()`, `toDouble()`) can be used to control the -exact type sent to the server — see <<gremlin-javascript-numeric-types>>. +doubles. Typed wrappers (e.g. `toInt()`, `toDouble()`) can be used to control the exact type sent to the server. See +<<gremlin-javascript-numeric-types>>. +* GraphBinary `BigDecimal`, `Char`, and `Duration` values are not implemented. +* JavaScript `Date` values do not support the extreme values of GraphBinary's `DateTime` maximum and minimum. Those +GraphBinary `DateTime` values are not supported. * The driver applies GremlinLang type suffixes automatically based on value characteristics: integers within the 32-bit signed range are unsuffixed (Int), integers beyond that up to `Number.MAX_SAFE_INTEGER` use the `L` suffix (Long), non-integer numbers and integers beyond the safe range use the `D` suffix (Double), and `BigInt` values use the `N` @@ -3100,6 +3114,8 @@ so offset date-time values at those boundaries will fail to deserialize. `Duration.FOREVER`) that exceed `TimeSpan.MaxValue` or `TimeSpan.MinValue` will fail to deserialize. * Gremlin's `BigDecimal` supports up to 33 digits of precision while C#'s `decimal` type is limited to 28-29 significant digits, so high-precision values may lose accuracy or fail to deserialize. +* C# `char` values do not support values outside the Basic Multilingual Plane, which are mainly 4-byte UTF-8 +characters. Those GraphBinary `Char` values are not supported. * C# `Dictionary` does not allow `null` keys, so `Map` results with `null` keys (e.g. from `group()` or `groupCount()` on a missing property) will fail during deserialization. diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/PdtGraphSONSerializersV4.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/PdtGraphSONSerializersV4.java index a4bcd391ad..f9652b2591 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/PdtGraphSONSerializersV4.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/PdtGraphSONSerializersV4.java @@ -57,12 +57,7 @@ final class PdtGraphSONSerializersV4 { jsonGenerator.writeStartObject(); jsonGenerator.writeStringField("type", pdt.getName()); jsonGenerator.writeFieldName("fields"); - jsonGenerator.writeStartObject(); - for (final Map.Entry<String, Object> entry : pdt.getFields().entrySet()) { - jsonGenerator.writeFieldName(entry.getKey()); - jsonGenerator.writeObject(entry.getValue()); - } - jsonGenerator.writeEndObject(); + jsonGenerator.writeObject(pdt.getFields()); jsonGenerator.writeEndObject(); } } @@ -92,12 +87,7 @@ final class PdtGraphSONSerializersV4 { typeName = jsonParser.getText(); } else if ("fields".equals(fieldName)) { jsonParser.nextToken(); - while (jsonParser.nextToken() != JsonToken.END_OBJECT) { - final String key = jsonParser.getCurrentName(); - jsonParser.nextToken(); - final Object value = deserializationContext.readValue(jsonParser, Object.class); - fields.put(key, value); - } + fields = deserializationContext.readValue(jsonParser, Map.class); } } diff --git a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/structure/io/graphson/PdtGraphSONSerializersV4Test.java b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/structure/io/graphson/PdtGraphSONSerializersV4Test.java index 6da520d0e0..f153e95ce4 100644 --- a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/structure/io/graphson/PdtGraphSONSerializersV4Test.java +++ b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/structure/io/graphson/PdtGraphSONSerializersV4Test.java @@ -70,15 +70,18 @@ public class PdtGraphSONSerializersV4Test extends AbstractGraphSONTest { assertEquals("Point", value.get("type").asText()); final JsonNode fields = value.get("fields"); - assertEquals("g:Int32", fields.get("x").get("@type").asText()); - assertEquals(1, fields.get("x").get("@value").asInt()); - assertEquals("g:Int32", fields.get("y").get("@type").asText()); - assertEquals(2, fields.get("y").get("@value").asInt()); + assertEquals("g:Map", fields.get("@type").asText()); + final JsonNode x = getMapValue(fields, "x"); + assertEquals("g:Int32", x.get("@type").asText()); + assertEquals(1, x.get("@value").asInt()); + final JsonNode y = getMapValue(fields, "y"); + assertEquals("g:Int32", y.get("@type").asText()); + assertEquals(2, y.get("@value").asInt()); } @Test public void shouldDeserializeValidJson() throws Exception { - final String json = "{\"@type\":\"g:CompositePdt\",\"@value\":{\"type\":\"Point\",\"fields\":{\"x\":{\"@type\":\"g:Int32\",\"@value\":1},\"y\":{\"@type\":\"g:Int32\",\"@value\":2}}}}"; + final String json = "{\"@type\":\"g:CompositePdt\",\"@value\":{\"type\":\"Point\",\"fields\":{\"@type\":\"g:Map\",\"@value\":[\"x\",{\"@type\":\"g:Int32\",\"@value\":1},\"y\",{\"@type\":\"g:Int32\",\"@value\":2}]}}}"; final CompositePDT pdt = mapper.readValue(json, CompositePDT.class); assertEquals("Point", pdt.getName()); @@ -117,7 +120,7 @@ public class PdtGraphSONSerializersV4Test extends AbstractGraphSONTest { assertEquals("g:CompositePdt", node.get("@type").asText()); final JsonNode fields = node.get("@value").get("fields"); - final JsonNode locationNode = fields.get("location"); + final JsonNode locationNode = getMapValue(fields, "location"); assertEquals("g:CompositePdt", locationNode.get("@type").asText()); assertEquals("Point", locationNode.get("@value").get("type").asText()); @@ -318,7 +321,7 @@ public class PdtGraphSONSerializersV4Test extends AbstractGraphSONTest { final JsonNode node = plainMapper.readTree(json); assertEquals("g:CompositePdt", node.get("@type").asText()); - final JsonNode durNode = node.get("@value").get("fields").get("dur"); + final JsonNode durNode = getMapValue(node.get("@value").get("fields"), "dur"); assertEquals("g:PrimitivePdt", durNode.get("@type").asText()); assertEquals("Duration", durNode.get("@value").get("type").asText()); assertEquals("PT1H", durNode.get("@value").get("value").asText()); @@ -332,6 +335,16 @@ public class PdtGraphSONSerializersV4Test extends AbstractGraphSONTest { assertEquals("PT1H", nestedResult.getValue()); } + private JsonNode getMapValue(final JsonNode graphsonMap, final String key) { + final JsonNode values = graphsonMap.get("@value"); + for (int ix = 0; ix < values.size(); ix = ix + 2) { + if (key.equals(values.get(ix).asText())) + return values.get(ix + 1); + } + + throw new IllegalArgumentException("Could not find key in GraphSON map: " + key); + } + // helper types for primitive PDT hydration tests static class MyDuration { diff --git a/gremlin-dotnet/test/Gremlin.Net.UnitTest/Structure/IO/GraphBinary4/Model.cs b/gremlin-dotnet/test/Gremlin.Net.UnitTest/Structure/IO/GraphBinary4/Model.cs index bc92428e9d..b161044af2 100644 --- a/gremlin-dotnet/test/Gremlin.Net.UnitTest/Structure/IO/GraphBinary4/Model.cs +++ b/gremlin-dotnet/test/Gremlin.Net.UnitTest/Structure/IO/GraphBinary4/Model.cs @@ -23,6 +23,7 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Numerics; using Gremlin.Net.Process.Traversal; using Gremlin.Net.Structure; @@ -33,12 +34,12 @@ namespace Gremlin.Net.UnitTest.Structure.IO.GraphBinary4 /// Defines the supported types for GraphBinary 4.0 IO and provides test entries for round-trip testing. /// /// The following models aren't supported: - /// tinker-graph Graph type not implemented /// max-offsetdatetime DateTimeOffset.MaxValue exceeds serialization range /// min-offsetdatetime DateTimeOffset.MinValue exceeds serialization range /// forever-duration TimeSpan cannot represent Duration.FOREVER /// pos-bigdecimal Java BigDecimal precision (33 digits) exceeds C# decimal (28-29 digits) /// neg-bigdecimal Java BigDecimal precision (33 digits) exceeds C# decimal (28-29 digits) + /// four-byte-char C# char cannot represent a supplementary Unicode code point as a single scalar value /// var-type-map Dictionary doesn't support null key /// </summary> public static class Model @@ -70,11 +71,50 @@ namespace Gremlin.Net.UnitTest.Structure.IO.GraphBinary4 return tree; } + private static Tree BuildNullKeyTree() + { + var tree = new Tree(); + tree.GetOrCreateChild(null); + return tree; + } + + private static Tree BuildMixedKeyTypesTree() + { + var tree = new Tree(); + tree.GetOrCreateChild("name"); + tree.GetOrCreateChild(123); + return tree; + } + + private static Tree BuildDeepNestingTree() + { + var tree = new Tree(); + tree.GetOrCreateChild("root").GetOrCreateChild("branch").GetOrCreateChild("leaf"); + return tree; + } + public static Dictionary<string, object?> Entries { get; } = new() { + // BigDecimal + ["zero-bigdecimal"] = 0m, + ["scale-zero-bigdecimal"] = 1234m, + ["negative-scale-bigdecimal"] = 123400m, + ["small-decimal-bigdecimal"] = 12.34m, + // BigInteger ["pos-biginteger"] = BigInteger.Parse("123456789987654321123456789987654321"), ["neg-biginteger"] = BigInteger.Parse("-123456789987654321123456789987654321"), + ["zero-biginteger"] = BigInteger.Zero, + ["sign-boundary-pos-biginteger"] = new BigInteger(128), + ["sign-boundary-neg-biginteger"] = new BigInteger(-129), + + // Provider-defined types + ["uint8-primitive-pdt"] = new PrimitivePDT("Uint8", "10"), + ["point-composite-pdt"] = new CompositePDT("Point", new Dictionary<string, object?> + { + { "x", 1 }, + { "y", 2 }, + }), // Byte (sbyte in C#) ["min-byte"] = sbyte.MinValue, // -128 @@ -108,10 +148,17 @@ namespace Gremlin.Net.UnitTest.Structure.IO.GraphBinary4 // Char ["single-byte-char"] = 'a', - ["multi-byte-char"] = '\u03A9', // Greek capital letter Omega + ["two-byte-char"] = '\u03A9', // Greek capital letter Omega + ["three-byte-char"] = '\u20AC', // Null ["unspecified-null"] = null, + ["null-int"] = null, + ["null-long"] = null, + ["null-string"] = null, + ["null-list"] = null, + ["null-map"] = null, + ["null-set"] = null, // Boolean ["true-boolean"] = true, @@ -120,6 +167,7 @@ namespace Gremlin.Net.UnitTest.Structure.IO.GraphBinary4 // String ["single-byte-string"] = "abc", ["mixed-string"] = "abc\u0391\u0392\u0393", // abc + Greek letters Alpha, Beta, Gamma + ["empty-string"] = "", // BulkSet (represented as List with duplicates) ["var-bulklist"] = new List<object?> { "marko", "josh", "josh" }, @@ -127,11 +175,15 @@ namespace Gremlin.Net.UnitTest.Structure.IO.GraphBinary4 // Duration (TimeSpan) ["zero-duration"] = TimeSpan.Zero, + ["positive-duration"] = TimeSpan.FromSeconds(123), + ["negative-duration"] = TimeSpan.FromSeconds(-123), + ["nanos-duration"] = TimeSpan.FromSeconds(123) + TimeSpan.FromTicks(4567), // Edge ["traversal-edge"] = new Edge(13, new Vertex(1, "person"), "develops", new Vertex(10, "software"), new dynamic[] { SinceProperty }), ["no-prop-edge"] = new Edge(13, new Vertex(1, "person"), "develops", new Vertex(10, "software")), + ["tinker-graph"] = CrewGraphFactory.Create(), // Int ["max-int"] = int.MaxValue, @@ -154,16 +206,34 @@ namespace Gremlin.Net.UnitTest.Structure.IO.GraphBinary4 // Note: null key not supported in C# Dictionary }, ["empty-map"] = new Dictionary<object, object?>(), + ["ordered-string-int-map"] = new Dictionary<object, object?> + { + { "delta", 4 }, + { "alpha", 1 }, + { "charlie", 3 }, + { "bravo", 2 }, + { "echo", 5 }, + { "foxtrot", 6 }, + }, // Path ["traversal-path"] = new Path( new List<ISet<string>> { new HashSet<string>(), new HashSet<string>(), new HashSet<string>() }, new List<object?> { new Vertex(1, "person"), new Vertex(10, "software"), new Vertex(11, "software") }), ["empty-path"] = new Path(new List<ISet<string>>(), new List<object?>()), + ["path-zero-labels"] = new Path( + new List<ISet<string>> { new HashSet<string>() }, + new List<object?> { "marko" }), + ["path-multiple-labels"] = new Path( + new List<ISet<string>> { new HashSet<string> { "a", "b" } }, + new List<object?> { "marko" }), // Tree ["traversal-tree"] = BuildTraversalTree(), ["empty-tree"] = new Tree(), + ["tree-null-key"] = BuildNullKeyTree(), + ["tree-mixed-key-types"] = BuildMixedKeyTypesTree(), + ["tree-deep-nesting"] = BuildDeepNestingTree(), ["prop-path"] = new Path( new List<ISet<string>> { new HashSet<string>(), new HashSet<string>(), new HashSet<string>() }, @@ -199,6 +269,8 @@ namespace Gremlin.Net.UnitTest.Structure.IO.GraphBinary4 // Vertex ["no-prop-vertex"] = new Vertex(1, "person"), + ["multi-label-vertex"] = new Vertex(1, "person", labels: new[] { "person", "employee" }), + ["empty-label-vertex"] = new Vertex(1, "", labels: Array.Empty<string>()), ["traversal-vertex"] = new Vertex(1, "person", new dynamic[] { new Property("name", NameMarko, null), @@ -217,6 +289,92 @@ namespace Gremlin.Net.UnitTest.Structure.IO.GraphBinary4 // Direction enum ["out-direction"] = Direction.Out, + + // Merge enum + ["merge-on-create"] = Merge.OnCreate, + ["merge-on-match"] = Merge.OnMatch, + ["merge-out-v"] = Merge.OutV, + ["merge-in-v"] = Merge.InV, }; + + private static class CrewGraphFactory + { + public static Graph Create() + { + var graph = new Graph(); + var v1 = AddVertex(graph, 1, "person", + Vp(0L, "name", "marko"), + Vp(6L, "location", "san diego", new Property("startTime", 1997, null), + new Property("endTime", 2001, null)), + Vp(7L, "location", "santa cruz", new Property("startTime", 2001, null), + new Property("endTime", 2004, null)), + Vp(8L, "location", "brussels", new Property("startTime", 2004, null), + new Property("endTime", 2005, null)), + Vp(9L, "location", "santa fe", new Property("startTime", 2005, null))); + var v7 = AddVertex(graph, 7, "person", + Vp(1L, "name", "stephen"), + Vp(10L, "location", "centreville", new Property("startTime", 1990, null), + new Property("endTime", 2000, null)), + Vp(11L, "location", "dulles", new Property("startTime", 2000, null), + new Property("endTime", 2006, null)), + Vp(12L, "location", "purcellville", new Property("startTime", 2006, null))); + var v8 = AddVertex(graph, 8, "person", + Vp(2L, "name", "matthias"), + Vp(13L, "location", "bremen", new Property("startTime", 2004, null), + new Property("endTime", 2007, null)), + Vp(14L, "location", "baltimore", new Property("startTime", 2007, null), + new Property("endTime", 2011, null)), + Vp(15L, "location", "oakland", new Property("startTime", 2011, null), + new Property("endTime", 2014, null)), + Vp(16L, "location", "seattle", new Property("startTime", 2014, null))); + var v9 = AddVertex(graph, 9, "person", + Vp(3L, "name", "daniel"), + Vp(17L, "location", "spremberg", new Property("startTime", 1982, null), + new Property("endTime", 2005, null)), + Vp(18L, "location", "kaiserslautern", new Property("startTime", 2005, null), + new Property("endTime", 2009, null)), + Vp(19L, "location", "aachen", new Property("startTime", 2009, null))); + var v10 = AddVertex(graph, 10, "software", Vp(4L, "name", "gremlin")); + var v11 = AddVertex(graph, 11, "software", Vp(5L, "name", "tinkergraph")); + + AddEdge(graph, 13, v1, "develops", v10, new Property("since", 2009, null)); + AddEdge(graph, 14, v1, "develops", v11, new Property("since", 2010, null)); + AddEdge(graph, 15, v1, "uses", v10, new Property("skill", 4, null)); + AddEdge(graph, 16, v1, "uses", v11, new Property("skill", 5, null)); + AddEdge(graph, 17, v7, "develops", v10, new Property("since", 2010, null)); + AddEdge(graph, 18, v7, "develops", v11, new Property("since", 2011, null)); + AddEdge(graph, 19, v7, "uses", v10, new Property("skill", 5, null)); + AddEdge(graph, 20, v7, "uses", v11, new Property("skill", 4, null)); + AddEdge(graph, 21, v8, "develops", v10, new Property("since", 2012, null)); + AddEdge(graph, 22, v8, "uses", v10, new Property("skill", 3, null)); + AddEdge(graph, 23, v8, "uses", v11, new Property("skill", 3, null)); + AddEdge(graph, 24, v9, "uses", v10, new Property("skill", 5, null)); + AddEdge(graph, 25, v9, "uses", v11, new Property("skill", 3, null)); + AddEdge(graph, 26, v10, "traverses", v11); + + return graph; + } + + private static VertexProperty Vp(object id, string label, object? value, params Property[] metaProperties) + { + return new VertexProperty(id, label, value, null, metaProperties.Cast<dynamic>().ToArray()); + } + + private static Vertex AddVertex(Graph graph, object id, string label, + params VertexProperty[] vertexProperties) + { + var vertex = new Vertex(id, label, vertexProperties.Cast<dynamic>().ToArray()); + graph.Vertices[id] = vertex; + return vertex; + } + + private static Edge AddEdge(Graph graph, object id, Vertex outV, string label, Vertex inV, + params Property[] properties) + { + var edge = new Edge(id, outV, label, inV, properties.Cast<dynamic>().ToArray()); + graph.Edges[id] = edge; + return edge; + } + } } } diff --git a/gremlin-dotnet/test/Gremlin.Net.UnitTest/Structure/IO/GraphBinary4/RoundTripTests.cs b/gremlin-dotnet/test/Gremlin.Net.UnitTest/Structure/IO/GraphBinary4/RoundTripTests.cs index 8622316b49..f1e25c3507 100644 --- a/gremlin-dotnet/test/Gremlin.Net.UnitTest/Structure/IO/GraphBinary4/RoundTripTests.cs +++ b/gremlin-dotnet/test/Gremlin.Net.UnitTest/Structure/IO/GraphBinary4/RoundTripTests.cs @@ -22,9 +22,17 @@ #endregion using System; +using System.Collections.Generic; using System.IO; +using System.Linq; using System.Threading.Tasks; using Gremlin.Net.Structure.IO.GraphBinary4; +using GremlinEdge = Gremlin.Net.Structure.Edge; +using GremlinGraph = Gremlin.Net.Structure.Graph; +using GremlinPath = Gremlin.Net.Structure.Path; +using GremlinProperty = Gremlin.Net.Structure.Property; +using GremlinVertex = Gremlin.Net.Structure.Vertex; +using GremlinVertexProperty = Gremlin.Net.Structure.VertexProperty; using Xunit; namespace Gremlin.Net.UnitTest.Structure.IO.GraphBinary4 @@ -159,6 +167,134 @@ namespace Gremlin.Net.UnitTest.Structure.IO.GraphBinary4 }; } + private static bool OrderedMapComparator(object? x, object? y) + { + if (x is not IDictionary<object, object?> expected || y is not IDictionary<object, object?> actual) + return false; + if (expected.Count != actual.Count) + return false; + + return expected.Zip(actual).All(pair => + Equals(pair.First.Key, pair.Second.Key) && Equals(pair.First.Value, pair.Second.Value)); + } + + private static bool PathComparator(object? x, object? y) + { + if (x is not GremlinPath expected || y is not GremlinPath actual) + return false; + if (!expected.Objects.SequenceEqual(actual.Objects) || expected.Labels.Count != actual.Labels.Count) + return false; + + for (var ix = 0; ix < expected.Labels.Count; ix++) + { + if (!expected.Labels[ix].SetEquals(actual.Labels[ix])) + return false; + } + + return true; + } + + private static bool VertexLabelComparator(object? x, object? y) + { + if (x is not GremlinVertex expected || y is not GremlinVertex actual) + return false; + + return expected.Equals(actual) && + expected.Labels.Count == actual.Labels.Count && + expected.Labels.All(actual.Labels.Contains); + } + + private static bool GraphComparator(object? x, object? y) + { + if (x is not GremlinGraph expected || y is not GremlinGraph actual) + return false; + if (expected.Vertices.Count != actual.Vertices.Count || expected.Edges.Count != actual.Edges.Count) + return false; + + foreach (var pair in expected.Vertices) + { + if (!actual.Vertices.TryGetValue(pair.Key, out var actualVertex) || + !VertexComparator(pair.Value, actualVertex)) + return false; + } + + foreach (var pair in expected.Edges) + { + if (!actual.Edges.TryGetValue(pair.Key, out var actualEdge) || !EdgeComparator(pair.Value, actualEdge)) + return false; + } + + return true; + } + + private static bool VertexComparator(GremlinVertex expected, GremlinVertex actual) + { + return expected.Equals(actual) && + expected.Label == actual.Label && + LabelsEqual(expected.Labels, actual.Labels) && + VertexPropertiesEqual(expected.Properties, actual.Properties); + } + + private static bool EdgeComparator(GremlinEdge expected, GremlinEdge actual) + { + return expected.Equals(actual) && + expected.Label == actual.Label && + LabelsEqual(expected.Labels, actual.Labels) && + Equals(expected.OutV.Id, actual.OutV.Id) && + Equals(expected.InV.Id, actual.InV.Id) && + PropertiesEqual(expected.Properties, actual.Properties); + } + + private static bool VertexPropertiesEqual(dynamic[] expected, dynamic[] actual) + { + if (expected.Length != actual.Length) + return false; + + for (var ix = 0; ix < expected.Length; ix++) + { + if (expected[ix] is not GremlinVertexProperty expectedProperty || + actual[ix] is not GremlinVertexProperty actualProperty || + !VertexPropertyComparator(expectedProperty, actualProperty)) + return false; + } + + return true; + } + + private static bool VertexPropertyComparator(GremlinVertexProperty expected, GremlinVertexProperty actual) + { + return expected.Equals(actual) && + expected.Label == actual.Label && + Equals(expected.Value, actual.Value) && + PropertiesEqual(expected.Properties, actual.Properties); + } + + private static bool PropertiesEqual(dynamic[] expected, dynamic[] actual) + { + if (expected.Length != actual.Length) + return false; + + for (var ix = 0; ix < expected.Length; ix++) + { + if (expected[ix] is not GremlinProperty expectedProperty || + actual[ix] is not GremlinProperty actualProperty || + !PropertyComparator(expectedProperty, actualProperty)) + return false; + } + + return true; + } + + private static bool PropertyComparator(GremlinProperty expected, GremlinProperty actual) + { + return expected.Key == actual.Key && Equals(expected.Value, actual.Value); + } + + private static bool LabelsEqual(IReadOnlySet<string> expected, IReadOnlySet<string> actual) + { + return expected.Count == actual.Count && expected.All(actual.Contains); + } + // BigInteger tests [Fact] public Task TestPosBigInteger() => Run("pos-biginteger"); @@ -166,6 +302,36 @@ namespace Gremlin.Net.UnitTest.Structure.IO.GraphBinary4 [Fact] public Task TestNegBigInteger() => Run("neg-biginteger"); + [Fact] + public Task TestZeroBigInteger() => Run("zero-biginteger"); + + [Fact] + public Task TestSignBoundaryPosBigInteger() => Run("sign-boundary-pos-biginteger"); + + [Fact] + public Task TestSignBoundaryNegBigInteger() => Run("sign-boundary-neg-biginteger"); + + // BigDecimal tests + [Fact] + public Task TestZeroBigDecimal() => Run("zero-bigdecimal"); + + [Fact] + public Task TestScaleZeroBigDecimal() => Run("scale-zero-bigdecimal"); + + [Fact] + public Task TestNegativeScaleBigDecimal() => + RunWriteRead("negative-scale-bigdecimal"); // decimal doesn't preserve negative scale for byte-exact writes + + [Fact] + public Task TestSmallDecimalBigDecimal() => Run("small-decimal-bigdecimal"); + + // Provider-defined type tests + [Fact] + public Task TestUint8PrimitivePdt() => Run("uint8-primitive-pdt"); + + [Fact] + public Task TestPointCompositePdt() => Run("point-composite-pdt"); + // Byte tests [Fact] public Task TestMinByte() => Run("min-byte"); @@ -235,12 +401,33 @@ namespace Gremlin.Net.UnitTest.Structure.IO.GraphBinary4 public Task TestSingleByteChar() => Run("single-byte-char"); [Fact] - public Task TestMultiByteChar() => Run("multi-byte-char"); + public Task TestTwoByteChar() => Run("two-byte-char"); + + [Fact] + public Task TestThreeByteChar() => Run("three-byte-char"); // Null test [Fact] public Task TestUnspecifiedNull() => Run("unspecified-null"); + [Fact] + public Task TestNullInt() => RunRead("null-int"); // typed nulls deserialize to plain null + + [Fact] + public Task TestNullLong() => RunRead("null-long"); // typed nulls deserialize to plain null + + [Fact] + public Task TestNullString() => RunRead("null-string"); // typed nulls deserialize to plain null + + [Fact] + public Task TestNullList() => RunRead("null-list"); // typed nulls deserialize to plain null + + [Fact] + public Task TestNullMap() => RunRead("null-map"); // typed nulls deserialize to plain null + + [Fact] + public Task TestNullSet() => RunRead("null-set"); // typed nulls deserialize to plain null + // Boolean tests [Fact] public Task TestTrueBoolean() => Run("true-boolean"); @@ -255,17 +442,30 @@ namespace Gremlin.Net.UnitTest.Structure.IO.GraphBinary4 [Fact] public Task TestMixedString() => Run("mixed-string"); + [Fact] + public Task TestEmptyString() => Run("empty-string"); + // BulkSet tests (read only - BulkSet deserialized as List) [Fact] - public Task TestVarBulkList() => RunRead("var-bulklist"); + public Task TestVarBulkList() => RunRead("var-bulklist"); // BulkList deserializes as a plain List [Fact] - public Task TestEmptyBulkList() => RunRead("empty-bulklist"); + public Task TestEmptyBulkList() => RunRead("empty-bulklist"); // BulkList deserializes as a plain List // Duration tests [Fact] public Task TestZeroDuration() => Run("zero-duration"); + [Fact] + public Task TestPositiveDuration() => Run("positive-duration"); + + [Fact] + public Task TestNegativeDuration() => Run("negative-duration"); + + [Fact] + public Task TestNanosDuration() => + RunWriteRead("nanos-duration"); // TimeSpan truncates sub-tick nanoseconds for byte-exact writes + // Edge tests [Fact] public Task TestTraversalEdge() => RunWriteRead("traversal-edge"); // properties aren't serialized @@ -298,6 +498,10 @@ namespace Gremlin.Net.UnitTest.Structure.IO.GraphBinary4 [Fact] public Task TestEmptyMap() => Run("empty-map"); + [Fact] + public Task TestOrderedStringIntMap() => + RunWriteRead("ordered-string-int-map", OrderedMapComparator); // writer doesn't emit the ordered-map value flag + // Path tests [Fact] public Task TestTraversalPath() => RunWriteRead("traversal-path"); // properties written as null not empty list @@ -305,6 +509,13 @@ namespace Gremlin.Net.UnitTest.Structure.IO.GraphBinary4 [Fact] public Task TestEmptyPath() => Run("empty-path"); + [Fact] + public Task TestPathZeroLabels() => Run("path-zero-labels", PathComparator); + + [Fact] + public Task TestPathMultipleLabels() => + RunWriteRead("path-multiple-labels", PathComparator); // label set ordering isn't guaranteed + [Fact] public Task TestPropPath() => RunWriteRead("prop-path"); // properties aren't serialized @@ -313,19 +524,16 @@ namespace Gremlin.Net.UnitTest.Structure.IO.GraphBinary4 public Task TestTraversalTree() => RunWriteRead("traversal-tree"); // vertex properties aren't serialized [Fact] - public Task TestEmptyTree() => TestEmptyTreeRoundTrip(); + public Task TestEmptyTree() => Run("empty-tree"); - private async Task TestEmptyTreeRoundTrip() - { - // no .gbin resource exists for an empty tree, so exercise an in-memory round trip - var empty = (Gremlin.Net.Structure.Tree)Model.Entries["empty-tree"]!; - using var writeStream = new MemoryStream(); - await Writer.WriteAsync(empty, writeStream); - using var readStream = new MemoryStream(writeStream.ToArray()); - var roundTripped = await Reader.ReadAsync(readStream); - Assert.Equal(empty, roundTripped); - Assert.True(((Gremlin.Net.Structure.Tree)roundTripped!).IsLeaf()); - } + [Fact] + public Task TestTreeNullKey() => Run("tree-null-key"); + + [Fact] + public Task TestTreeMixedKeyTypes() => Run("tree-mixed-key-types"); + + [Fact] + public Task TestTreeDeepNesting() => Run("tree-deep-nesting"); // Property tests [Fact] @@ -362,6 +570,17 @@ namespace Gremlin.Net.UnitTest.Structure.IO.GraphBinary4 [Fact] public Task TestTraversalVertex() => RunWriteRead("traversal-vertex"); // properties aren't serialized + [Fact] + public Task TestTinkerGraph() => Run("tinker-graph", GraphComparator); + + [Fact] + public Task TestMultiLabelVertex() => + RunWriteRead("multi-label-vertex", VertexLabelComparator); // Vertex equality is id-only + + [Fact] + public Task TestEmptyLabelVertex() => + RunWriteRead("empty-label-vertex", VertexLabelComparator); // Vertex equality is id-only + // VertexProperty tests [Fact] public Task TestTraversalVertexProperty() => RunWriteRead("traversal-vertexproperty"); // properties aren't serialized @@ -379,5 +598,18 @@ namespace Gremlin.Net.UnitTest.Structure.IO.GraphBinary4 // Direction enum test [Fact] public Task TestOutDirection() => Run("out-direction"); + + // Merge enum tests + [Fact] + public Task TestMergeOnCreate() => Run("merge-on-create"); + + [Fact] + public Task TestMergeOnMatch() => Run("merge-on-match"); + + [Fact] + public Task TestMergeOutV() => Run("merge-out-v"); + + [Fact] + public Task TestMergeInV() => Run("merge-in-v"); } } diff --git a/gremlin-js/gremlin-javascript/lib/structure/io/binary/internals/MapSerializer.js b/gremlin-js/gremlin-javascript/lib/structure/io/binary/internals/MapSerializer.js index b3db2853c9..6ca8500f28 100644 --- a/gremlin-js/gremlin-javascript/lib/structure/io/binary/internals/MapSerializer.js +++ b/gremlin-js/gremlin-javascript/lib/structure/io/binary/internals/MapSerializer.js @@ -111,7 +111,7 @@ export default class MapSerializer { if (value_flag === 0x01) { return null; } - if (value_flag !== 0x00) { + if (value_flag !== 0x00 && value_flag !== 0x02) { throw new Error(`MapSerializer: unexpected {value_flag}=0x${value_flag.toString(16)}`); } return this.deserializeValue(reader, value_flag, type_code); diff --git a/gremlin-js/gremlin-javascript/test/unit/graphbinary/model-test.js b/gremlin-js/gremlin-javascript/test/unit/graphbinary/model-test.js index bca8a47d7a..a61bfb566e 100644 --- a/gremlin-js/gremlin-javascript/test/unit/graphbinary/model-test.js +++ b/gremlin-js/gremlin-javascript/test/unit/graphbinary/model-test.js @@ -124,13 +124,6 @@ function runRead(name, comparator = assertEqual) { }); } -// unimplemented type -function skip(name, reason) { - describe(name, () => { - it.skip(`${reason}`, () => {}); - }); -} - function assertEqual(actual, expected) { if (Number.isNaN(expected) && Number.isNaN(actual)) return; if (Object.is(expected, -0) && Object.is(actual, -0)) return; @@ -165,6 +158,89 @@ function setComparator(actual, expected) { assert.deepStrictEqual([...actual].sort(), [...expected].sort()); } +function orderedMapComparator(actual, expected) { + assert.instanceOf(actual, Map); + assert.instanceOf(expected, Map); + assert.deepStrictEqual([...actual.entries()], [...expected.entries()]); +} + +function treeComparator(actual, expected) { + assert.isTrue(actual.equals(expected)); +} + +function pathComparator(actual, expected) { + assert.deepStrictEqual(actual.objects, expected.objects); + assert.strictEqual(actual.labels.length, expected.labels.length); + for (let ix = 0; ix < actual.labels.length; ix++) { + setComparator(actual.labels[ix], expected.labels[ix]); + } +} + +function vertexLabelComparator(actual, expected) { + assert.strictEqual(actual.id, expected.id); + setComparator(actual.labels, expected.labels); +} + +function propertyComparator(actual, expected) { + assert.strictEqual(actual.key, expected.key); + assert.deepStrictEqual(actual.value, expected.value); +} + +function vertexPropertyComparator(actual, expected) { + assert.strictEqual(actual.id, expected.id); + assert.strictEqual(actual.label, expected.label); + assert.deepStrictEqual(actual.value, expected.value); + assert.strictEqual(actual.properties.length, expected.properties.length); + for (let ix = 0; ix < actual.properties.length; ix++) { + propertyComparator(actual.properties[ix], expected.properties[ix]); + } +} + +function vertexComparator(actual, expected) { + assert.strictEqual(actual.id, expected.id); + assert.strictEqual(actual.label, expected.label); + setComparator(actual.labels, expected.labels); + assert.strictEqual(actual.properties.length, expected.properties.length); + for (let ix = 0; ix < actual.properties.length; ix++) { + vertexPropertyComparator(actual.properties[ix], expected.properties[ix]); + } +} + +function edgeComparator(actual, expected) { + assert.strictEqual(actual.id, expected.id); + assert.strictEqual(actual.label, expected.label); + setComparator(actual.labels, expected.labels); + assert.strictEqual(actual.outV.id, expected.outV.id); + assert.strictEqual(actual.inV.id, expected.inV.id); + assert.strictEqual(actual.properties.length, expected.properties.length); + for (let ix = 0; ix < actual.properties.length; ix++) { + propertyComparator(actual.properties[ix], expected.properties[ix]); + } +} + +function graphComparator(actual, expected) { + assert.strictEqual(actual.vertices.size, expected.vertices.size); + assert.strictEqual(actual.edges.size, expected.edges.size); + assert.deepStrictEqual([...actual.vertices.keys()], [...expected.vertices.keys()]); + assert.deepStrictEqual([...actual.edges.keys()], [...expected.edges.keys()]); + for (const [id, expectedVertex] of expected.vertices) { + vertexComparator(actual.vertices.get(id), expectedVertex); + } + for (const [id, expectedEdge] of expected.edges) { + edgeComparator(actual.edges.get(id), expectedEdge); + } +} + +function primitivePdtComparator(actual, expected) { + assert.strictEqual(actual.name, expected.name); + assert.strictEqual(actual.value, expected.value); +} + +function compositePdtComparator(actual, expected) { + assert.strictEqual(actual.name, expected.name); + assert.deepStrictEqual(actual.fields, expected.fields); +} + function setCardinalityComparator(actual, expected) { assert.strictEqual(actual.id, expected.id); assert.strictEqual(actual.label, expected.label); @@ -182,9 +258,14 @@ function invalidDateComparator(actual, expected) { } describe('GraphBinary v4 Model Tests', () => { - // run mode (31 entries) + // run mode run('pos-biginteger'); run('neg-biginteger'); + run('zero-biginteger'); + run('sign-boundary-pos-biginteger'); + run('sign-boundary-neg-biginteger'); + run('uint8-primitive-pdt', primitivePdtComparator); + run('point-composite-pdt', compositePdtComparator); run('empty-binary'); run('str-binary'); run('max-double'); @@ -199,6 +280,7 @@ describe('GraphBinary v4 Model Tests', () => { run('false-boolean'); run('single-byte-string'); run('mixed-string'); + run('empty-string'); run('var-type-list'); run('empty-list'); run('no-prop-edge'); @@ -207,15 +289,26 @@ describe('GraphBinary v4 Model Tests', () => { run('empty-map'); run('traversal-path'); run('empty-path'); + run('path-zero-labels', pathComparator); + run('empty-tree', treeComparator); + run('tree-null-key', treeComparator); + run('tree-mixed-key-types', treeComparator); + run('tree-deep-nesting', treeComparator); run('edge-property'); run('null-property'); run('empty-set'); run('no-prop-vertex'); + run('multi-label-vertex', vertexLabelComparator); + run('empty-label-vertex', vertexLabelComparator); run('id-t'); run('out-direction'); + run('merge-on-create'); + run('merge-on-match'); + run('merge-out-v'); + run('merge-in-v'); run('neg-zero-double', negZeroComparator); - // runWriteRead mode (23 entries) + // runWriteRead mode runWriteRead('min-byte'); runWriteRead('max-byte'); runWriteRead('max-float'); @@ -239,35 +332,29 @@ describe('GraphBinary v4 Model Tests', () => { runWriteRead('meta-vertexproperty'); runWriteRead('set-cardinality-vertexproperty', setCardinalityComparator); runWriteRead('traversal-vertex'); + // JS deserializes safe Long values to Number, so vertex-property ids can't be re-emitted byte-exactly. + runWriteRead('tinker-graph', graphComparator); + // label set ordering isn't guaranteed after a JS object round trip. + runWriteRead('path-multiple-labels', pathComparator); + // JS preserves Map entry order, but its writer doesn't emit the ordered-map value flag. + runWriteRead('ordered-string-int-map', orderedMapComparator); + runWriteRead('traversal-tree', treeComparator); // vertex properties aren't serialized - // Tree - runWriteRead('traversal-tree'); // vertex properties aren't serialized - - // empty-tree has no .gbin resource, so exercise an in-memory round trip - describe('empty-tree', () => { - it('in-memory round trip', async () => { - const empty = model['empty-tree']; - const roundTripped = await anySerializer.deserialize( - StreamReader.fromBuffer(anySerializer.serialize(empty)), - ); - assert.deepStrictEqual(roundTripped, empty); - assert.isTrue(roundTripped.isLeaf()); - }); - }); - - // runRead mode (5 entries) + // runRead mode + // JS Number can't re-emit this as a Float. runRead('neg-zero-float', negZeroComparator); + // invalid Date models can't be serialized back. runRead('max-offsetdatetime', invalidDateComparator); runRead('min-offsetdatetime', invalidDateComparator); + // properties aren't serialized in JS for this path fixture. runRead('prop-path'); + // this fixture has complex keys that JS can't write back byte-exactly. runRead('var-type-map'); - - // skip mode (7 entries) - skip('single-byte-char', 'Char type (0x80) not implemented'); - skip('multi-byte-char', 'Char type (0x80) not implemented'); - skip('tinker-graph', 'Graph type not implemented'); - skip('pos-bigdecimal', 'BigDecimal type not implemented'); - skip('neg-bigdecimal', 'BigDecimal type not implemented'); - skip('forever-duration', 'Duration type not implemented'); - skip('zero-duration', 'Duration type not implemented'); -}); \ No newline at end of file + // typed nulls deserialize to plain null, so the original type can't be re-emitted. + runRead('null-int'); + runRead('null-long'); + runRead('null-string'); + runRead('null-list'); + runRead('null-map'); + runRead('null-set'); +}); diff --git a/gremlin-js/gremlin-javascript/test/unit/graphbinary/model.js b/gremlin-js/gremlin-javascript/test/unit/graphbinary/model.js index 63aa9dbfb3..c06f4c4965 100644 --- a/gremlin-js/gremlin-javascript/test/unit/graphbinary/model.js +++ b/gremlin-js/gremlin-javascript/test/unit/graphbinary/model.js @@ -17,15 +17,26 @@ * under the License. */ -import { Vertex, Edge, Property, VertexProperty, Path, Tree } from '../../../lib/structure/graph.js'; -import { direction, t } from '../../../lib/process/traversal.js'; +import { + Graph, + Vertex, + Edge, + Property, + VertexProperty, + Path, + Tree, + CompositePDT, + PrimitivePDT +} from '../../../lib/structure/graph.js'; +import { direction, merge, t } from '../../../lib/process/traversal.js'; /* * Unsupported types (no map entries, .gbin files exist but types not implemented): - * - single-byte-char, multi-byte-char (Char 0x80 not implemented) - * - tinker-graph (Graph type not implemented) - * - pos-bigdecimal, neg-bigdecimal (BigDecimal type not implemented) - * - forever-duration, zero-duration (Duration type not implemented) + * - single-byte-char, two-byte-char, three-byte-char, four-byte-char (Char 0x80 not implemented) + * - pos-bigdecimal, neg-bigdecimal, zero-bigdecimal, scale-zero-bigdecimal, + * negative-scale-bigdecimal, small-decimal-bigdecimal (BigDecimal type not implemented) + * - forever-duration, zero-duration, positive-duration, negative-duration, + * nanos-duration (Duration type not implemented) */ const model = {}; @@ -33,6 +44,13 @@ const model = {}; // BigInteger values model['pos-biginteger'] = BigInt('123456789987654321123456789987654321'); model['neg-biginteger'] = BigInt('-123456789987654321123456789987654321'); +model['zero-biginteger'] = 0n; +model['sign-boundary-pos-biginteger'] = 128n; +model['sign-boundary-neg-biginteger'] = -129n; + +// Provider-defined type values +model['uint8-primitive-pdt'] = new PrimitivePDT('Uint8', '10'); +model['point-composite-pdt'] = new CompositePDT('Point', { x: 1, y: 2 }); // Byte values (JS has no byte type, deserializes to Number) model['min-byte'] = -128; @@ -64,6 +82,12 @@ model['neg-zero-float'] = -0; // Null model['unspecified-null'] = null; +model['null-int'] = null; +model['null-long'] = null; +model['null-string'] = null; +model['null-list'] = null; +model['null-map'] = null; +model['null-set'] = null; // Boolean values model['true-boolean'] = true; @@ -72,6 +96,7 @@ model['false-boolean'] = false; // String values model['single-byte-string'] = 'abc'; model['mixed-string'] = 'abc\u0391\u0392\u0393'; +model['empty-string'] = ''; // List values model['var-bulklist'] = ['marko', 'josh', 'josh']; @@ -111,6 +136,14 @@ model['var-type-map'] = new Map([ ['test', 123] ]); model['empty-map'] = new Map(); +model['ordered-string-int-map'] = new Map([ + ['delta', 4], + ['alpha', 1], + ['charlie', 3], + ['bravo', 2], + ['echo', 5], + ['foxtrot', 6] +]); // Path values model['traversal-path'] = new Path( @@ -118,6 +151,8 @@ model['traversal-path'] = new Path( [new Vertex(1, 'person'), new Vertex(10, 'software'), new Vertex(11, 'software')] ); model['empty-path'] = new Path([], []); +model['path-zero-labels'] = new Path([new Set()], ['marko']); +model['path-multiple-labels'] = new Path([new Set(['a', 'b'])], ['marko']); // Tree values // tree for g.V(10).out().tree(): v[10] -> v[11] @@ -125,6 +160,16 @@ const traversalTree = new Tree(); traversalTree.getOrCreateChild(new Vertex(10, 'software')).getOrCreateChild(new Vertex(11, 'software')); model['traversal-tree'] = traversalTree; model['empty-tree'] = new Tree(); +const treeNullKey = new Tree(); +treeNullKey.getOrCreateChild(null); +model['tree-null-key'] = treeNullKey; +const treeMixedKeyTypes = new Tree(); +treeMixedKeyTypes.getOrCreateChild('name'); +treeMixedKeyTypes.getOrCreateChild(123); +model['tree-mixed-key-types'] = treeMixedKeyTypes; +const treeDeepNesting = new Tree(); +treeDeepNesting.getOrCreateChild('root').getOrCreateChild('branch').getOrCreateChild('leaf'); +model['tree-deep-nesting'] = treeDeepNesting; // Complex path with nested properties const propPathVertex = new Vertex(1, 'person', [ @@ -183,6 +228,10 @@ model['set-cardinality-vertexproperty'] = new VertexProperty(1, 'person', new Se // Enum values model['id-t'] = t.id; model['out-direction'] = direction.out; +model['merge-on-create'] = merge.onCreate; +model['merge-on-match'] = merge.onMatch; +model['merge-out-v'] = merge.outV; +model['merge-in-v'] = merge.inV; // Complex vertex with properties (from .gbin deserialization structure) const name = new VertexProperty(0, 'name', 'marko'); @@ -202,9 +251,86 @@ const santaFe = new VertexProperty(9, 'location', 'santa fe', [ new Property('startTime', 2005) ]); model['traversal-vertex'] = new Vertex(1, 'person', [name, sanDiego, santaCruz, brussels, santaFe]); +model['multi-label-vertex'] = new Vertex(1, ['person', 'employee']); +model['empty-label-vertex'] = new Vertex(1, 'vertex', [], []); // DateTime values (invalid dates for overflow cases) model['max-offsetdatetime'] = new Date(NaN); // Year 999999999 overflows JS Date model['min-offsetdatetime'] = new Date(NaN); // Year -999999999 overflows JS Date -export { model }; \ No newline at end of file +class CrewGraphFactory { + static vertexProperty(id, label, value, metaProperties = []) { + return new VertexProperty( + id, + label, + value, + metaProperties.map(([key, metaValue]) => new Property(key, metaValue)) + ); + } + + static addVertex(graph, id, label, propertySpecs) { + const vertex = new Vertex(id, label); + for (const spec of propertySpecs) { + vertex.properties.push(CrewGraphFactory.vertexProperty(spec[0], spec[1], spec[2], spec[3] || [])); + } + graph.vertices.set(id, vertex); + return vertex; + } + + static addEdge(graph, id, outV, label, inV, properties = []) { + const edge = new Edge(id, outV, label, inV, properties.map(([key, value]) => new Property(key, value))); + graph.edges.set(id, edge); + return edge; + } + + static create() { + const graph = new Graph(); + const v1 = CrewGraphFactory.addVertex(graph, 1, 'person', [ + [0, 'name', 'marko'], + [6, 'location', 'san diego', [['startTime', 1997], ['endTime', 2001]]], + [7, 'location', 'santa cruz', [['startTime', 2001], ['endTime', 2004]]], + [8, 'location', 'brussels', [['startTime', 2004], ['endTime', 2005]]], + [9, 'location', 'santa fe', [['startTime', 2005]]] + ]); + const v7 = CrewGraphFactory.addVertex(graph, 7, 'person', [ + [1, 'name', 'stephen'], + [10, 'location', 'centreville', [['startTime', 1990], ['endTime', 2000]]], + [11, 'location', 'dulles', [['startTime', 2000], ['endTime', 2006]]], + [12, 'location', 'purcellville', [['startTime', 2006]]] + ]); + const v8 = CrewGraphFactory.addVertex(graph, 8, 'person', [ + [2, 'name', 'matthias'], + [13, 'location', 'bremen', [['startTime', 2004], ['endTime', 2007]]], + [14, 'location', 'baltimore', [['startTime', 2007], ['endTime', 2011]]], + [15, 'location', 'oakland', [['startTime', 2011], ['endTime', 2014]]], + [16, 'location', 'seattle', [['startTime', 2014]]] + ]); + const v9 = CrewGraphFactory.addVertex(graph, 9, 'person', [ + [3, 'name', 'daniel'], + [17, 'location', 'spremberg', [['startTime', 1982], ['endTime', 2005]]], + [18, 'location', 'kaiserslautern', [['startTime', 2005], ['endTime', 2009]]], + [19, 'location', 'aachen', [['startTime', 2009]]] + ]); + const v10 = CrewGraphFactory.addVertex(graph, 10, 'software', [[4, 'name', 'gremlin']]); + const v11 = CrewGraphFactory.addVertex(graph, 11, 'software', [[5, 'name', 'tinkergraph']]); + CrewGraphFactory.addEdge(graph, 13, v1, 'develops', v10, [['since', 2009]]); + CrewGraphFactory.addEdge(graph, 14, v1, 'develops', v11, [['since', 2010]]); + CrewGraphFactory.addEdge(graph, 15, v1, 'uses', v10, [['skill', 4]]); + CrewGraphFactory.addEdge(graph, 16, v1, 'uses', v11, [['skill', 5]]); + CrewGraphFactory.addEdge(graph, 17, v7, 'develops', v10, [['since', 2010]]); + CrewGraphFactory.addEdge(graph, 18, v7, 'develops', v11, [['since', 2011]]); + CrewGraphFactory.addEdge(graph, 19, v7, 'uses', v10, [['skill', 5]]); + CrewGraphFactory.addEdge(graph, 20, v7, 'uses', v11, [['skill', 4]]); + CrewGraphFactory.addEdge(graph, 21, v8, 'develops', v10, [['since', 2012]]); + CrewGraphFactory.addEdge(graph, 22, v8, 'uses', v10, [['skill', 3]]); + CrewGraphFactory.addEdge(graph, 23, v8, 'uses', v11, [['skill', 3]]); + CrewGraphFactory.addEdge(graph, 24, v9, 'uses', v10, [['skill', 5]]); + CrewGraphFactory.addEdge(graph, 25, v9, 'uses', v11, [['skill', 3]]); + CrewGraphFactory.addEdge(graph, 26, v10, 'traverses', v11); + return graph; + } +} + +model['tinker-graph'] = CrewGraphFactory.create(); + +export { model }; diff --git a/gremlin-python/src/main/python/gremlin_python/structure/io/graphbinaryV4.py b/gremlin-python/src/main/python/gremlin_python/structure/io/graphbinaryV4.py index 90556996d1..1cf5b0f856 100644 --- a/gremlin-python/src/main/python/gremlin_python/structure/io/graphbinaryV4.py +++ b/gremlin-python/src/main/python/gremlin_python/structure/io/graphbinaryV4.py @@ -1060,4 +1060,4 @@ class PrimitivePDTIO(_GraphBinaryTypeIO): def _read_primitive_pdt(cls, b, r): name = r.read_object(b) value = r.read_object(b) - return PrimitivePDT(name, value) \ No newline at end of file + return PrimitivePDT(name, value) diff --git a/gremlin-python/src/main/python/tests/unit/structure/io/model.py b/gremlin-python/src/main/python/tests/unit/structure/io/model.py index 38200f5149..8410872d06 100644 --- a/gremlin-python/src/main/python/tests/unit/structure/io/model.py +++ b/gremlin-python/src/main/python/tests/unit/structure/io/model.py @@ -19,13 +19,13 @@ under the License. import datetime import uuid -from gremlin_python.statics import short, long, bigint, BigDecimal, SingleByte -from gremlin_python.structure.graph import Vertex, Edge, Property, VertexProperty, Path, Tree -from gremlin_python.process.traversal import Direction, T +from collections import OrderedDict +from gremlin_python.statics import short, long, bigint, BigDecimal, SingleByte, SingleChar +from gremlin_python.structure.graph import Graph, Vertex, Edge, Property, VertexProperty, Path, Tree, CompositePDT, PrimitivePDT +from gremlin_python.process.traversal import Direction, Merge, T """ The following models aren't supported. -tinker-graph Graph type not implemented max-offsetdatetime Too large for datetime min-offsetdatetime Too small for datetime forever-duration Too large for duration @@ -43,8 +43,17 @@ model = {} model["pos-bigdecimal"] = BigDecimal(33, 123456789987654321123456789987654321) model["neg-bigdecimal"] = BigDecimal(33, -123456789987654321123456789987654321) +model["zero-bigdecimal"] = BigDecimal(0, 0) +model["scale-zero-bigdecimal"] = BigDecimal(0, 1234) +model["negative-scale-bigdecimal"] = BigDecimal(-2, 1234) +model["small-decimal-bigdecimal"] = BigDecimal(2, 1234) model["pos-biginteger"] = bigint(123456789987654321123456789987654321) model["neg-biginteger"] = bigint(-123456789987654321123456789987654321) +model["zero-biginteger"] = bigint(0) +model["sign-boundary-pos-biginteger"] = bigint(128) +model["sign-boundary-neg-biginteger"] = bigint(-129) +model["uint8-primitive-pdt"] = PrimitivePDT("Uint8", "10") +model["point-composite-pdt"] = CompositePDT("Point", {"x": 1, "y": 2}) model["min-byte"] = SingleByte(-128) model["max-byte"] = SingleByte(127) model["empty-binary"] = bytes("", "utf8") @@ -58,15 +67,27 @@ model["pos-inf-double"] = float('inf') model["neg-inf-double"] = float('-inf') model["neg-zero-double"] = -0.0 model["single-byte-char"] = 'a' -model["multi-byte-char"] = '\u03A9' +model["two-byte-char"] = '\u03A9' +model["three-byte-char"] = SingleChar('\u20AC') +model["four-byte-char"] = SingleChar('\U0001F600') model["unspecified-null"] = None +model["null-int"] = None +model["null-long"] = None +model["null-string"] = None +model["null-list"] = None +model["null-map"] = None +model["null-set"] = None model["true-boolean"] = True model["false-boolean"] = False model["single-byte-string"] = "abc" model["mixed-string"] = "abc\u0391\u0392\u0393" +model["empty-string"] = "" model["var-bulklist"] = ["marko", "josh", "josh"] model["empty-bulklist"] = [] model["zero-duration"] = datetime.timedelta() +model["positive-duration"] = datetime.timedelta(seconds=123) +model["negative-duration"] = datetime.timedelta(seconds=-123) +model["nanos-duration"] = datetime.timedelta(seconds=123, microseconds=456.789) model["traversal-edge"] = Edge( 13, Vertex(1, 'person'), @@ -93,17 +114,37 @@ model["var-type-map"] = { None: None } model["empty-map"] = {} +model["ordered-string-int-map"] = OrderedDict([ + ("delta", 4), + ("alpha", 1), + ("charlie", 3), + ("bravo", 2), + ("echo", 5), + ("foxtrot", 6) +]) model["traversal-path"] = Path( [set(), set(), set()], [Vertex(1, "person"), Vertex(10, "software"), Vertex(11, "software")] ) model["empty-path"] = Path([], []) +model["path-zero-labels"] = Path([set()], ["marko"]) +model["path-multiple-labels"] = Path([{"a", "b"}], ["marko"]) # tree for g.V(10).out().tree(): v[10] -> v[11] _traversal_tree = Tree() _traversal_tree.get_or_create_child(Vertex(10, "software")).get_or_create_child(Vertex(11, "software")) model["traversal-tree"] = _traversal_tree model["empty-tree"] = Tree() +_tree_null_key = Tree() +_tree_null_key.get_or_create_child(None) +model["tree-null-key"] = _tree_null_key +_tree_mixed_key_types = Tree() +_tree_mixed_key_types.get_or_create_child("name") +_tree_mixed_key_types.get_or_create_child(123) +model["tree-mixed-key-types"] = _tree_mixed_key_types +_tree_deep_nesting = Tree() +_tree_deep_nesting.get_or_create_child("root").get_or_create_child("branch").get_or_create_child("leaf") +model["tree-deep-nesting"] = _tree_deep_nesting model["prop-path"] = Path( [set(), set(), set()], @@ -135,6 +176,10 @@ model["meta-vertexproperty"] = VertexProperty(1, "person", "stephen", None, [Pro model["set-cardinality-vertexproperty"] = VertexProperty(1, "person", {"stephen", "marko"}, None, [Property("a", "b", None)]) model["id-t"] = T.id model["out-direction"] = Direction.OUT +model["merge-on-create"] = Merge.on_create +model["merge-on-match"] = Merge.on_match +model["merge-out-v"] = Merge.out_v +model["merge-in-v"] = Merge.in_v santa_fe = VertexProperty( 9, "location", "santa fe", None, @@ -158,3 +203,82 @@ model["traversal-vertex"] = Vertex(1, "person", [ Property("name", name, None), Property("location", [ san_diego, santa_cruz, brussels, santa_fe ], None)]) +model["multi-label-vertex"] = Vertex(1, labels=["person", "employee"]) +model["empty-label-vertex"] = Vertex(1, labels=[]) + + +class _CrewGraphFactory: + @staticmethod + def vertex_property(id, label, value, vertex, meta_properties=None): + vp = VertexProperty(id, label, value, vertex) + for key, meta_value in meta_properties or []: + vp.properties.append(Property(key, meta_value, vp)) + return vp + + @staticmethod + def add_vertex(graph, id, label, property_specs): + vertex = Vertex(id, label) + for spec in property_specs: + vertex.properties.append( + _CrewGraphFactory.vertex_property( + spec[0], spec[1], spec[2], vertex, spec[3] if len(spec) > 3 else [])) + graph.vertices[id] = vertex + return vertex + + @staticmethod + def add_edge(graph, id, out_v, label, in_v, properties=None): + edge = Edge(id, out_v, label, in_v) + for key, value in properties or []: + edge.properties.append(Property(key, value, edge)) + graph.edges[id] = edge + return edge + + @staticmethod + def create(): + graph = Graph() + v1 = _CrewGraphFactory.add_vertex(graph, 1, "person", [ + (0, "name", "marko"), + (6, "location", "san diego", [("startTime", 1997), ("endTime", 2001)]), + (7, "location", "santa cruz", [("startTime", 2001), ("endTime", 2004)]), + (8, "location", "brussels", [("startTime", 2004), ("endTime", 2005)]), + (9, "location", "santa fe", [("startTime", 2005)]) + ]) + v7 = _CrewGraphFactory.add_vertex(graph, 7, "person", [ + (1, "name", "stephen"), + (10, "location", "centreville", [("startTime", 1990), ("endTime", 2000)]), + (11, "location", "dulles", [("startTime", 2000), ("endTime", 2006)]), + (12, "location", "purcellville", [("startTime", 2006)]) + ]) + v8 = _CrewGraphFactory.add_vertex(graph, 8, "person", [ + (2, "name", "matthias"), + (13, "location", "bremen", [("startTime", 2004), ("endTime", 2007)]), + (14, "location", "baltimore", [("startTime", 2007), ("endTime", 2011)]), + (15, "location", "oakland", [("startTime", 2011), ("endTime", 2014)]), + (16, "location", "seattle", [("startTime", 2014)]) + ]) + v9 = _CrewGraphFactory.add_vertex(graph, 9, "person", [ + (3, "name", "daniel"), + (17, "location", "spremberg", [("startTime", 1982), ("endTime", 2005)]), + (18, "location", "kaiserslautern", [("startTime", 2005), ("endTime", 2009)]), + (19, "location", "aachen", [("startTime", 2009)]) + ]) + v10 = _CrewGraphFactory.add_vertex(graph, 10, "software", [(4, "name", "gremlin")]) + v11 = _CrewGraphFactory.add_vertex(graph, 11, "software", [(5, "name", "tinkergraph")]) + _CrewGraphFactory.add_edge(graph, 13, v1, "develops", v10, [("since", 2009)]) + _CrewGraphFactory.add_edge(graph, 14, v1, "develops", v11, [("since", 2010)]) + _CrewGraphFactory.add_edge(graph, 15, v1, "uses", v10, [("skill", 4)]) + _CrewGraphFactory.add_edge(graph, 16, v1, "uses", v11, [("skill", 5)]) + _CrewGraphFactory.add_edge(graph, 17, v7, "develops", v10, [("since", 2010)]) + _CrewGraphFactory.add_edge(graph, 18, v7, "develops", v11, [("since", 2011)]) + _CrewGraphFactory.add_edge(graph, 19, v7, "uses", v10, [("skill", 5)]) + _CrewGraphFactory.add_edge(graph, 20, v7, "uses", v11, [("skill", 4)]) + _CrewGraphFactory.add_edge(graph, 21, v8, "develops", v10, [("since", 2012)]) + _CrewGraphFactory.add_edge(graph, 22, v8, "uses", v10, [("skill", 3)]) + _CrewGraphFactory.add_edge(graph, 23, v8, "uses", v11, [("skill", 3)]) + _CrewGraphFactory.add_edge(graph, 24, v9, "uses", v10, [("skill", 5)]) + _CrewGraphFactory.add_edge(graph, 25, v9, "uses", v11, [("skill", 3)]) + _CrewGraphFactory.add_edge(graph, 26, v10, "traverses", v11) + return graph + + +model["tinker-graph"] = _CrewGraphFactory.create() diff --git a/gremlin-python/src/main/python/tests/unit/structure/io/test_graphbinaryv4model.py b/gremlin-python/src/main/python/tests/unit/structure/io/test_graphbinaryv4model.py index 0e4935a916..dc8ed2a34f 100644 --- a/gremlin-python/src/main/python/tests/unit/structure/io/test_graphbinaryv4model.py +++ b/gremlin-python/src/main/python/tests/unit/structure/io/test_graphbinaryv4model.py @@ -38,6 +38,37 @@ test_resource_directory = os.environ.get('IO_TEST_DIRECTORY', default_dir + grem writer = GraphBinaryWriter() reader = GraphBinaryReader() +def decimal_cmp(x, y): + return x.scale == y.scale and x.unscaled_value == y.unscaled_value and x.value == y.value + +def vertex_label_cmp(x, y): + return x == y and x.labels == y.labels + +def property_cmp(x, y): + return x.key == y.key and x.value == y.value + +def vertex_property_cmp(x, y): + return x == y and x.label == y.label and x.value == y.value and \ + len(x.properties) == len(y.properties) and \ + all(property_cmp(xp, yp) for xp, yp in zip(x.properties, y.properties)) + +def vertex_cmp(x, y): + return x == y and x.label == y.label and x.labels == y.labels and \ + len(x.properties) == len(y.properties) and \ + all(vertex_property_cmp(xp, yp) for xp, yp in zip(x.properties, y.properties)) + +def edge_cmp(x, y): + return x == y and x.label == y.label and x.labels == y.labels and \ + x.outV == y.outV and x.inV == y.inV and \ + len(x.properties) == len(y.properties) and \ + all(property_cmp(xp, yp) for xp, yp in zip(x.properties, y.properties)) + +def graph_cmp(x, y): + return x.vertices.keys() == y.vertices.keys() and \ + x.edges.keys() == y.edges.keys() and \ + all(vertex_cmp(x.vertices[k], y.vertices[k]) for k in x.vertices.keys()) and \ + all(edge_cmp(x.edges[k], y.edges[k]) for k in x.edges.keys()) + def get_entry(title): return model[title] @@ -47,23 +78,29 @@ def read_file_by_name(resource_name): return bytearray(resource_file.read()) def test_pos_bigdecimal(): - def decimal_cmp(x, y): - if x.scale == y.scale and x.unscaled_value == y.unscaled_value and x.value == y.value: - return True - else: - return False - + # BigDecimal writes through Python BigInteger, which may not preserve byte-exact sign padding. run_writeread("pos-bigdecimal", decimal_cmp) def test_neg_bigdecimal(): - def decimal_cmp(x, y): - if x.scale == y.scale and x.unscaled_value == y.unscaled_value and x.value == y.value: - return True - else: - return False - + # BigDecimal writes through Python BigInteger, which may not preserve byte-exact sign padding. run_writeread("neg-bigdecimal", decimal_cmp) +def test_zero_bigdecimal(): + # Use the same reduced mode as other Python BigDecimal model cases. + run_writeread("zero-bigdecimal", decimal_cmp) + +def test_scale_zero_bigdecimal(): + # Use the same reduced mode as other Python BigDecimal model cases. + run_writeread("scale-zero-bigdecimal", decimal_cmp) + +def test_negative_scale_bigdecimal(): + # Use the same reduced mode as other Python BigDecimal model cases. + run_writeread("negative-scale-bigdecimal", decimal_cmp) + +def test_small_decimal_bigdecimal(): + # Use the same reduced mode as other Python BigDecimal model cases. + run_writeread("small-decimal-bigdecimal", decimal_cmp) + def test_pos_biginteger(): # gremlin-python adds an extra 0 byte to the value. run_writeread("pos-biginteger") @@ -72,6 +109,25 @@ def test_neg_biginteger(): # gremlin-python adds an extra 0 byte to the value. run_writeread("neg-biginteger") +def test_zero_biginteger(): + # gremlin-python may add an extra sign-padding byte to the value. + run_writeread("zero-biginteger") + +def test_sign_boundary_pos_biginteger(): + # gremlin-python may add an extra sign-padding byte to the value. + run_writeread("sign-boundary-pos-biginteger") + +def test_sign_boundary_neg_biginteger(): + # gremlin-python can't serialize -129 because BigIntIO computes a byte length + # that is too small for signed two's-complement encoding. + run_read("sign-boundary-neg-biginteger") + +def test_uint8_primitive_pdt(): + run("uint8-primitive-pdt") + +def test_point_composite_pdt(): + run("point-composite-pdt") + def test_min_byte(): run("min-byte") @@ -116,6 +172,17 @@ def test_neg_zero_double(): def test_zero_duration(): run("zero-duration") +def test_positive_duration(): + run("positive-duration") + +def test_negative_duration(): + # timedelta.seconds normalizes negative values, so the writer can't preserve the wire value. + run_read("negative-duration") + +def test_nanos_duration(): + # Python timedelta only preserves microseconds, not arbitrary nanoseconds. + run_read("nanos-duration") + def test_traversal_edge(): # properties aren't serialized in gremlin-python run_writeread("traversal-edge") @@ -151,6 +218,9 @@ def test_var_type_map(): def test_empty_map(): run("empty-map") +def test_ordered_string_int_map(): + run("ordered-string-int-map") + def test_traversal_path(): # gremlin-python serializes/deserializes "null" for props not empty list run_writeread("traversal-path") @@ -158,19 +228,29 @@ def test_traversal_path(): def test_empty_path(): run("empty-path") +def test_path_zero_labels(): + run("path-zero-labels") + +def test_path_multiple_labels(): + # label set ordering isn't guaranteed after a Python object round trip. + run_writeread("path-multiple-labels") + def test_traversal_tree(): # gremlin-python doesn't serialize vertex properties, so the read tree's # vertices have no properties; vertex equality is by id so the model matches. run_writeread("traversal-tree") def test_empty_tree(): - # no .gbin resource exists for an empty tree, so exercise an in-memory round trip - from gremlin_python.structure.graph import Tree - empty = model["empty-tree"] - round_tripped = reader.read_object(writer.write_object(empty)) - assert isinstance(round_tripped, Tree) - assert empty == round_tripped - assert round_tripped.is_leaf() + run("empty-tree") + +def test_tree_null_key(): + run("tree-null-key") + +def test_tree_mixed_key_types(): + run("tree-mixed-key-types") + +def test_tree_deep_nesting(): + run("tree-deep-nesting") def test_prop_path(): # gremlin-python doesn't serialize properties @@ -226,23 +306,57 @@ def test_out_direction(): run("out-direction") def test_var_bulklist(): + # BulkList is deserialized as a plain list, so the writer emits a regular List. run_read("var-bulklist") def test_empty_bulklist(): + # BulkList is deserialized as a plain list, so the writer emits a regular List. run_read("empty-bulklist") def test_single_byte_char(): # char is serialized as string run_writeread("single-byte-char") -def test_multi_byte_char(): +def test_two_byte_char(): # char is serialized as string - run_writeread("multi-byte-char") + run_writeread("two-byte-char") + +def test_three_byte_char(): + # char is serialized as string + run_writeread("three-byte-char") + +def test_four_byte_char(): + # char is serialized as string + run_writeread("four-byte-char") def test_unspecified_null(): # no serializer for plain null run_writeread("unspecified-null") +def test_null_int(): + # typed nulls deserialize to plain None, so the original type can't be re-emitted. + run_read("null-int") + +def test_null_long(): + # typed nulls deserialize to plain None, so the original type can't be re-emitted. + run_read("null-long") + +def test_null_string(): + # typed nulls deserialize to plain None, so the original type can't be re-emitted. + run_read("null-string") + +def test_null_list(): + # typed nulls deserialize to plain None, so the original type can't be re-emitted. + run_read("null-list") + +def test_null_map(): + # typed nulls deserialize to plain None, so the original type can't be re-emitted. + run_read("null-map") + +def test_null_set(): + # typed nulls deserialize to plain None, so the original type can't be re-emitted. + run_read("null-set") + def test_true_boolean(): run("true-boolean") @@ -255,6 +369,38 @@ def test_single_byte_string(): def test_mixed_string(): run("mixed-string") +def test_empty_string(): + run("empty-string") + +def test_traversal_vertex(): + # properties aren't serialized in gremlin-python + run_writeread("traversal-vertex") + +def test_multi_label_vertex(): + # Vertex equality is id-only, so compare labels explicitly. + run_writeread("multi-label-vertex", vertex_label_cmp) + +def test_empty_label_vertex(): + # Vertex equality is id-only, so compare labels explicitly. + run_writeread("empty-label-vertex", vertex_label_cmp) + +def test_tinker_graph(): + # The hand-built graph is structurally equivalent, but it doesn't preserve + # every wire-level numeric type tag needed for byte-exact writes. + run_writeread("tinker-graph", graph_cmp) + +def test_merge_on_create(): + run("merge-on-create") + +def test_merge_on_match(): + run("merge-on-match") + +def test_merge_out_v(): + run("merge-out-v") + +def test_merge_in_v(): + run("merge-in-v") + def run(resource_name, comparator = None): """ Runs the regular set of tests for the type which is diff --git a/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/GremlinServerSerializationIntegrateTest.java b/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/GremlinServerSerializationIntegrateTest.java index 9e41f45d79..bbcbfd5738 100644 --- a/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/GremlinServerSerializationIntegrateTest.java +++ b/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/GremlinServerSerializationIntegrateTest.java @@ -338,8 +338,11 @@ public class GremlinServerSerializationIntegrateTest extends AbstractGremlinServ assertEquals("g:CompositePdt", pdtNode.get("@type").asText()); final JsonNode value = pdtNode.get(GraphSONTokens.VALUEPROP); assertEquals("Point", value.get("type").asText()); - assertEquals(1, value.get("fields").get("x").get(GraphSONTokens.VALUEPROP).intValue()); - assertEquals(2, value.get("fields").get("y").get(GraphSONTokens.VALUEPROP).intValue()); + final JsonNode fields = value.get("fields").get(GraphSONTokens.VALUEPROP); + assertEquals("x", fields.get(0).asText()); + assertEquals(1, fields.get(1).get(GraphSONTokens.VALUEPROP).intValue()); + assertEquals("y", fields.get(2).asText()); + assertEquals(2, fields.get(3).get(GraphSONTokens.VALUEPROP).intValue()); } } diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphbinary/empty-label-vertex-v4.gbin b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphbinary/empty-label-vertex-v4.gbin new file mode 100644 index 0000000000..eae30101be Binary files /dev/null and b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphbinary/empty-label-vertex-v4.gbin differ diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphbinary/empty-string-v4.gbin b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphbinary/empty-string-v4.gbin new file mode 100644 index 0000000000..f8287f3da7 Binary files /dev/null and b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphbinary/empty-string-v4.gbin differ diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphbinary/empty-tree-v4.gbin b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphbinary/empty-tree-v4.gbin new file mode 100644 index 0000000000..4be0227de7 Binary files /dev/null and b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphbinary/empty-tree-v4.gbin differ diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphbinary/four-byte-char-v4.gbin b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphbinary/four-byte-char-v4.gbin new file mode 100644 index 0000000000..ed5831f36f Binary files /dev/null and b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphbinary/four-byte-char-v4.gbin differ diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphbinary/merge-in-v-v4.gbin b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphbinary/merge-in-v-v4.gbin new file mode 100644 index 0000000000..7998e4009f Binary files /dev/null and b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphbinary/merge-in-v-v4.gbin differ diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphbinary/merge-on-create-v4.gbin b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphbinary/merge-on-create-v4.gbin new file mode 100644 index 0000000000..af8f22e257 Binary files /dev/null and b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphbinary/merge-on-create-v4.gbin differ diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphbinary/merge-on-match-v4.gbin b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphbinary/merge-on-match-v4.gbin new file mode 100644 index 0000000000..e31b45bfb2 Binary files /dev/null and b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphbinary/merge-on-match-v4.gbin differ diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphbinary/merge-out-v-v4.gbin b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphbinary/merge-out-v-v4.gbin new file mode 100644 index 0000000000..fafab80fb0 Binary files /dev/null and b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphbinary/merge-out-v-v4.gbin differ diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphbinary/multi-label-vertex-v4.gbin b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphbinary/multi-label-vertex-v4.gbin new file mode 100644 index 0000000000..f18f8fbbbd Binary files /dev/null and b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphbinary/multi-label-vertex-v4.gbin differ diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphbinary/nanos-duration-v4.gbin b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphbinary/nanos-duration-v4.gbin new file mode 100644 index 0000000000..e73df638e5 Binary files /dev/null and b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphbinary/nanos-duration-v4.gbin differ diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphbinary/negative-duration-v4.gbin b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphbinary/negative-duration-v4.gbin new file mode 100644 index 0000000000..16d08d701a Binary files /dev/null and b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphbinary/negative-duration-v4.gbin differ diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphbinary/negative-scale-bigdecimal-v4.gbin b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphbinary/negative-scale-bigdecimal-v4.gbin new file mode 100644 index 0000000000..9cb1daf9e2 Binary files /dev/null and b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphbinary/negative-scale-bigdecimal-v4.gbin differ diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphbinary/null-int-v4.gbin b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphbinary/null-int-v4.gbin new file mode 100644 index 0000000000..40fdece9d2 --- /dev/null +++ b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphbinary/null-int-v4.gbin @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphbinary/null-list-v4.gbin b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphbinary/null-list-v4.gbin new file mode 100644 index 0000000000..93938cf057 --- /dev/null +++ b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphbinary/null-list-v4.gbin @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphbinary/null-long-v4.gbin b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphbinary/null-long-v4.gbin new file mode 100644 index 0000000000..03afaa5de0 --- /dev/null +++ b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphbinary/null-long-v4.gbin @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphbinary/null-map-v4.gbin b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphbinary/null-map-v4.gbin new file mode 100644 index 0000000000..bbe779818e --- /dev/null +++ b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphbinary/null-map-v4.gbin @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphbinary/null-set-v4.gbin b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphbinary/null-set-v4.gbin new file mode 100644 index 0000000000..d67137bfd2 --- /dev/null +++ b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphbinary/null-set-v4.gbin @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphbinary/null-string-v4.gbin b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphbinary/null-string-v4.gbin new file mode 100644 index 0000000000..d8d3825962 --- /dev/null +++ b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphbinary/null-string-v4.gbin @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphbinary/ordered-string-int-map-v4.gbin b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphbinary/ordered-string-int-map-v4.gbin new file mode 100644 index 0000000000..1e8097ca81 Binary files /dev/null and b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphbinary/ordered-string-int-map-v4.gbin differ diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphbinary/path-multiple-labels-v4.gbin b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphbinary/path-multiple-labels-v4.gbin new file mode 100644 index 0000000000..a01de6154a Binary files /dev/null and b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphbinary/path-multiple-labels-v4.gbin differ diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphbinary/path-zero-labels-v4.gbin b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphbinary/path-zero-labels-v4.gbin new file mode 100644 index 0000000000..a456ed2bb5 Binary files /dev/null and b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphbinary/path-zero-labels-v4.gbin differ diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphbinary/point-composite-pdt-v4.gbin b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphbinary/point-composite-pdt-v4.gbin new file mode 100644 index 0000000000..3c5c3a5eb4 Binary files /dev/null and b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphbinary/point-composite-pdt-v4.gbin differ diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphbinary/positive-duration-v4.gbin b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphbinary/positive-duration-v4.gbin new file mode 100644 index 0000000000..11b750d97a Binary files /dev/null and b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphbinary/positive-duration-v4.gbin differ diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphbinary/scale-zero-bigdecimal-v4.gbin b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphbinary/scale-zero-bigdecimal-v4.gbin new file mode 100644 index 0000000000..18bbdc206b Binary files /dev/null and b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphbinary/scale-zero-bigdecimal-v4.gbin differ diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphbinary/sign-boundary-neg-biginteger-v4.gbin b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphbinary/sign-boundary-neg-biginteger-v4.gbin new file mode 100644 index 0000000000..fbf5e60d4e Binary files /dev/null and b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphbinary/sign-boundary-neg-biginteger-v4.gbin differ diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphbinary/sign-boundary-pos-biginteger-v4.gbin b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphbinary/sign-boundary-pos-biginteger-v4.gbin new file mode 100644 index 0000000000..afcf9b041e Binary files /dev/null and b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphbinary/sign-boundary-pos-biginteger-v4.gbin differ diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphbinary/small-decimal-bigdecimal-v4.gbin b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphbinary/small-decimal-bigdecimal-v4.gbin new file mode 100644 index 0000000000..321d4cfc1c Binary files /dev/null and b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphbinary/small-decimal-bigdecimal-v4.gbin differ diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphbinary/three-byte-char-v4.gbin b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphbinary/three-byte-char-v4.gbin new file mode 100644 index 0000000000..e4927c407b Binary files /dev/null and b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphbinary/three-byte-char-v4.gbin differ diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphbinary/tree-deep-nesting-v4.gbin b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphbinary/tree-deep-nesting-v4.gbin new file mode 100644 index 0000000000..72b0f37865 Binary files /dev/null and b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphbinary/tree-deep-nesting-v4.gbin differ diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphbinary/tree-mixed-key-types-v4.gbin b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphbinary/tree-mixed-key-types-v4.gbin new file mode 100644 index 0000000000..40f0ec93ea Binary files /dev/null and b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphbinary/tree-mixed-key-types-v4.gbin differ diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphbinary/tree-null-key-v4.gbin b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphbinary/tree-null-key-v4.gbin new file mode 100644 index 0000000000..0d7f35dfa4 Binary files /dev/null and b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphbinary/tree-null-key-v4.gbin differ diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphbinary/multi-byte-char-v4.gbin b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphbinary/two-byte-char-v4.gbin similarity index 100% rename from gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphbinary/multi-byte-char-v4.gbin rename to gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphbinary/two-byte-char-v4.gbin diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphbinary/uint8-primitive-pdt-v4.gbin b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphbinary/uint8-primitive-pdt-v4.gbin new file mode 100644 index 0000000000..eb81eea59c Binary files /dev/null and b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphbinary/uint8-primitive-pdt-v4.gbin differ diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphbinary/zero-bigdecimal-v4.gbin b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphbinary/zero-bigdecimal-v4.gbin new file mode 100644 index 0000000000..1e3af93198 Binary files /dev/null and b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphbinary/zero-bigdecimal-v4.gbin differ diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphbinary/zero-biginteger-v4.gbin b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphbinary/zero-biginteger-v4.gbin new file mode 100644 index 0000000000..8cd75a9567 Binary files /dev/null and b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphbinary/zero-biginteger-v4.gbin differ diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphson/empty-label-vertex-v4.json b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphson/empty-label-vertex-v4.json new file mode 100644 index 0000000000..0d729d3637 --- /dev/null +++ b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphson/empty-label-vertex-v4.json @@ -0,0 +1,10 @@ +{ + "@type" : "g:Vertex", + "@value" : { + "id" : { + "@type" : "g:Int32", + "@value" : 1 + }, + "label" : [ ] + } +} \ No newline at end of file diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphson/empty-string-v4.json b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphson/empty-string-v4.json new file mode 100644 index 0000000000..3cc762b550 --- /dev/null +++ b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphson/empty-string-v4.json @@ -0,0 +1 @@ +"" \ No newline at end of file diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphson/merge-in-v-v4.json b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphson/merge-in-v-v4.json new file mode 100644 index 0000000000..3f56a2d5e1 --- /dev/null +++ b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphson/merge-in-v-v4.json @@ -0,0 +1,4 @@ +{ + "@type" : "g:Merge", + "@value" : "inV" +} \ No newline at end of file diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphson/merge-on-create-v4.json b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphson/merge-on-create-v4.json new file mode 100644 index 0000000000..b1f1094b4d --- /dev/null +++ b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphson/merge-on-create-v4.json @@ -0,0 +1,4 @@ +{ + "@type" : "g:Merge", + "@value" : "onCreate" +} \ No newline at end of file diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphson/merge-on-match-v4.json b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphson/merge-on-match-v4.json new file mode 100644 index 0000000000..330dea4040 --- /dev/null +++ b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphson/merge-on-match-v4.json @@ -0,0 +1,4 @@ +{ + "@type" : "g:Merge", + "@value" : "onMatch" +} \ No newline at end of file diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphson/merge-out-v-v4.json b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphson/merge-out-v-v4.json new file mode 100644 index 0000000000..fa6342fd4e --- /dev/null +++ b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphson/merge-out-v-v4.json @@ -0,0 +1,4 @@ +{ + "@type" : "g:Merge", + "@value" : "outV" +} \ No newline at end of file diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphson/multi-label-vertex-v4.json b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphson/multi-label-vertex-v4.json new file mode 100644 index 0000000000..a568881135 --- /dev/null +++ b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphson/multi-label-vertex-v4.json @@ -0,0 +1,10 @@ +{ + "@type" : "g:Vertex", + "@value" : { + "id" : { + "@type" : "g:Int32", + "@value" : 1 + }, + "label" : [ "person", "employee" ] + } +} \ No newline at end of file diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphson/nanos-duration-v4.json b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphson/nanos-duration-v4.json new file mode 100644 index 0000000000..9a7c0a6eac --- /dev/null +++ b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphson/nanos-duration-v4.json @@ -0,0 +1,4 @@ +{ + "@type" : "g:Duration", + "@value" : "PT2M3.000456789S" +} \ No newline at end of file diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphson/negative-duration-v4.json b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphson/negative-duration-v4.json new file mode 100644 index 0000000000..ca738da37b --- /dev/null +++ b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphson/negative-duration-v4.json @@ -0,0 +1,4 @@ +{ + "@type" : "g:Duration", + "@value" : "PT-2M-3S" +} \ No newline at end of file diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphson/negative-scale-bigdecimal-v4.json b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphson/negative-scale-bigdecimal-v4.json new file mode 100644 index 0000000000..e4e57921ae --- /dev/null +++ b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphson/negative-scale-bigdecimal-v4.json @@ -0,0 +1,4 @@ +{ + "@type" : "g:BigDecimal", + "@value" : 1.234E+5 +} \ No newline at end of file diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphson/ordered-string-int-map-v4.json b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphson/ordered-string-int-map-v4.json new file mode 100644 index 0000000000..fa7cc60b6a --- /dev/null +++ b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphson/ordered-string-int-map-v4.json @@ -0,0 +1,22 @@ +{ + "@type" : "g:Map", + "@value" : [ "delta", { + "@type" : "g:Int32", + "@value" : 4 + }, "alpha", { + "@type" : "g:Int32", + "@value" : 1 + }, "charlie", { + "@type" : "g:Int32", + "@value" : 3 + }, "bravo", { + "@type" : "g:Int32", + "@value" : 2 + }, "echo", { + "@type" : "g:Int32", + "@value" : 5 + }, "foxtrot", { + "@type" : "g:Int32", + "@value" : 6 + } ] +} \ No newline at end of file diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphson/path-multiple-labels-v4.json b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphson/path-multiple-labels-v4.json new file mode 100644 index 0000000000..f1fc5c5f95 --- /dev/null +++ b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphson/path-multiple-labels-v4.json @@ -0,0 +1,16 @@ +{ + "@type" : "g:Path", + "@value" : { + "labels" : { + "@type" : "g:List", + "@value" : [ { + "@type" : "g:Set", + "@value" : [ "a", "b" ] + } ] + }, + "objects" : { + "@type" : "g:List", + "@value" : [ "marko" ] + } + } +} \ No newline at end of file diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphson/path-zero-labels-v4.json b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphson/path-zero-labels-v4.json new file mode 100644 index 0000000000..055a9573de --- /dev/null +++ b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphson/path-zero-labels-v4.json @@ -0,0 +1,16 @@ +{ + "@type" : "g:Path", + "@value" : { + "labels" : { + "@type" : "g:List", + "@value" : [ { + "@type" : "g:Set", + "@value" : [ ] + } ] + }, + "objects" : { + "@type" : "g:List", + "@value" : [ "marko" ] + } + } +} \ No newline at end of file diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphson/point-composite-pdt-v4.json b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphson/point-composite-pdt-v4.json new file mode 100644 index 0000000000..39f2b188a8 --- /dev/null +++ b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphson/point-composite-pdt-v4.json @@ -0,0 +1,16 @@ +{ + "@type" : "g:CompositePdt", + "@value" : { + "type" : "Point", + "fields" : { + "@type" : "g:Map", + "@value" : [ "x", { + "@type" : "g:Int32", + "@value" : 1 + }, "y", { + "@type" : "g:Int32", + "@value" : 2 + } ] + } + } +} diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphson/positive-duration-v4.json b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphson/positive-duration-v4.json new file mode 100644 index 0000000000..9d25f26d45 --- /dev/null +++ b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphson/positive-duration-v4.json @@ -0,0 +1,4 @@ +{ + "@type" : "g:Duration", + "@value" : "PT2M3S" +} \ No newline at end of file diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphson/scale-zero-bigdecimal-v4.json b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphson/scale-zero-bigdecimal-v4.json new file mode 100644 index 0000000000..248f75b08b --- /dev/null +++ b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphson/scale-zero-bigdecimal-v4.json @@ -0,0 +1,4 @@ +{ + "@type" : "g:BigDecimal", + "@value" : 1234 +} \ No newline at end of file diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphson/sign-boundary-neg-biginteger-v4.json b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphson/sign-boundary-neg-biginteger-v4.json new file mode 100644 index 0000000000..ac8603df5f --- /dev/null +++ b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphson/sign-boundary-neg-biginteger-v4.json @@ -0,0 +1,4 @@ +{ + "@type" : "g:BigInteger", + "@value" : -129 +} \ No newline at end of file diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphson/sign-boundary-pos-biginteger-v4.json b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphson/sign-boundary-pos-biginteger-v4.json new file mode 100644 index 0000000000..adb4a52396 --- /dev/null +++ b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphson/sign-boundary-pos-biginteger-v4.json @@ -0,0 +1,4 @@ +{ + "@type" : "g:BigInteger", + "@value" : 128 +} \ No newline at end of file diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphson/small-decimal-bigdecimal-v4.json b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphson/small-decimal-bigdecimal-v4.json new file mode 100644 index 0000000000..ff1cd1e182 --- /dev/null +++ b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphson/small-decimal-bigdecimal-v4.json @@ -0,0 +1,4 @@ +{ + "@type" : "g:BigDecimal", + "@value" : 12.34 +} \ No newline at end of file diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphson/multi-byte-char-v4.json b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphson/three-byte-char-v4.json similarity index 54% copy from gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphson/multi-byte-char-v4.json copy to gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphson/three-byte-char-v4.json index 46acefff3e..df42204445 100644 --- a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphson/multi-byte-char-v4.json +++ b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphson/three-byte-char-v4.json @@ -1,4 +1,4 @@ { "@type" : "g:Char", - "@value" : "Ω" + "@value" : "€" } \ No newline at end of file diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphson/multi-byte-char-v4.json b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphson/two-byte-char-v4.json similarity index 100% rename from gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphson/multi-byte-char-v4.json rename to gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphson/two-byte-char-v4.json diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphson/uint8-primitive-pdt-v4.json b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphson/uint8-primitive-pdt-v4.json new file mode 100644 index 0000000000..ae994ba0c3 --- /dev/null +++ b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphson/uint8-primitive-pdt-v4.json @@ -0,0 +1,7 @@ +{ + "@type" : "g:PrimitivePdt", + "@value" : { + "type" : "Uint8", + "value" : "10" + } +} \ No newline at end of file diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphson/zero-bigdecimal-v4.json b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphson/zero-bigdecimal-v4.json new file mode 100644 index 0000000000..ef5815d110 --- /dev/null +++ b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphson/zero-bigdecimal-v4.json @@ -0,0 +1,4 @@ +{ + "@type" : "g:BigDecimal", + "@value" : 0 +} \ No newline at end of file diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphson/zero-biginteger-v4.json b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphson/zero-biginteger-v4.json new file mode 100644 index 0000000000..4c719fdb15 --- /dev/null +++ b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphson/zero-biginteger-v4.json @@ -0,0 +1,4 @@ +{ + "@type" : "g:BigInteger", + "@value" : 0 +} \ No newline at end of file diff --git a/gremlin-util/src/test/java/org/apache/tinkerpop/gremlin/structure/io/AbstractTypedCompatibilityTest.java b/gremlin-util/src/test/java/org/apache/tinkerpop/gremlin/structure/io/AbstractTypedCompatibilityTest.java index 745a65fba3..a986c1b2be 100644 --- a/gremlin-util/src/test/java/org/apache/tinkerpop/gremlin/structure/io/AbstractTypedCompatibilityTest.java +++ b/gremlin-util/src/test/java/org/apache/tinkerpop/gremlin/structure/io/AbstractTypedCompatibilityTest.java @@ -20,6 +20,7 @@ package org.apache.tinkerpop.gremlin.structure.io; import io.netty.buffer.ByteBuf; import io.netty.buffer.UnpooledByteBufAllocator; +import org.apache.tinkerpop.gremlin.process.traversal.Merge; import org.apache.tinkerpop.gremlin.process.traversal.Operator; import org.apache.tinkerpop.gremlin.process.traversal.Order; import org.apache.tinkerpop.gremlin.process.traversal.P; @@ -41,6 +42,8 @@ import org.apache.tinkerpop.gremlin.structure.Vertex; import org.apache.tinkerpop.gremlin.structure.VertexProperty; import org.apache.tinkerpop.gremlin.structure.io.graphbinary.GraphBinaryCompatibilityTest; import org.apache.tinkerpop.gremlin.structure.io.graphson.GraphSONTypedCompatibilityTest; +import org.apache.tinkerpop.gremlin.structure.io.pdt.CompositePDT; +import org.apache.tinkerpop.gremlin.structure.io.pdt.PrimitivePDT; import org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerGraph; import org.apache.tinkerpop.gremlin.util.function.Lambda; import org.apache.tinkerpop.gremlin.util.message.RequestMessage; @@ -72,6 +75,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Date; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; @@ -115,6 +119,57 @@ public abstract class AbstractTypedCompatibilityTest extends AbstractCompatibili assertEquals(resource, recycled); } + @Test + public void shouldReadWriteZeroBigDecimal() throws Exception { + final String resourceName = "zero-bigdecimal"; + + final BigDecimal resource = findModelEntryObject(resourceName); + final BigDecimal fromStatic = read(readFromResource(resourceName), BigDecimal.class); + final BigDecimal recycled = read(write(fromStatic, BigDecimal.class, resourceName), BigDecimal.class); + assertEquals(fromStatic, recycled); + assertEquals(resource, fromStatic); + assertEquals(resource, recycled); + } + + @Test + public void shouldReadWriteScaleZeroBigDecimal() throws Exception { + final String resourceName = "scale-zero-bigdecimal"; + + final BigDecimal resource = findModelEntryObject(resourceName); + final BigDecimal fromStatic = read(readFromResource(resourceName), BigDecimal.class); + final BigDecimal recycled = read(write(fromStatic, BigDecimal.class, resourceName), BigDecimal.class); + assertNotSame(fromStatic, recycled); + assertEquals(fromStatic, recycled); + assertEquals(resource, fromStatic); + assertEquals(resource, recycled); + } + + @Test + public void shouldReadWriteNegativeScaleBigDecimal() throws Exception { + final String resourceName = "negative-scale-bigdecimal"; + + final BigDecimal resource = findModelEntryObject(resourceName); + final BigDecimal fromStatic = read(readFromResource(resourceName), BigDecimal.class); + final BigDecimal recycled = read(write(fromStatic, BigDecimal.class, resourceName), BigDecimal.class); + assertNotSame(fromStatic, recycled); + assertEquals(fromStatic, recycled); + assertEquals(resource, fromStatic); + assertEquals(resource, recycled); + } + + @Test + public void shouldReadWriteSmallDecimalBigDecimal() throws Exception { + final String resourceName = "small-decimal-bigdecimal"; + + final BigDecimal resource = findModelEntryObject(resourceName); + final BigDecimal fromStatic = read(readFromResource(resourceName), BigDecimal.class); + final BigDecimal recycled = read(write(fromStatic, BigDecimal.class, resourceName), BigDecimal.class); + assertNotSame(fromStatic, recycled); + assertEquals(fromStatic, recycled); + assertEquals(resource, fromStatic); + assertEquals(resource, recycled); + } + @Test public void shouldReadWritePositiveBigInteger() throws Exception { final String resourceName = "pos-biginteger"; @@ -141,6 +196,44 @@ public abstract class AbstractTypedCompatibilityTest extends AbstractCompatibili assertEquals(resource, recycled); } + @Test + public void shouldReadWriteZeroBigInteger() throws Exception { + final String resourceName = "zero-biginteger"; + + final BigInteger resource = findModelEntryObject(resourceName); + final BigInteger fromStatic = read(readFromResource(resourceName), BigInteger.class); + final BigInteger recycled = read(write(fromStatic, BigInteger.class, resourceName), BigInteger.class); + assertEquals(fromStatic, recycled); + assertEquals(resource, fromStatic); + assertEquals(resource, recycled); + } + + @Test + public void shouldReadWriteSignBoundaryPositiveBigInteger() throws Exception { + final String resourceName = "sign-boundary-pos-biginteger"; + + final BigInteger resource = findModelEntryObject(resourceName); + final BigInteger fromStatic = read(readFromResource(resourceName), BigInteger.class); + final BigInteger recycled = read(write(fromStatic, BigInteger.class, resourceName), BigInteger.class); + assertNotSame(fromStatic, recycled); + assertEquals(fromStatic, recycled); + assertEquals(resource, fromStatic); + assertEquals(resource, recycled); + } + + @Test + public void shouldReadWriteSignBoundaryNegativeBigInteger() throws Exception { + final String resourceName = "sign-boundary-neg-biginteger"; + + final BigInteger resource = findModelEntryObject(resourceName); + final BigInteger fromStatic = read(readFromResource(resourceName), BigInteger.class); + final BigInteger recycled = read(write(fromStatic, BigInteger.class, resourceName), BigInteger.class); + assertNotSame(fromStatic, recycled); + assertEquals(fromStatic, recycled); + assertEquals(resource, fromStatic); + assertEquals(resource, recycled); + } + @Test public void shouldReadWriteMinByte() throws Exception { final String resourceName = "min-byte"; @@ -328,6 +421,45 @@ public abstract class AbstractTypedCompatibilityTest extends AbstractCompatibili assertEquals(resource, recycled); } + @Test + public void shouldReadWritePositiveDuration() throws Exception { + final String resourceName = "positive-duration"; + + final Duration resource = findModelEntryObject(resourceName); + final Duration fromStatic = read(readFromResource(resourceName), Duration.class); + final Duration recycled = read(write(fromStatic, Duration.class, resourceName), Duration.class); + assertNotSame(fromStatic, recycled); + assertEquals(fromStatic, recycled); + assertEquals(resource, fromStatic); + assertEquals(resource, recycled); + } + + @Test + public void shouldReadWriteNegativeDuration() throws Exception { + final String resourceName = "negative-duration"; + + final Duration resource = findModelEntryObject(resourceName); + final Duration fromStatic = read(readFromResource(resourceName), Duration.class); + final Duration recycled = read(write(fromStatic, Duration.class, resourceName), Duration.class); + assertNotSame(fromStatic, recycled); + assertEquals(fromStatic, recycled); + assertEquals(resource, fromStatic); + assertEquals(resource, recycled); + } + + @Test + public void shouldReadWriteNanosDuration() throws Exception { + final String resourceName = "nanos-duration"; + + final Duration resource = findModelEntryObject(resourceName); + final Duration fromStatic = read(readFromResource(resourceName), Duration.class); + final Duration recycled = read(write(fromStatic, Duration.class, resourceName), Duration.class); + assertNotSame(fromStatic, recycled); + assertEquals(fromStatic, recycled); + assertEquals(resource, fromStatic); + assertEquals(resource, recycled); + } + @Test public void shouldReadWriteEdge() throws Exception { final String resourceName = "traversal-edge"; @@ -558,6 +690,21 @@ public abstract class AbstractTypedCompatibilityTest extends AbstractCompatibili assertEquals(resource, recycled); } + @Test + public void shouldReadWriteOrderedMap() throws Exception { + final String resourceName = "ordered-string-int-map"; + + final Map resource = findModelEntryObject(resourceName); + final Map fromStatic = read(readFromResource(resourceName), Map.class); + final Map recycled = read(write(fromStatic, Map.class, resourceName), Map.class); + assertThat(fromStatic instanceof LinkedHashMap, is(true)); + assertThat(recycled instanceof LinkedHashMap, is(true)); + assertEquals(new ArrayList<>(resource.keySet()), new ArrayList<>(fromStatic.keySet())); + assertEquals(new ArrayList<>(resource.keySet()), new ArrayList<>(recycled.keySet())); + assertEquals(resource, fromStatic); + assertEquals(resource, recycled); + } + @Test public void shouldReadWriteMaxOffsetDateTime() throws Exception { final String resourceName = "max-offsetdatetime"; @@ -623,6 +770,32 @@ public abstract class AbstractTypedCompatibilityTest extends AbstractCompatibili assertEquals(resource, recycled); } + @Test + public void shouldReadWritePathWithZeroLabels() throws Exception { + final String resourceName = "path-zero-labels"; + + final Path resource = findModelEntryObject(resourceName); + final Path fromStatic = read(readFromResource(resourceName), Path.class); + final Path recycled = read(write(fromStatic, Path.class, resourceName), Path.class); + assertNotSame(fromStatic, recycled); + assertEquals(fromStatic, recycled); + assertEquals(resource, fromStatic); + assertEquals(resource, recycled); + } + + @Test + public void shouldReadWritePathWithMultipleLabels() throws Exception { + final String resourceName = "path-multiple-labels"; + + final Path resource = findModelEntryObject(resourceName); + final Path fromStatic = read(readFromResource(resourceName), Path.class); + final Path recycled = read(write(fromStatic, Path.class, resourceName), Path.class); + assertNotSame(fromStatic, recycled); + assertEquals(fromStatic, recycled); + assertEquals(resource, fromStatic); + assertEquals(resource, recycled); + } + @Test public void shouldReadWriteEdgeProperty() throws Exception { final String resourceName = "edge-property"; @@ -763,6 +936,34 @@ public abstract class AbstractTypedCompatibilityTest extends AbstractCompatibili assertVertex(resource, recycled); } + @Test + public void shouldReadWriteMultiLabelVertex() throws Exception { + final String resourceName = "multi-label-vertex"; + + final Vertex resource = findModelEntryObject(resourceName); + final Vertex fromStatic = read(readFromResource(resourceName), Vertex.class); + final Vertex recycled = read(write(fromStatic, Vertex.class, resourceName), Vertex.class); + assertNotSame(fromStatic, recycled); + assertEquals(resource.id(), fromStatic.id()); + assertEquals(resource.id(), recycled.id()); + assertEquals(resource.labels(), fromStatic.labels()); + assertEquals(resource.labels(), recycled.labels()); + } + + @Test + public void shouldReadWriteEmptyLabelVertex() throws Exception { + final String resourceName = "empty-label-vertex"; + + final Vertex resource = findModelEntryObject(resourceName); + final Vertex fromStatic = read(readFromResource(resourceName), Vertex.class); + final Vertex recycled = read(write(fromStatic, Vertex.class, resourceName), Vertex.class); + assertNotSame(fromStatic, recycled); + assertEquals(resource.id(), fromStatic.id()); + assertEquals(resource.id(), recycled.id()); + assertEquals(resource.labels(), fromStatic.labels()); + assertEquals(resource.labels(), recycled.labels()); + } + @Test public void shouldReadWriteVertexProperty() throws Exception { final String resourceName = "traversal-vertexproperty"; @@ -832,6 +1033,80 @@ public abstract class AbstractTypedCompatibilityTest extends AbstractCompatibili assertEquals(resource, recycled); } + @Test + public void shouldReadWriteMergeOnCreate() throws Exception { + final String resourceName = "merge-on-create"; + + final Merge resource = findModelEntryObject(resourceName); + final Merge fromStatic = read(readFromResource(resourceName), Merge.class); + final Merge recycled = read(write(fromStatic, Merge.class, resourceName), Merge.class); + assertEquals(fromStatic, recycled); + assertEquals(resource, fromStatic); + assertEquals(resource, recycled); + } + + @Test + public void shouldReadWriteMergeOnMatch() throws Exception { + final String resourceName = "merge-on-match"; + + final Merge resource = findModelEntryObject(resourceName); + final Merge fromStatic = read(readFromResource(resourceName), Merge.class); + final Merge recycled = read(write(fromStatic, Merge.class, resourceName), Merge.class); + assertEquals(fromStatic, recycled); + assertEquals(resource, fromStatic); + assertEquals(resource, recycled); + } + + @Test + public void shouldReadWriteMergeOutV() throws Exception { + final String resourceName = "merge-out-v"; + + final Merge resource = findModelEntryObject(resourceName); + final Merge fromStatic = read(readFromResource(resourceName), Merge.class); + final Merge recycled = read(write(fromStatic, Merge.class, resourceName), Merge.class); + assertEquals(fromStatic, recycled); + assertEquals(resource, fromStatic); + assertEquals(resource, recycled); + } + + @Test + public void shouldReadWriteMergeInV() throws Exception { + final String resourceName = "merge-in-v"; + + final Merge resource = findModelEntryObject(resourceName); + final Merge fromStatic = read(readFromResource(resourceName), Merge.class); + final Merge recycled = read(write(fromStatic, Merge.class, resourceName), Merge.class); + assertEquals(fromStatic, recycled); + assertEquals(resource, fromStatic); + assertEquals(resource, recycled); + } + + @Test + public void shouldReadWritePrimitiveProviderDefinedType() throws Exception { + final String resourceName = "uint8-primitive-pdt"; + + final PrimitivePDT resource = findModelEntryObject(resourceName); + final PrimitivePDT fromStatic = read(readFromResource(resourceName), PrimitivePDT.class); + final PrimitivePDT recycled = read(write(fromStatic, PrimitivePDT.class, resourceName), PrimitivePDT.class); + assertNotSame(fromStatic, recycled); + assertEquals(fromStatic, recycled); + assertEquals(resource, fromStatic); + assertEquals(resource, recycled); + } + + @Test + public void shouldReadWriteCompositeProviderDefinedType() throws Exception { + final String resourceName = "point-composite-pdt"; + + final CompositePDT resource = findModelEntryObject(resourceName); + final CompositePDT fromStatic = read(readFromResource(resourceName), CompositePDT.class); + final CompositePDT recycled = read(write(fromStatic, CompositePDT.class, resourceName), CompositePDT.class); + assertNotSame(fromStatic, recycled); + assertEquals(fromStatic, recycled); + assertEquals(resource, fromStatic); + assertEquals(resource, recycled); + } + @Test public void shouldReadWriteVarBulkList() throws Exception { final String resourceName = "var-bulklist"; @@ -875,8 +1150,20 @@ public abstract class AbstractTypedCompatibilityTest extends AbstractCompatibili } @Test - public void shouldReadWriteMultiByteChar() throws Exception { - final String resourceName = "multi-byte-char"; + public void shouldReadWriteTwoByteChar() throws Exception { + final String resourceName = "two-byte-char"; + + final Character resource = findModelEntryObject(resourceName); + final Character fromStatic = read(readFromResource(resourceName), Character.class); + final Character recycled = read(write(fromStatic, Character.class, resourceName), Character.class); + assertEquals(fromStatic, recycled); + assertEquals(resource, fromStatic); + assertEquals(resource, recycled); + } + + @Test + public void shouldReadWriteThreeByteChar() throws Exception { + final String resourceName = "three-byte-char"; final Character resource = findModelEntryObject(resourceName); final Character fromStatic = read(readFromResource(resourceName), Character.class); @@ -934,6 +1221,18 @@ public abstract class AbstractTypedCompatibilityTest extends AbstractCompatibili assertEquals(resource, recycled); } + @Test + public void shouldReadWriteEmptyString() throws Exception { + final String resourceName = "empty-string"; + + final String resource = findModelEntryObject(resourceName); + final String fromStatic = read(readFromResource(resourceName), String.class); + final String recycled = read(write(fromStatic, String.class, resourceName), String.class); + assertEquals(fromStatic, recycled); + assertEquals(resource, fromStatic); + assertEquals(resource, recycled); + } + @Test public void shouldReadWriteMixedString() throws Exception { final String resourceName = "mixed-string"; diff --git a/gremlin-util/src/test/java/org/apache/tinkerpop/gremlin/structure/io/Model.java b/gremlin-util/src/test/java/org/apache/tinkerpop/gremlin/structure/io/Model.java index 864697ce70..ac08ad8313 100644 --- a/gremlin-util/src/test/java/org/apache/tinkerpop/gremlin/structure/io/Model.java +++ b/gremlin-util/src/test/java/org/apache/tinkerpop/gremlin/structure/io/Model.java @@ -20,14 +20,22 @@ package org.apache.tinkerpop.gremlin.structure.io; import org.apache.commons.configuration2.BaseConfiguration; import org.apache.commons.configuration2.Configuration; +import org.apache.tinkerpop.gremlin.process.traversal.Merge; +import org.apache.tinkerpop.gremlin.process.traversal.Path; import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource; import org.apache.tinkerpop.gremlin.process.traversal.step.util.BulkSet; import org.apache.tinkerpop.gremlin.process.traversal.step.util.EmptyPath; +import org.apache.tinkerpop.gremlin.process.traversal.step.util.MutablePath; +import org.apache.tinkerpop.gremlin.process.traversal.step.util.Tree; 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.VertexProperty; +import org.apache.tinkerpop.gremlin.structure.io.pdt.CompositePDT; +import org.apache.tinkerpop.gremlin.structure.io.pdt.PrimitivePDT; import org.apache.tinkerpop.gremlin.structure.util.detached.DetachedFactory; import org.apache.tinkerpop.gremlin.structure.util.detached.DetachedProperty; +import org.apache.tinkerpop.gremlin.structure.util.detached.DetachedVertex; import org.apache.tinkerpop.gremlin.structure.util.detached.DetachedVertexProperty; import org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerFactory; import org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerGraph; @@ -48,6 +56,8 @@ import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Optional; @@ -61,6 +71,9 @@ import static org.apache.tinkerpop.gremlin.structure.util.detached.DetachedFacto /** * Defines the supported types for IO and the versions (and configurations) to which they apply and are tested. * + * The following GraphBinary model resources aren't supported by the Java model: + * four-byte-char Character cannot represent a supplementary Unicode code point as a single scalar value + * * @author Stephen Mallette (http://stephen.genoprime.com) */ public class Model { @@ -111,6 +124,14 @@ public class Model { map.put(null, null); addCoreEntry(map, "var-type-map", "Map is redefined so that to provide the ability to allow for non-String keys, which is not possible in JSON."); addCoreEntry(Collections.EMPTY_MAP, "empty-map"); + final Map<Object,Object> orderedMap = new LinkedHashMap<>(); + orderedMap.put("delta", 4); + orderedMap.put("alpha", 1); + orderedMap.put("charlie", 3); + orderedMap.put("bravo", 2); + orderedMap.put("echo", 5); + orderedMap.put("foxtrot", 6); + addCoreEntry(orderedMap, "ordered-string-int-map"); addCoreEntry(new HashSet<>(Arrays.asList(2, "person", true, null)), "var-type-set", "Allows a JSON collection to behave as a Set."); addCoreEntry(Collections.EMPTY_SET, "empty-set"); @@ -120,19 +141,42 @@ public class Model { addCoreEntry(Direction.OUT, "out-direction", ""); addCoreEntry(T.id, "id-t", ""); + addCoreEntry(Merge.onCreate, "merge-on-create", ""); + addCoreEntry(Merge.onMatch, "merge-on-match", ""); + addCoreEntry(Merge.outV, "merge-out-v", ""); + addCoreEntry(Merge.inV, "merge-in-v", ""); addCoreEntry('a', "single-byte-char", ""); - addCoreEntry('\u03A9', "multi-byte-char", ""); + addCoreEntry('\u03A9', "two-byte-char", ""); + addCoreEntry('\u20AC', "three-byte-char", ""); addEntry("Core", () -> null, "unspecified-null", ""); + addEntry("Typed Null", () -> null, "null-int", ""); + addEntry("Typed Null", () -> null, "null-long", ""); + addEntry("Typed Null", () -> null, "null-string", ""); + addEntry("Typed Null", () -> null, "null-list", ""); + addEntry("Typed Null", () -> null, "null-map", ""); + addEntry("Typed Null", () -> null, "null-set", ""); addCoreEntry(true, "true-boolean", ""); addCoreEntry(false, "false-boolean", ""); addCoreEntry("abc", "single-byte-string", ""); addCoreEntry("abc\u0391\u0392\u0393", "mixed-string", ""); + addCoreEntry("", "empty-string", ""); addCoreEntry(g.V(10).out().tree().next(), "traversal-tree", ""); + addCoreEntry(new Tree<>(), "empty-tree", ""); + final Tree<Object> nullKeyTree = new Tree<>(); + nullKeyTree.getOrCreateChild(null); + addCoreEntry(nullKeyTree, "tree-null-key", ""); + final Tree<Object> mixedKeyTree = new Tree<>(); + mixedKeyTree.getOrCreateChild("name"); + mixedKeyTree.getOrCreateChild(123); + addCoreEntry(mixedKeyTree, "tree-mixed-key-types", ""); + final Tree<Object> deepTree = new Tree<>(); + deepTree.getOrCreateChild("root").getOrCreateChild("branch").getOrCreateChild("leaf"); + addCoreEntry(deepTree, "tree-deep-nesting", ""); addGraphStructureEntry(graph.edges().next(), "traversal-edge", ""); addGraphStructureEntry(DetachedFactory.detach(graph.edges().next(), false), "no-prop-edge", ""); @@ -140,6 +184,10 @@ public class Model { addGraphStructureEntry(detach(g.V().out().out().path().next(), false), "traversal-path", ""); addGraphStructureEntry(EmptyPath.instance(), "empty-path", ""); addGraphStructureEntry(detach(g.V().out().out().path().next(), true), "prop-path", ""); + final Path zeroLabelsPath = MutablePath.make().extend("marko", Collections.emptySet()); + addGraphStructureEntry(zeroLabelsPath, "path-zero-labels", ""); + final Path multipleLabelsPath = MutablePath.make().extend("marko", new LinkedHashSet<>(Arrays.asList("a", "b"))); + addGraphStructureEntry(multipleLabelsPath, "path-multiple-labels", ""); addGraphStructureEntry(graph.edges().next().properties().next(), "edge-property", ""); addGraphStructureEntry(new DetachedProperty<>("", null), "null-property", ""); @@ -148,6 +196,16 @@ public class Model { addGraphStructureEntry(graph.vertices().next(), "traversal-vertex", ""); addGraphStructureEntry(DetachedFactory.detach(graph.vertices().next(), false), "no-prop-vertex", ""); + final Vertex multiLabelVertex = DetachedVertex.build() + .setId(1) + .setLabels(new LinkedHashSet<>(Arrays.asList("person", "employee"))) + .create(); + addGraphStructureEntry(multiLabelVertex, "multi-label-vertex", ""); + final Vertex emptyLabelVertex = DetachedVertex.build() + .setId(1) + .setLabels(Collections.emptySet()) + .create(); + addGraphStructureEntry(emptyLabelVertex, "empty-label-vertex", ""); addGraphStructureEntry(graph.vertices().next().properties().next(), "traversal-vertexproperty", ""); final Map<String,Object> metaProperties = new HashMap<>(); @@ -166,9 +224,22 @@ public class Model { addExtendedEntry(new BigDecimal("123.456789987654321123456789987654321"), "pos-bigdecimal", ""); addExtendedEntry(new BigDecimal("-123.456789987654321123456789987654321"), "neg-bigdecimal", ""); + addExtendedEntry(BigDecimal.ZERO, "zero-bigdecimal", ""); + addExtendedEntry(new BigDecimal(new BigInteger("1234"), 0), "scale-zero-bigdecimal", ""); + addExtendedEntry(new BigDecimal(new BigInteger("1234"), -2), "negative-scale-bigdecimal", ""); + addExtendedEntry(new BigDecimal("12.34"), "small-decimal-bigdecimal", ""); addExtendedEntry(new BigInteger("123456789987654321123456789987654321"), "pos-biginteger", ""); addExtendedEntry(new BigInteger("-123456789987654321123456789987654321"), "neg-biginteger", ""); + addExtendedEntry(BigInteger.ZERO, "zero-biginteger", ""); + addExtendedEntry(BigInteger.valueOf(128), "sign-boundary-pos-biginteger", ""); + addExtendedEntry(BigInteger.valueOf(-129), "sign-boundary-neg-biginteger", ""); + + addExtendedEntry(new PrimitivePDT("Uint8", "10"), "uint8-primitive-pdt", ""); + final Map<String,Object> pointFields = new LinkedHashMap<>(); + pointFields.put("x", 1); + pointFields.put("y", 2); + addExtendedEntry(new CompositePDT("Point", pointFields), "point-composite-pdt", ""); addExtendedEntry(Byte.MAX_VALUE, "max-byte", ""); addExtendedEntry(Byte.MIN_VALUE, "min-byte", ""); @@ -178,6 +249,9 @@ public class Model { addExtendedEntry(Duration.ZERO, "zero-duration","The following example is a zero `Duration`"); addExtendedEntry(ChronoUnit.FOREVER.getDuration(), "forever-duration",""); + addExtendedEntry(Duration.ofSeconds(123), "positive-duration",""); + addExtendedEntry(Duration.ofSeconds(-123), "negative-duration",""); + addExtendedEntry(Duration.ofSeconds(123, 456789), "nanos-duration",""); addExtendedEntry(OffsetDateTime.MAX, "max-offsetdatetime", ""); addExtendedEntry(OffsetDateTime.MIN, "min-offsetdatetime", ""); diff --git a/gremlin-util/src/test/java/org/apache/tinkerpop/gremlin/structure/io/graphbinary/GraphBinaryCompatibilityTest.java b/gremlin-util/src/test/java/org/apache/tinkerpop/gremlin/structure/io/graphbinary/GraphBinaryCompatibilityTest.java index 092c1adf6c..7ede7cfab5 100644 --- a/gremlin-util/src/test/java/org/apache/tinkerpop/gremlin/structure/io/graphbinary/GraphBinaryCompatibilityTest.java +++ b/gremlin-util/src/test/java/org/apache/tinkerpop/gremlin/structure/io/graphbinary/GraphBinaryCompatibilityTest.java @@ -21,11 +21,13 @@ package org.apache.tinkerpop.gremlin.structure.io.graphbinary; import io.netty.buffer.ByteBufAllocator; import org.apache.commons.io.IOUtils; import org.apache.commons.io.output.ByteArrayOutputStream; +import org.apache.tinkerpop.gremlin.process.traversal.step.util.Tree; import org.apache.tinkerpop.gremlin.structure.io.AbstractTypedCompatibilityTest; import org.apache.tinkerpop.gremlin.structure.io.Buffer; import org.apache.tinkerpop.gremlin.structure.io.binary.GraphBinaryReader; import org.apache.tinkerpop.gremlin.structure.io.binary.GraphBinaryWriter; import org.apache.tinkerpop.gremlin.util.ser.NettyBufferFactory; +import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -33,6 +35,12 @@ import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; /** * @author Stephen Mallette (http://stephen.genoprime.com) @@ -107,6 +115,102 @@ public class GraphBinaryCompatibilityTest extends AbstractTypedCompatibilityTest } } + @Test + public void shouldReadNullInt() throws Exception { + final String resourceName = "null-int"; + + final Integer fromStatic = read(readFromResource(resourceName), Integer.class); + assertNull(fromStatic); + } + + @Test + public void shouldReadNullLong() throws Exception { + final String resourceName = "null-long"; + + final Long fromStatic = read(readFromResource(resourceName), Long.class); + assertNull(fromStatic); + } + + @Test + public void shouldReadNullString() throws Exception { + final String resourceName = "null-string"; + + final String fromStatic = read(readFromResource(resourceName), String.class); + assertNull(fromStatic); + } + + @Test + public void shouldReadNullList() throws Exception { + final String resourceName = "null-list"; + + final List fromStatic = read(readFromResource(resourceName), List.class); + assertNull(fromStatic); + } + + @Test + public void shouldReadNullMap() throws Exception { + final String resourceName = "null-map"; + + final Map fromStatic = read(readFromResource(resourceName), Map.class); + assertNull(fromStatic); + } + + @Test + public void shouldReadNullSet() throws Exception { + final String resourceName = "null-set"; + + final Set fromStatic = read(readFromResource(resourceName), Set.class); + assertNull(fromStatic); + } + + @Test + public void shouldReadWriteEmptyTree() throws Exception { + final String resourceName = "empty-tree"; + + final Tree resource = findModelEntryObject(resourceName); + final Tree fromStatic = read(readFromResource(resourceName), Tree.class); + final Tree recycled = read(write(fromStatic, Tree.class, resourceName), Tree.class); + assertEquals(fromStatic, recycled); + assertEquals(resource, fromStatic); + assertEquals(resource, recycled); + } + + @Test + public void shouldReadWriteTreeWithNullKey() throws Exception { + final String resourceName = "tree-null-key"; + + final Tree resource = findModelEntryObject(resourceName); + final Tree fromStatic = read(readFromResource(resourceName), Tree.class); + final Tree recycled = read(write(fromStatic, Tree.class, resourceName), Tree.class); + assertEquals(fromStatic, recycled); + assertEquals(resource, fromStatic); + assertEquals(resource, recycled); + } + + @Test + public void shouldReadWriteTreeWithMixedKeyTypes() throws Exception { + final String resourceName = "tree-mixed-key-types"; + + final Tree resource = findModelEntryObject(resourceName); + final Tree fromStatic = read(readFromResource(resourceName), Tree.class); + final Tree recycled = read(write(fromStatic, Tree.class, resourceName), Tree.class); + assertEquals(fromStatic, recycled); + assertEquals(resource, fromStatic); + assertEquals(resource, recycled); + } + + @Test + public void shouldReadWriteTreeWithDeepNesting() throws Exception { + final String resourceName = "tree-deep-nesting"; + + final Tree resource = findModelEntryObject(resourceName); + final Tree fromStatic = read(readFromResource(resourceName), Tree.class); + final Tree recycled = read(write(fromStatic, Tree.class, resourceName), Tree.class); + assertEquals(fromStatic, recycled); + assertEquals(resource, fromStatic); + assertEquals(resource, recycled); + } + // TODO: revisit // @Override // public void shouldReadWriteAuthenticationChallenge() throws Exception {
