[ https://issues.apache.org/jira/browse/TINKERPOP-2824?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=17690453#comment-17690453 ]
ASF GitHub Bot commented on TINKERPOP-2824: ------------------------------------------- FlorianHockmann commented on code in PR #1843: URL: https://github.com/apache/tinkerpop/pull/1843#discussion_r1108148751 ########## gremlin-dotnet/src/Gremlin.Net/Structure/IO/GraphBinary/Types/EdgeSerializer.cs: ########## @@ -45,15 +46,17 @@ public EdgeSerializer() : base(DataType.Edge) { await writer.WriteAsync(value.Id, stream, cancellationToken).ConfigureAwait(false); await writer.WriteNonNullableValueAsync(value.Label, stream, cancellationToken).ConfigureAwait(false); - + + // !!! await writer.WriteValueAsync(value.Label, stream, false, cancellationToken).ConfigureAwait(false); Review Comment: ? ########## gremlin-dotnet/src/Gremlin.Net/Structure/Edge.cs: ########## @@ -52,6 +55,15 @@ public Edge(object? id, Vertex outV, string label, Vertex inV) /// </summary> public Vertex OutV { get; } + /// <summary> + /// Get property by key + /// </summary> + /// <returns>property or null when not found</returns> + public Property? Property(string key) Review Comment: Any reason against implementing this in `Element` instead of here and in `Vertex`? ########## gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Process/Traversal/DriverRemoteConnection/RemoteConnectionFactory.cs: ########## @@ -49,19 +49,32 @@ public IRemoteConnection CreateRemoteConnection(int connectionPoolSize = 2) return CreateRemoteConnection("gmodern", connectionPoolSize); } - public IRemoteConnection CreateRemoteConnection(string traversalSource, int connectionPoolSize = 2) + public IRemoteConnection CreateRemoteConnection(string traversalSource, + int connectionPoolSize = 2, + IMessageSerializer? messageSerializer = null) { var c = new DriverRemoteConnectionImpl( - new GremlinClient(new GremlinServer(TestHost, TestPort), _messageSerializer, + new GremlinClient(new GremlinServer(TestHost, TestPort), Review Comment: (nitpick) Why not call the newly added method `CreateClient` here? ########## gremlin-javascript/src/main/javascript/gremlin-javascript/test/integration/client-tests.js: ########## @@ -75,6 +75,68 @@ describe('Client', function () { }); }); + it('should handle properties for bytecode request', function () { + return client.submit(new Bytecode().addStep('V', [1])) + .then(function (result) { + assert.ok(result); + assert.strictEqual(result.length, 1); + const vertex = result.first().object; + assert.ok(vertex instanceof graphModule.Vertex); + let age, name + if (vertex.properties instanceof Array) { + age = vertex.properties[1] + name = vertex.properties[0] + } else { + age = vertex.properties.age[0] + name = vertex.properties.name[0] + } + assert.strictEqual(age.value, 29); + assert.strictEqual(name.value, 'marko'); + }); + }); + + it('should skip properties for bytecode request', function () { Review Comment: (nitpick) ```suggestion it('should skip properties for bytecode request with tokens', function () { ``` ########## gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Process/Traversal/DriverRemoteConnection/RemoteConnectionFactory.cs: ########## @@ -49,19 +49,32 @@ public IRemoteConnection CreateRemoteConnection(int connectionPoolSize = 2) return CreateRemoteConnection("gmodern", connectionPoolSize); } - public IRemoteConnection CreateRemoteConnection(string traversalSource, int connectionPoolSize = 2) + public IRemoteConnection CreateRemoteConnection(string traversalSource, + int connectionPoolSize = 2, + IMessageSerializer? messageSerializer = null) { var c = new DriverRemoteConnectionImpl( - new GremlinClient(new GremlinServer(TestHost, TestPort), _messageSerializer, + new GremlinClient(new GremlinServer(TestHost, TestPort), + messageSerializer ?? _messageSerializer, connectionPoolSettings: new ConnectionPoolSettings { PoolSize = connectionPoolSize }), traversalSource); - _connections.Add(c); + _cleanUp.Add(c); + return c; + } + + public IGremlinClient CreateClient(IMessageSerializer messageSerializer = null) Review Comment: Please add a nullable annotation: ```suggestion public IGremlinClient CreateClient(IMessageSerializer? messageSerializer = null) ``` ########## gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Driver/PropertyDeserializationTests.cs: ########## @@ -0,0 +1,247 @@ +#region License + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#endregion + +using Gremlin.Net.Driver; +using Gremlin.Net.IntegrationTest.Process.Traversal.DriverRemoteConnection; +using Gremlin.Net.Process.Traversal; +using Gremlin.Net.Structure; +using Gremlin.Net.Structure.IO.GraphBinary; +using Gremlin.Net.Structure.IO.GraphSON; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Xunit; + +namespace Gremlin.Net.IntegrationTest.Driver +{ + public class PropertyDeserializationTests + { + private readonly RemoteConnectionFactory _connectionFactory = new(); + + [Theory] + [MemberData(nameof(Serializers))] + public void ShouldDeserializeVertexPropertiesForBytecode(IMessageSerializer serializer) + { + var connection = _connectionFactory.CreateRemoteConnection("gmodern", 2, serializer); + var g = AnonymousTraversalSource.Traversal().WithRemote(connection); + + var vertex = g.V(1).Next(); + + VerifyVertexProperties(vertex); + } + + [Theory] + [MemberData(nameof(Serializers))] + public void ShouldRespectMaterializePropertiesTokensForBytecode(IMessageSerializer serializer) + { + var connection = _connectionFactory.CreateRemoteConnection("gmodern", 2, serializer); + var g = AnonymousTraversalSource.Traversal().WithRemote(connection); + + var vertex = g.With(Tokens.ArgMaterializeProperties, "tokens").V(1).Next(); + + VerifyEmptyProperties(vertex); + } + + [Theory] + [MemberData(nameof(Serializers))] + public void ShouldRespectMaterializePropertiesAllForBytecode(IMessageSerializer serializer) + { + var connection = _connectionFactory.CreateRemoteConnection("gmodern", 2, serializer); + var g = AnonymousTraversalSource.Traversal().WithRemote(connection); + + var vertex = g.With(Tokens.ArgMaterializeProperties, "all").V(1).Next(); + + VerifyVertexProperties(vertex); + } + + [Theory] + [MemberData(nameof(Serializers))] + public void ShouldHandleEmptyVertexPropertiesForBytecode(IMessageSerializer serializer) + { + var connection = _connectionFactory.CreateRemoteConnection("gimmutable", 2, serializer); + var g = AnonymousTraversalSource.Traversal().WithRemote(connection); + + var vertex = g.AddV("test").Next(); + + VerifyEmptyProperties(vertex); + } + + [Theory] + [MemberData(nameof(Serializers))] + public async Task ShouldDeserializeVertexPropertiesForGremlin(IMessageSerializer serializer) + { + var client = _connectionFactory.CreateClient(serializer); + + var vertex = await client.SubmitWithSingleResultAsync<Vertex>("gmodern.V(1)"); + + VerifyVertexProperties(vertex); + } + + [Theory] + [MemberData(nameof(Serializers))] + public async Task ShouldHandleEmptyVertexPropertiesForGremlin(IMessageSerializer serializer) + { + var client = _connectionFactory.CreateClient(serializer); + + var vertex = await client.SubmitWithSingleResultAsync<Vertex>("gimmutable.addV('test')"); + + VerifyEmptyProperties(vertex); + } + + [Theory] + [MemberData(nameof(Serializers))] + public async Task ShouldRespectMaterializePropertiesAllForGremlin(IMessageSerializer serializer) + { + var client = _connectionFactory.CreateClient(serializer); + + var vertex = await client.SubmitWithSingleResultAsync<Vertex>("gmodern.with('materializeProperties', 'all').V(1)"); + + VerifyVertexProperties(vertex); + } + + [Theory] + [MemberData(nameof(Serializers))] + public async Task ShouldRespectMaterializePropertiesTokensForGremlin(IMessageSerializer serializer) + { + var client = _connectionFactory.CreateClient(serializer); + + var vertex = await client.SubmitWithSingleResultAsync<Vertex>("gmodern.with('materializeProperties', 'tokens').V(1)"); + + VerifyEmptyProperties(vertex); + } + + [Theory] + [MemberData(nameof(Serializers))] + public void ShouldDeserializeEdgePropertiesForBytecode(IMessageSerializer serializer) + { + var connection = _connectionFactory.CreateRemoteConnection("gmodern", 2, serializer); + var g = AnonymousTraversalSource.Traversal().WithRemote(connection); + + var edge = g.E(7).Next(); + + VerifyEdgeProperties(edge); + } + + [Theory] + [MemberData(nameof(Serializers))] + public void ShouldHandleEmptyEdgePropertiesForBytecode(IMessageSerializer serializer) + { + var connection = _connectionFactory.CreateRemoteConnection("gimmutable", 2, serializer); + var g = AnonymousTraversalSource.Traversal().WithRemote(connection); + + var v1 = g.AddV("v1").Next(); + var v2 = g.AddV("v2").Next(); + var edge = g.AddE("test").From(v1).To(v2).Next(); + + VerifyEmptyProperties(edge); + } + + [Theory] + [MemberData(nameof(Serializers))] + public async Task ShouldDeserializeEdgePropertiesForGremlin(IMessageSerializer serializer) + { + var client = _connectionFactory.CreateClient(serializer); + + var edge = await client.SubmitWithSingleResultAsync<Edge>("gmodern.E(7)"); + + VerifyEdgeProperties(edge); + } + + [Theory] + [MemberData(nameof(Serializers))] + public async Task ShouldHandleEmptyEdgePropertiesForGremlin(IMessageSerializer serializer) + { + var client = _connectionFactory.CreateClient(serializer); + + var edge = await client.SubmitWithSingleResultAsync<Edge>( + "gimmutable.addV().as('v1').addV().as('v2').addE('test').from('v1').to('v2')"); + + VerifyEmptyProperties(edge); + } + + [Theory] + [MemberData(nameof(Serializers))] + public void ShouldHandleMultiplePropertiesWithSameNameForVertex(IMessageSerializer serializer) + { + var connection = _connectionFactory.CreateRemoteConnection("gimmutable", 2, serializer); + var g = AnonymousTraversalSource.Traversal().WithRemote(connection); + + var vertex = g.AddV() + .Property(Cardinality.List, "test", "value1") + .Property(Cardinality.List, "test", "value2") + .Property(Cardinality.List, "test", "value3") + .Next()!; + + vertex = g.V(vertex.Id).Next(); + + Assert.NotNull(vertex); + + var properties = vertex.Properties!; + Assert.Equal(3, properties.Length); + var propertyValues = properties.Cast<VertexProperty>().Where(p => p.Key == "test").Select(p => p.Value).ToArray(); + Assert.Equal(3, propertyValues.Length); + Assert.Equal(new[] { "value1", "value2", "value3" }, propertyValues); + } + + private static void VerifyVertexProperties(Vertex? vertex) + { + Assert.NotNull(vertex); + Assert.Equal(1, vertex.Id); + + Assert.Equal(2, vertex.Properties!.Length); + var age = vertex.Property("age"); + Assert.NotNull(age); + Assert.Equal(29, age.Value); + } + + private static void VerifyEdgeProperties(Edge? edge) + { + Assert.NotNull(edge); + Assert.Equal(7, edge.Id); + + Assert.Single(edge.Properties!); + var weight = edge.Property("weight"); + Assert.NotNull(weight); + Assert.Equal(0.5, weight.Value); + } + + private static void VerifyEmptyProperties(Vertex? vertex) Review Comment: Not that important but you could easily let this take an `Element` instead and then remove the method below. ########## gremlin-go/driver/graphBinary.go: ########## @@ -1052,8 +1052,8 @@ func vertexReader(data *[]byte, i *int) (interface{}, error) { return vertexReaderNullByte(data, i, true) } -// {fully qualified id}{unqualified label}{[unused null byte]} -func vertexReaderNullByte(data *[]byte, i *int, unusedByte bool) (interface{}, error) { +// {fully qualified id}{unqualified label}{fully qualified properties} +func vertexReaderNullByte(data *[]byte, i *int, readProperties bool) (interface{}, error) { Review Comment: Shouldn't this now be named `vertexReaderReadingProperties` or something like that? ########## gremlin-javascript/src/main/javascript/gremlin-javascript/test/integration/client-tests.js: ########## @@ -75,6 +75,68 @@ describe('Client', function () { }); }); + it('should handle properties for bytecode request', function () { + return client.submit(new Bytecode().addStep('V', [1])) + .then(function (result) { + assert.ok(result); + assert.strictEqual(result.length, 1); + const vertex = result.first().object; + assert.ok(vertex instanceof graphModule.Vertex); + let age, name + if (vertex.properties instanceof Array) { + age = vertex.properties[1] + name = vertex.properties[0] + } else { + age = vertex.properties.age[0] + name = vertex.properties.name[0] + } + assert.strictEqual(age.value, 29); + assert.strictEqual(name.value, 'marko'); + }); + }); + + it('should skip properties for bytecode request', function () { + return client.submit(new Bytecode().addStep('V', [1]), null, {'materializeProperties': 'tokens'}) + .then(function (result) { + assert.ok(result); + assert.strictEqual(result.length, 1); + const vertex = result.first().object; + assert.ok(vertex instanceof graphModule.Vertex); + assert.ok(vertex.properties === undefined || vertex.properties.length === 0); + }); + }); + + it('should handle properties for gremlin request', function () { + return client.submit('g.V(1)') + .then(function (result) { + assert.ok(result); + assert.strictEqual(result.length, 1); + const vertex = result.first(); + assert.ok(vertex instanceof graphModule.Vertex); + let age, name + if (vertex.properties instanceof Array) { + age = vertex.properties[1] + name = vertex.properties[0] + } else { + age = vertex.properties.age[0] + name = vertex.properties.name[0] + } + assert.strictEqual(age.value, 29); + assert.strictEqual(name.value, 'marko'); + }); + }); + + it('should skip properties for gremlin request', function () { Review Comment: ```suggestion it('should skip properties for gremlin request with tokens', function () { ``` ########## gremlin-python/src/main/python/tests/structure/io/test_functionalityio.py: ########## @@ -25,6 +25,34 @@ from gremlin_python.statics import * +def test_vertex(remote_connection): + g = Graph().traversal().withRemote(remote_connection) + vertex = g.V(1).next() + assert vertex.id == 1 + assert len(vertex.properties) == 2 + assert vertex.properties[0].key == 'name' + assert vertex.properties[0].value == 'marko' + assert vertex.properties[1].key == 'age' + assert vertex.properties[1].value == 29 + + +def test_vertex_without_properties(remote_connection): + g = Graph().traversal().withRemote(remote_connection) + vertex = g.with_('materializeProperties', 'tokens').V(1).next() + assert vertex.id == 1 + # empty array for GraphBinary and missing field for GraphSON + assert vertex.properties is None or len(vertex.properties) == 0 + + +def test_edge(remote_connection): Review Comment: (nitpick) You could also add a test for an edge with `tokens`. ########## gremlin-server/src/test/scripts/generate-all.groovy: ########## @@ -61,7 +61,7 @@ globals << [hook : [ // add default TraversalSource instances for each graph instance globals << [gclassic : traversal().withEmbedded(classic).withStrategies(ReferenceElementStrategy)] -globals << [gmodern : traversal().withEmbedded(modern).withStrategies(ReferenceElementStrategy)] +globals << [gmodern : traversal().withEmbedded(modern)] Review Comment: Just for my own understanding: You're removing this here so `gmodern` can be used by tests where we want to get properties back, right? ########## gremlin-javascript/src/main/javascript/gremlin-javascript/test/cucumber/feature-steps.js: ########## @@ -131,17 +131,30 @@ Given(/^using the parameter (.+) defined as "(.+)"$/, function (paramName, strin }); }); +var removeProperties = function(p) { + if (p === undefined) { + } else if (p instanceof graphModule.Vertex || p instanceof graphModule.Edge) { + p.properties = undefined; + } else if (p instanceof Array) { + p.forEach(removeProperties) + } else if (p instanceof Map) { + removeProperties(Array.from(p.keys())) + removeProperties(Array.from(p.values())) + } else if (p instanceof graphModule.Path) { + removeProperties(p.objects) + } + + return p +} + When('iterated to list', function () { - return this.traversal.toList().then(list => this.result = list).catch(err => this.result = err); + return this.traversal.toList().then(list => this.result = removeProperties(list)).catch(err => this.result = err); Review Comment: Shouldn't we make sure that things like element equality is handled the same way across GLVs? I think JS should also treat elements as equal if their IDs are equal to stay consistent with Java & .NET. But that's probably out of scope for this PR. We can also create an issue for this if we agree that all GLVs should behave the same in this regard. > Properties on Elements > ---------------------- > > Key: TINKERPOP-2824 > URL: https://issues.apache.org/jira/browse/TINKERPOP-2824 > Project: TinkerPop > Issue Type: Improvement > Components: dotnet, driver, go, javascript, process, python > Affects Versions: 3.5.4 > Reporter: Valentyn Kahamlyk > Assignee: Valentyn Kahamlyk > Priority: Major > > Problem: When a user writes `g.V()` they get back a Vertex object. The > problem is that depending on the execution context of the traversal, the > result could be quite different, with or without properties. > Solution: Implement new finalization strategy DetachStrategy(detachMode, > properties) where mode is one of ALL, NONE or CUSTOM. `properties` is list of > properties name, are taken into account only for CUSTOM mode. > Discussion thread in dev list: [Proposal to handle properties on response > Elements-Apache Mail > Archives|https://lists.apache.org/thread/l8rw7ydj7kym8vhtwk50nhbp45ng9986] > Stephen's thread in dev list: [The Issue of Detachment-Apache Mail > Archives|https://lists.apache.org/thread/xltcon4zxnwq4fyw2r2126syyrqm8spy] -- This message was sent by Atlassian Jira (v8.20.10#820010)