[TINKERPOP-3105] Running 3.6.x python-driver with 3.7.x server leads to deserialization errors (#2742)
Added check and returned `null` for properties in reference elements instead of empty list in GraphBinary serializer for compatibility, and changed deserialization of null properties from reference elements into empty list for compatibility.
diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc
index 822a3f4..edc3e10 100644
--- a/CHANGELOG.asciidoc
+++ b/CHANGELOG.asciidoc
@@ -36,6 +36,7 @@
* Fix cases where Map keys of incomparable types could panic in `gremlin-go`.
* Fixed an issue where missing necessary parameters for logging, resulting in '%!x(MISSING)' output in `gremlin-go`.
* Added getter method to `ConcatStep`, `ConjoinStep`, `SplitGlobalStep` and `SplitLocalStep` for their private fields.
+* When using `ReferenceElementStrategy` with `GraphBinaryV1`, properties on elements will be returned as `null` instead of empty list, to stay compatible with older drivers.
* Gremlin Server docker containers shutdown gracefully when receiving a SIGTERM.
* Added DefaultIdManager.STRING for proper string id creation/handling.
* Allowed specification of an `Operator` as a reducer in `withSideEffect` when parsing with the grammar.
diff --git a/docs/src/reference/gremlin-applications.asciidoc b/docs/src/reference/gremlin-applications.asciidoc
index a241264..196e59f 100644
--- a/docs/src/reference/gremlin-applications.asciidoc
+++ b/docs/src/reference/gremlin-applications.asciidoc
@@ -2336,6 +2336,45 @@
Both of the above requests return a list of `Map` instances that contain the `id`, `label` and the "name" property.
+*Compatibility*
+
+*It is not recommended to use 3.6.x or below driver versions with 3.7.x or above Gremlin Server*, as some older drivers do not construct
+graph elements with properties and thus are not designed to handle the returned properties by default; however, compatibility
+can be achieved by configuring `ReferenceElementStrategy` in the server such that properties are not returned.
+Per-request configuration option `materializeProperties` is not supported older driver versions.
+
+Also note that older drivers of different language variants will handle incoming properties differently with different
+serializers used. Drivers using `GraphSON` serializers will remain compatible, but may encounter deserialization errors
+with `GraphBinary`. Below is a table documenting GLV behaviors using `GraphBinary` when properties are returned by the
+default 3.7.x server, as well as if `ReferenceElementStrategy` is configured (i.e. mimic the behavior
+of a 3.6.x server). This can be observed with the results of `g.V().next()`. Note that only `gremlin-driver`
+and `gremlin-javacript` have the `properties` attribute in the Element objects, all other GLVs only have `id` and `label`.
+
+[cols="1,1,1"]
+|===
+|3.6.x drivers with `GraphBinary` |Behavior with default 3.7.x Server | Behavior with `ReferenceElementStrategy`
+
+|`gremlin-driver`
+|Properties returned as empty iterator
+|Properties returned as empty iterator
+
+|`gremlin-dotnet`
+|Skips properties in Elements
+|Skips properties in Elements
+
+|`gremlin-javascript`
+|Deserialization error
+|Properties returned as empty list
+
+|`gremlin-python`
+|Deserialization error
+|Skips properties in Elements
+
+|`gremlin-go`
+|Deserialization error
+|Skips properties in Elements
+|===
+
TIP: Consider utilizing `ReferenceElementStrategy` whenever creating a `GraphTraversalSource` in Java to ensure
the most portable Gremlin.
diff --git a/docs/src/upgrade/release-3.7.x.asciidoc b/docs/src/upgrade/release-3.7.x.asciidoc
index 4dda8ba..8fd5605 100644
--- a/docs/src/upgrade/release-3.7.x.asciidoc
+++ b/docs/src/upgrade/release-3.7.x.asciidoc
@@ -21,7 +21,7 @@
*Gremfir Master of the Pan Flute*
-=== TinkerPop 3.7.3
+== TinkerPop 3.7.3
*Release Date: NOT OFFICIALLY RELEASED YET*
@@ -30,6 +30,25 @@
=== Upgrading for Users
+==== GraphBinary Deserialization of Reference Element Properties with ReferenceElementStrategy
+When properties on element were introduced and returned as default in 3.7.0, setting ReferenceElementStrategy on the
+server meant as a way to send reference element without properties, for lightweight wire transfer and compatibility
+reasons. However, an issue was discovered where using GraphBinary, the 3.7.x server were not serializing properties as
+`null` as per IO spec, but as empty lists instead. This caused deserialization failures in Python, JavaScript and Go
+driver versions 3.6.x or below.
+
+A fix was introduced to correct such error, where Gremlin Server versions 3.7.3 and above will return element properties
+as `null` when ReferenceElementStrategy is applied, or when `token` is used with `materializedProperties` option in 3.7.x
+drivers. However, this also led to a change in 3.7.x driver behavior, where all non-Java drivers returns `null` instead
+of empty list. As such, an additional change was introduced in these GLVs, where `null` properties from reference elements
+will now deserialized into an empty list, to maintain such behavior with older 3.7.x drivers. This should minimize impact for users.
+
+One caveat is that when using 3.7.0 to 3.7.2 drivers to connect to 3.7.3 and above server, these drivers will not contain
+the deserialization change and return `null` as properties. In these cases, it is recommended to upgrade to 3.7.3 drivers.
+
+See: link:https://tinkerpop.apache.org/docs/x.y.z/reference/#_properties_of_elements[Properties of Elements],
+link:https://issues.apache.org/jira/browse/TINKERPOP-3105[TINKERPOP-3105]
+
== TinkerPop 3.7.2
*Release Date: April 8, 2024*
@@ -705,7 +724,7 @@
===== Enabling the previous behavior
Note that drivers from earlier versions like 3.5 and 3.6 will not be able to retrieve properties on elements. Older
-drivers connecting to 3.7.x servers should disable this functionality server-side in one of two ways:
+drivers connecting to 3.7.x servers should disable this functionality server-side:
*Configure Gremlin Server to not return properties* - update Gremlin Server initialization script with
`ReferenceElementStrategy`. This configuration is essentially the one used in older versions of the server by default.
@@ -715,7 +734,7 @@
globals << [g : traversal().withEmbedded(graph).withStrategies(ReferenceElementStrategy)]
----
-*Disable property inclusion per request* - the `materializeProperties` has a `tokens` option for this purpose.
+For 3.7 drivers, properties on elements can also be disabled per request using the `tokens` option with `materializeProperties`.
[source,csharp]
----
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/binary/types/EdgeSerializer.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/binary/types/EdgeSerializer.java
index fdfb94f..09b7c10 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/binary/types/EdgeSerializer.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/binary/types/EdgeSerializer.java
@@ -27,6 +27,7 @@
import org.apache.tinkerpop.gremlin.structure.io.binary.GraphBinaryWriter;
import org.apache.tinkerpop.gremlin.structure.util.detached.DetachedEdge;
import org.apache.tinkerpop.gremlin.structure.util.detached.DetachedVertex;
+import org.apache.tinkerpop.gremlin.structure.util.reference.ReferenceEdge;
import java.io.IOException;
import java.util.List;
@@ -81,7 +82,7 @@
// we don't serialize the parent Vertex for edges.
context.write(null, buffer);
- if (value.properties() == null) {
+ if (value instanceof ReferenceEdge) {
context.write(null, buffer);
}
else {
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/binary/types/VertexPropertySerializer.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/binary/types/VertexPropertySerializer.java
index 9850c13..c904de6 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/binary/types/VertexPropertySerializer.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/binary/types/VertexPropertySerializer.java
@@ -26,6 +26,7 @@
import org.apache.tinkerpop.gremlin.structure.VertexProperty;
import org.apache.tinkerpop.gremlin.structure.io.Buffer;
import org.apache.tinkerpop.gremlin.structure.util.detached.DetachedVertexProperty;
+import org.apache.tinkerpop.gremlin.structure.util.reference.ReferenceVertexProperty;
import java.io.IOException;
import java.util.Collections;
@@ -67,9 +68,14 @@
// we don't serialize the parent vertex, let's hold a place for it
context.write(null, buffer);
- final List<?> asList = value.graph().features().vertex().supportsMetaProperties() ?
- IteratorUtils.toList(value.properties()) :
- Collections.emptyList();
- context.write(asList, buffer);
+ if (value instanceof ReferenceVertexProperty) {
+ context.write(null, buffer);
+ }
+ else {
+ final List<?> asList = value.graph().features().vertex().supportsMetaProperties() ?
+ IteratorUtils.toList(value.properties()) :
+ Collections.emptyList();
+ context.write(asList, buffer);
+ }
}
}
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/binary/types/VertexSerializer.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/binary/types/VertexSerializer.java
index 5dbdfd1..ff5b99e 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/binary/types/VertexSerializer.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/binary/types/VertexSerializer.java
@@ -27,6 +27,7 @@
import org.apache.tinkerpop.gremlin.structure.io.binary.GraphBinaryWriter;
import org.apache.tinkerpop.gremlin.structure.util.detached.DetachedVertex;
import org.apache.tinkerpop.gremlin.structure.util.detached.DetachedVertexProperty;
+import org.apache.tinkerpop.gremlin.structure.util.reference.ReferenceVertex;
import java.io.IOException;
import java.util.List;
@@ -60,7 +61,7 @@
protected void writeValue(final Vertex value, final Buffer buffer, final GraphBinaryWriter context) throws IOException {
context.write(value.id(), buffer);
context.writeValue(value.label(), buffer, false);
- if (value.properties() == null) {
+ if (value instanceof ReferenceVertex) {
context.write(null, buffer);
}
else {
diff --git a/gremlin-dotnet/src/Gremlin.Net/Structure/IO/GraphBinary/Types/EdgeSerializer.cs b/gremlin-dotnet/src/Gremlin.Net/Structure/IO/GraphBinary/Types/EdgeSerializer.cs
index 13de71b..bfb9acf 100644
--- a/gremlin-dotnet/src/Gremlin.Net/Structure/IO/GraphBinary/Types/EdgeSerializer.cs
+++ b/gremlin-dotnet/src/Gremlin.Net/Structure/IO/GraphBinary/Types/EdgeSerializer.cs
@@ -21,6 +21,7 @@
#endregion
+using System;
using System.Collections.Generic;
using System.IO;
using System.Threading;
@@ -76,7 +77,7 @@
await reader.ReadAsync(stream, cancellationToken).ConfigureAwait(false);
var properties = await reader.ReadAsync(stream, cancellationToken).ConfigureAwait(false);
- var propertiesAsArray = (properties as List<object>)?.ToArray();
+ var propertiesAsArray = null == properties ? Array.Empty<object>() : (properties as List<object>)?.ToArray();
return new Edge(id, outV, label, inV, propertiesAsArray);
}
diff --git a/gremlin-dotnet/src/Gremlin.Net/Structure/IO/GraphBinary/Types/VertexPropertySerializer.cs b/gremlin-dotnet/src/Gremlin.Net/Structure/IO/GraphBinary/Types/VertexPropertySerializer.cs
index f347e0a..ea99aec 100644
--- a/gremlin-dotnet/src/Gremlin.Net/Structure/IO/GraphBinary/Types/VertexPropertySerializer.cs
+++ b/gremlin-dotnet/src/Gremlin.Net/Structure/IO/GraphBinary/Types/VertexPropertySerializer.cs
@@ -21,6 +21,7 @@
#endregion
+using System;
using System.Collections.Generic;
using System.IO;
using System.Threading;
@@ -68,7 +69,7 @@
await reader.ReadAsync(stream, cancellationToken).ConfigureAwait(false);
var properties = await reader.ReadAsync(stream, cancellationToken).ConfigureAwait(false);
- var propertiesAsArray = (properties as List<object>)?.ToArray();
+ var propertiesAsArray = null == properties ? Array.Empty<object>() : (properties as List<object>)?.ToArray();
return new VertexProperty(id, label, value, null, propertiesAsArray);
}
diff --git a/gremlin-dotnet/src/Gremlin.Net/Structure/IO/GraphBinary/Types/VertexSerializer.cs b/gremlin-dotnet/src/Gremlin.Net/Structure/IO/GraphBinary/Types/VertexSerializer.cs
index 0dd492c..51d2702 100644
--- a/gremlin-dotnet/src/Gremlin.Net/Structure/IO/GraphBinary/Types/VertexSerializer.cs
+++ b/gremlin-dotnet/src/Gremlin.Net/Structure/IO/GraphBinary/Types/VertexSerializer.cs
@@ -21,6 +21,7 @@
#endregion
+using System;
using System.Collections.Generic;
using System.IO;
using System.Threading;
@@ -57,7 +58,7 @@
var label = (string)await reader.ReadNonNullableValueAsync<string>(stream, cancellationToken).ConfigureAwait(false);
var properties = await reader.ReadAsync(stream, cancellationToken).ConfigureAwait(false);
- var propertiesAsArray = (properties as List<object>)?.ToArray();
+ var propertiesAsArray = null == properties ? Array.Empty<object>() : (properties as List<object>)?.ToArray();
return new Vertex(id, label, propertiesAsArray);
}
diff --git a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Process/Traversal/DriverRemoteConnection/GraphTraversalTests.cs b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Process/Traversal/DriverRemoteConnection/GraphTraversalTests.cs
index 2f48dca..717bf21 100644
--- a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Process/Traversal/DriverRemoteConnection/GraphTraversalTests.cs
+++ b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Process/Traversal/DriverRemoteConnection/GraphTraversalTests.cs
@@ -291,5 +291,47 @@
var exception = await Assert.ThrowsAsync<ResponseException>(async () => await tx.RollbackAsync());
Assert.Equal("ServerError: Graph does not support transactions", exception.Message);
}
+
+ [Fact]
+ public void shouldUseMaterializedPropertiesTokenInV()
+ {
+ var connection = _connectionFactory.CreateRemoteConnection();
+ var g = AnonymousTraversalSource.Traversal().WithRemote(connection);
+ var vertices = g.With("materializeProperties", "tokens").V().ToList();
+ foreach (var v in vertices)
+ {
+ Assert.NotNull(v);
+ // GraphSON will deserialize into null and GraphBinary to []
+ Assert.True(v.Properties == null || v.Properties.Length == 0);
+ }
+ }
+
+ [Fact]
+ public void shouldUseMaterializedPropertiesTokenInE()
+ {
+ var connection = _connectionFactory.CreateRemoteConnection();
+ var g = AnonymousTraversalSource.Traversal().WithRemote(connection);
+ var edges = g.With("materializeProperties", "tokens").E().ToList();
+ foreach (var e in edges)
+ {
+ Assert.NotNull(e);
+ // GraphSON will deserialize into null and GraphBinary to []
+ Assert.True(e.Properties == null || e.Properties.Length == 0);
+ }
+ }
+
+ [Fact]
+ public void shouldUseMaterializedPropertiesTokenInVP()
+ {
+ var connection = _connectionFactory.CreateRemoteConnection();
+ var g = AnonymousTraversalSource.Traversal().WithRemote(connection);
+ var vps = g.With("materializeProperties", "tokens").V().Properties<VertexProperty>().ToList();
+ foreach (var vp in vps)
+ {
+ Assert.NotNull(vp);
+ // GraphSON will deserialize into null and GraphBinary to []
+ Assert.True(vp.Properties == null || vp.Properties.Length == 0);
+ }
+ }
}
}
\ No newline at end of file
diff --git a/gremlin-go/driver/connection_test.go b/gremlin-go/driver/connection_test.go
index 92dce79..a4d0dd6 100644
--- a/gremlin-go/driver/connection_test.go
+++ b/gremlin-go/driver/connection_test.go
@@ -1230,4 +1230,41 @@
AssertMarkoVertexWithProperties(t, r)
})
+
+ t.Run("Test DriverRemoteConnection Traversal With materializeProperties in Modern Graph", func(t *testing.T) {
+ skipTestsIfNotEnabled(t, integrationTestSuiteName, testNoAuthEnable)
+
+ g := getModernGraph(t, testNoAuthUrl, &AuthInfo{}, &tls.Config{})
+ defer g.remoteConnection.Close()
+
+ vertices, err := g.With("materializeProperties", MaterializeProperties.Tokens).V().ToList()
+ assert.Nil(t, err)
+ for _, res := range vertices {
+ v, _ := res.GetVertex()
+ assert.Nil(t, err)
+ properties, ok := v.Properties.([]interface{})
+ assert.True(t, ok)
+ assert.Equal(t, 0, len(properties))
+ }
+
+ edges, err := g.With("materializeProperties", MaterializeProperties.Tokens).E().ToList()
+ assert.Nil(t, err)
+ for _, res := range edges {
+ e, _ := res.GetEdge()
+ assert.Nil(t, err)
+ properties, ok := e.Properties.([]interface{})
+ assert.True(t, ok)
+ assert.Equal(t, 0, len(properties))
+ }
+
+ vps, err := g.With("materializeProperties", MaterializeProperties.Tokens).V().Properties().ToList()
+ assert.Nil(t, err)
+ for _, res := range vps {
+ vp, _ := res.GetVertexProperty()
+ assert.Nil(t, err)
+ properties, ok := vp.Properties.([]interface{})
+ assert.True(t, ok)
+ assert.Equal(t, 0, len(properties))
+ }
+ })
}
diff --git a/gremlin-go/driver/graphBinary.go b/gremlin-go/driver/graphBinary.go
index 839a891..39c6d2f 100644
--- a/gremlin-go/driver/graphBinary.go
+++ b/gremlin-go/driver/graphBinary.go
@@ -1069,10 +1069,15 @@
}
v.Label = label.(string)
if readProperties {
- v.Properties, err = readFullyQualifiedNullable(data, i, true)
+ props, err := readFullyQualifiedNullable(data, i, true)
if err != nil {
return nil, err
}
+ // null properties are returned as empty slices
+ v.Properties = make([]interface{}, 0)
+ if props != nil {
+ v.Properties = props
+ }
}
return v, nil
}
@@ -1101,10 +1106,15 @@
}
e.OutV = *v.(*Vertex)
*i += 2
- e.Properties, err = readFullyQualifiedNullable(data, i, true)
+ props, err := readFullyQualifiedNullable(data, i, true)
if err != nil {
return nil, err
}
+ // null properties are returned as empty slices
+ e.Properties = make([]interface{}, 0)
+ if props != nil {
+ e.Properties = props
+ }
return e, nil
}
@@ -1149,7 +1159,11 @@
return nil, err
}
- vp.Properties = props
+ // null properties are returned as empty slices
+ vp.Properties = make([]interface{}, 0)
+ if props != nil {
+ vp.Properties = props
+ }
return vp, nil
}
diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/structure/io/binary/internals/VertexPropertySerializer.js b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/structure/io/binary/internals/VertexPropertySerializer.js
index b1cfc6d..597c0ab 100644
--- a/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/structure/io/binary/internals/VertexPropertySerializer.js
+++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/structure/io/binary/internals/VertexPropertySerializer.js
@@ -164,7 +164,10 @@
// TODO: should we verify that properties is null?
cursor = cursor.slice(properties_len);
- const v = new g.VertexProperty(id, label, value, properties);
+ // null properties are deserialized into empty lists
+ const vp_props = properties ? properties : [];
+
+ const v = new g.VertexProperty(id, label, value, vp_props);
return { v, len };
} catch (err) {
throw this.ioc.utils.des_error({ serializer: this, args: arguments, cursor, err });
diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/structure/io/binary/internals/VertexSerializer.js b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/structure/io/binary/internals/VertexSerializer.js
index 3d050c5..65ec4e4 100644
--- a/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/structure/io/binary/internals/VertexSerializer.js
+++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/structure/io/binary/internals/VertexSerializer.js
@@ -127,7 +127,10 @@
}
cursor = cursor.slice(properties_len);
- const v = new g.Vertex(id, label, properties);
+ // null properties are deserialized into empty lists
+ const vertex_props = properties ? properties : [];
+
+ const v = new g.Vertex(id, label, vertex_props);
return { v, len };
} catch (err) {
throw this.ioc.utils.des_error({ serializer: this, args: arguments, cursor, err });
diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/test/integration/traversal-test.js b/gremlin-javascript/src/main/javascript/gremlin-javascript/test/integration/traversal-test.js
index 5d15469..97b522e 100644
--- a/gremlin-javascript/src/main/javascript/gremlin-javascript/test/integration/traversal-test.js
+++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/test/integration/traversal-test.js
@@ -26,7 +26,7 @@
const assert = require('assert');
const { AssertionError } = require('assert');
const DriverRemoteConnection = require('../../lib/driver/driver-remote-connection');
-const { Vertex } = require('../../lib/structure/graph');
+const { Vertex, Edge, VertexProperty} = require('../../lib/structure/graph');
const { traversal } = require('../../lib/process/anonymous-traversal');
const { GraphTraversalSource, GraphTraversal, statics } = require('../../lib/process/graph-traversal');
const { SubgraphStrategy, ReadOnlyStrategy, SeedStrategy,
@@ -129,6 +129,36 @@
});
});
});
+ describe('#materializeProperties()', function () {
+ it('should skip vertex properties when tokens is set', function () {
+ var g = traversal().withRemote(connection);
+ return g.with_("materializeProperties", "tokens").V().toList().then(function (list) {
+ assert.ok(list);
+ assert.strictEqual(list.length, 6);
+ list.forEach(v => assert.ok(v instanceof Vertex));
+ list.forEach(v => assert.ok(v.properties === undefined || v.properties.length === 0));
+ });
+ });
+ it('should skip edge properties when tokens is set', function () {
+ var g = traversal().withRemote(connection);
+ return g.with_("materializeProperties", "tokens").E().toList().then(function (list) {
+ assert.ok(list);
+ assert.strictEqual(list.length, 6);
+ list.forEach(e => assert.ok(e instanceof Edge));
+ // due to the way edge is constructed, edge properties will be {} regardless if it's null or []
+ list.forEach(e => assert.strictEqual(Object.keys(e.properties).length, 0));
+ });
+ });
+ it('should skip vertex property properties when tokens is set', function () {
+ var g = traversal().withRemote(connection);
+ return g.with_("materializeProperties", "tokens").V().properties().toList().then(function (list) {
+ assert.ok(list);
+ assert.strictEqual(list.length, 12);
+ list.forEach(vp => assert.ok(vp instanceof VertexProperty));
+ list.forEach(vp => assert.ok(vp.properties === undefined || vp.properties.length === 0));
+ });
+ });
+ });
describe('lambdas', function() {
it('should handle 1-arg lambdas', function() {
const g = traversal().withRemote(connection);
diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/test/unit/graphbinary/AnySerializer-test.js b/gremlin-javascript/src/main/javascript/gremlin-javascript/test/unit/graphbinary/AnySerializer-test.js
index d4b5deb..b827ebb 100644
--- a/gremlin-javascript/src/main/javascript/gremlin-javascript/test/unit/graphbinary/AnySerializer-test.js
+++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/test/unit/graphbinary/AnySerializer-test.js
@@ -193,12 +193,12 @@
},
// VertexSerializer
- { v: new Vertex('A', 'Person', null),
+ { v: new Vertex('A', 'Person', []),
b: [
DataType.VERTEX,0x00,
DataType.STRING,0x00, 0x00,0x00,0x00,0x01, 0x41, // {id}
0x00,0x00,0x00,0x06, ...from('Person'), // {label}
- 0xFE,0x01, // {properties}
+ 0x09,0x00,0x00,0x00,0x00,0x00, // {properties}
]
},
@@ -457,13 +457,13 @@
// VERTEX
{ v:null, b:[0x11,0x01] },
- { v:new Vertex('00010203-0405-0607-0809-0a0b0c0d0e0f', 'A', null),
+ { v:new Vertex('00010203-0405-0607-0809-0a0b0c0d0e0f', 'A', []),
b:[0x11,0x00, 0x0C,0x00,0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0A,0x0B,0x0C,0x0D,0x0E,0x0F, 0x00,0x00,0x00,0x01,0x41, 0xFE,0x01]
},
// VERTEXPROPERTY
{ v:null, b:[0x12,0x01] },
- { v:new VertexProperty('00010203-0405-0607-0809-0a0b0c0d0e0f', 'Label', 42, null),
+ { v:new VertexProperty('00010203-0405-0607-0809-0a0b0c0d0e0f', 'Label', 42, []),
b:[
0x12,0x00,
0x0C,0x00, 0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0A,0x0B,0x0C,0x0D,0x0E,0x0F,
diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/test/unit/graphbinary/VertexPropertySerializer-test.js b/gremlin-javascript/src/main/javascript/gremlin-javascript/test/unit/graphbinary/VertexPropertySerializer-test.js
index 5b7f4a2..16444ae 100644
--- a/gremlin-javascript/src/main/javascript/gremlin-javascript/test/unit/graphbinary/VertexPropertySerializer-test.js
+++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/test/unit/graphbinary/VertexPropertySerializer-test.js
@@ -37,17 +37,17 @@
const cases = [
{ v:undefined, fq:1, b:[0x12,0x01], av:null },
- { v:undefined, fq:0, b:[0xFE,0x01, 0x00,0x00,0x00,0x00, 0xFE,0x01, 0xFE,0x01, 0xFE,0x01], av:new g.VertexProperty(null,'',null,null) },
+ { v:undefined, fq:0, b:[0xFE,0x01, 0x00,0x00,0x00,0x00, 0xFE,0x01, 0xFE,0x01, 0xFE,0x01], av:new g.VertexProperty(null,'',null,[]) },
{ v:null, fq:1, b:[0x12,0x01] },
- { v:null, fq:0, b:[0xFE,0x01, 0x00,0x00,0x00,0x00, 0xFE,0x01, 0xFE,0x01, 0xFE,0x01], av:new g.VertexProperty(null,'',null,null) },
+ { v:null, fq:0, b:[0xFE,0x01, 0x00,0x00,0x00,0x00, 0xFE,0x01, 0xFE,0x01, 0xFE,0x01], av:new g.VertexProperty(null,'',null,[]) },
- { v:new g.VertexProperty('Id', 'Label', 'Value', null),
+ { v:new g.VertexProperty('Id', 'Label', 'Value', []),
b:[
0x03,0x00, 0x00,0x00,0x00,0x02, ...from('Id'),
0x00,0x00,0x00,0x05, ...from('Label'),
0x03,0x00, 0x00,0x00,0x00,0x05, ...from('Value'),
0xFE,0x01,
- 0xFE,0x01,
+ 0x09,0x00,0x00,0x00,0x00,0x00,
]
},
diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/test/unit/graphbinary/VertexSerializer-test.js b/gremlin-javascript/src/main/javascript/gremlin-javascript/test/unit/graphbinary/VertexSerializer-test.js
index 9622593..774037b 100644
--- a/gremlin-javascript/src/main/javascript/gremlin-javascript/test/unit/graphbinary/VertexSerializer-test.js
+++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/test/unit/graphbinary/VertexSerializer-test.js
@@ -37,9 +37,9 @@
const cases = [
{ v:undefined, fq:1, b:[0x11,0x01], av:null },
- { v:undefined, fq:0, b:[0x03,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, 0xFE,0x01], av:new g.Vertex('','',null) },
+ { v:undefined, fq:0, b:[0x03,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, 0xFE,0x01], av:new g.Vertex('','',[]) },
{ v:null, fq:1, b:[0x11,0x01] },
- { v:null, fq:0, b:[0x03,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, 0xFE,0x01], av:new g.Vertex('','',null) },
+ { v:null, fq:0, b:[0x03,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, 0xFE,0x01], av:new g.Vertex('','',[]) },
{ v:new g.Vertex(42, 'A', -1),
b:[
@@ -51,7 +51,7 @@
// real case with id of UUID type, but JS does not have UUID type, it's presented as a string instead
{ des:1,
- v:new g.Vertex('28f38e3d-7739-4c99-8284-eb43db2a80f1', 'Person', null),
+ v:new g.Vertex('28f38e3d-7739-4c99-8284-eb43db2a80f1', 'Person', []),
b:[
0x0C,0x00, 0x28,0xF3,0x8E,0x3D, 0x77,0x39, 0x4C,0x99, 0x82,0x84, 0xEB,0x43,0xDB,0x2A,0x80,0xF1, // id
0x00,0x00,0x00,0x06, ...from('Person'), // label
diff --git a/gremlin-python/src/main/python/gremlin_python/structure/io/graphbinaryV1.py b/gremlin-python/src/main/python/gremlin_python/structure/io/graphbinaryV1.py
index cb9284f..20228bc 100644
--- a/gremlin-python/src/main/python/gremlin_python/structure/io/graphbinaryV1.py
+++ b/gremlin-python/src/main/python/gremlin_python/structure/io/graphbinaryV1.py
@@ -603,7 +603,9 @@
inv = Vertex(r.read_object(b), r.to_object(b, DataType.string, False))
outv = Vertex(r.read_object(b), r.to_object(b, DataType.string, False))
b.read(2)
- properties = r.read_object(b)
+ props = r.read_object(b)
+ # null properties are returned as empty lists
+ properties = [] if props is None else props
edge = Edge(edgeid, outv, edgelbl, inv, properties)
return edge
@@ -682,7 +684,12 @@
@classmethod
def _read_vertex(cls, b, r):
- vertex = Vertex(r.read_object(b), r.to_object(b, DataType.string, False), r.read_object(b))
+ vertex_id = r.read_object(b)
+ vertex_label = r.to_object(b, DataType.string, False)
+ props = r.read_object(b)
+ # null properties are returned as empty lists
+ properties = [] if props is None else props
+ vertex = Vertex(vertex_id, vertex_label, properties)
return vertex
@@ -709,7 +716,9 @@
def _read_vertexproperty(cls, b, r):
vp = VertexProperty(r.read_object(b), r.to_object(b, DataType.string, False), r.read_object(b), None)
b.read(2)
- vp.properties = r.read_object(b)
+ properties = r.read_object(b)
+ # null properties are returned as empty lists
+ vp.properties = [] if properties is None else properties
return vp
diff --git a/gremlin-python/src/main/python/tests/driver/test_client.py b/gremlin-python/src/main/python/tests/driver/test_client.py
index 3d84994..fdd2100 100644
--- a/gremlin-python/src/main/python/tests/driver/test_client.py
+++ b/gremlin-python/src/main/python/tests/driver/test_client.py
@@ -176,6 +176,19 @@
vertex = result[0]
assert 1 == vertex.id
assert 0 == len(vertex.properties)
+ ##
+ result_set = client.submit('g.with("materializeProperties", "tokens").E(7)')
+ result = result_set.all().result()
+ assert 1 == len(result)
+ edge = result[0]
+ assert 7 == edge.id
+ assert 0 == len(edge.properties)
+ ##
+ result_set = client.submit('g.with("materializeProperties", "tokens").V(1).properties()')
+ result = result_set.all().result()
+ assert 2 == len(result)
+ for vp in result:
+ assert 0 == len(vp.properties)
def test_client_bytecode(client):
diff --git a/gremlin-python/src/main/python/tests/driver/test_driver_remote_connection.py b/gremlin-python/src/main/python/tests/driver/test_driver_remote_connection.py
index 961b1b7..f6feff1 100644
--- a/gremlin-python/src/main/python/tests/driver/test_driver_remote_connection.py
+++ b/gremlin-python/src/main/python/tests/driver/test_driver_remote_connection.py
@@ -109,6 +109,21 @@
if not isinstance(remote_connection._client._message_serializer, GraphSONSerializersV2d0):
results = g.V().has('person', 'name', 'marko').both('knows').groupCount().by(__.values('name').fold()).next()
assert {tuple(['vadas']): 1, tuple(['josh']): 1} == results
+ # #
+ # test materializeProperties in V - GraphSON will deserialize into None and GraphBinary to []
+ results = g.with_("materializeProperties", "tokens").V().to_list()
+ for v in results:
+ assert v.properties is None or len(v.properties) == 0
+ # #
+ # test materializeProperties in E - GraphSON will deserialize into None and GraphBinary to []
+ results = g.with_("materializeProperties", "tokens").E().to_list()
+ for e in results:
+ assert e.properties is None or len(e.properties) == 0
+ # #
+ # test materializeProperties in VP - GraphSON will deserialize into None and GraphBinary to []
+ results = g.with_("materializeProperties", "tokens").V().properties().to_list()
+ for vp in results:
+ assert vp.properties is None or len(vp.properties) == 0
def test_lambda_traversals(self, remote_connection):
statics.load_statics(globals())
diff --git a/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/GremlinServerIntegrateTest.java b/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/GremlinServerIntegrateTest.java
index 0112131..0458c86 100644
--- a/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/GremlinServerIntegrateTest.java
+++ b/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/GremlinServerIntegrateTest.java
@@ -32,6 +32,7 @@
import org.apache.tinkerpop.gremlin.server.channel.WebSocketChannelizer;
import org.apache.tinkerpop.gremlin.server.channel.WebSocketTestChannelizer;
import org.apache.tinkerpop.gremlin.server.channel.WsAndHttpTestChannelizer;
+import org.apache.tinkerpop.gremlin.structure.VertexProperty;
import org.apache.tinkerpop.gremlin.util.ExceptionHelper;
import org.apache.tinkerpop.gremlin.TestHelper;
import org.apache.tinkerpop.gremlin.driver.Client;
@@ -63,6 +64,7 @@
import org.apache.tinkerpop.gremlin.structure.Graph;
import org.apache.tinkerpop.gremlin.structure.T;
import org.apache.tinkerpop.gremlin.structure.Vertex;
+import org.apache.tinkerpop.gremlin.structure.Edge;
import org.apache.tinkerpop.gremlin.util.function.Lambda;
import org.hamcrest.CoreMatchers;
import org.hamcrest.Matchers;
@@ -107,6 +109,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.fail;
import static org.junit.Assume.assumeThat;
@@ -1373,4 +1376,26 @@
assertEquals(ResponseStatusCode.SERVER_ERROR_FAIL_STEP, ((ResponseException) t).getResponseStatusCode());
}
}
+
+ @Test
+ public void shouldReturnEmptyPropertiesWithMaterializeProperties() {
+ final Cluster cluster = TestClientFactory.build().create();
+ final GraphTraversalSource g = traversal().withRemote(DriverRemoteConnection.using(cluster));
+
+ final Vertex v1 = g.addV("person").property("name", "marko").next();
+ final Vertex r1 = g.V().next();
+ assertEquals(v1.properties().next(), r1.properties().next());
+ final Vertex r1_tokens = g.with("materializeProperties", "tokens").V().next();
+ assertFalse(r1_tokens.properties().hasNext());
+
+ final VertexProperty vp1 = (VertexProperty) g.with("materializeProperties", "tokens").V().properties().next();
+ assertFalse(vp1.properties().hasNext());
+
+ final Vertex v2 = g.addV("person").property("name", "stephen").next();
+ g.V(v1).addE("knows").to(v2).property("weight", 0.75).iterate();
+ final Edge r2 = g.E().next();
+ assertEquals(r2.properties().next(), r2.properties().next());
+ final Edge r2_tokens = g.with("materializeProperties", "tokens").E().next();
+ assertFalse(r2_tokens.properties().hasNext());
+ }
}