This is an automated email from the ASF dual-hosted git repository.
colegreer pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/tinkerpop.git
The following commit(s) were added to refs/heads/master by this push:
new c7ddc90e79 Fix misc GremlinLang and GraphBinary issues in Go (#3371)
c7ddc90e79 is described below
commit c7ddc90e7946ff60ce873bb035a051987c5a308c
Author: Cole Greer <[email protected]>
AuthorDate: Mon Mar 30 12:56:31 2026 -0700
Fix misc GremlinLang and GraphBinary issues in Go (#3371)
Fixes several issues in the Go GLV related to GremlinLang arg conversions
and GraphBinary serialization/deserialization:
1. GraphBinary enum deserialization returns raw strings instead of typed
enums — readEnum() was returning plain strings for T, Direction, Merge, and
GType values. This caused map lookups using typed enum constants (e.g., T.Id)
to fail because the deserialized keys were strings like "id" rather than
t("id"). The fix passes the dataType into readEnum() and returns properly typed
values.
2. VertexProperty missing Key field on deserialization — When deserializing
a VertexProperty from GraphBinary, the Key field was not being populated (only
Label was set on the embedded Element). The fix sets Key to the label value,
matching the expected behavior.
3. Datetime serialization in GremlinLang lacks millisecond precision —
time.Time values were formatted using time.RFC3339 (second precision),
producing strings like "2021-01-01T09:30:00Z". Other GLVs use millisecond
precision. The fix switches to a custom format (2006-01-02T15:04:05.000Z07:00)
so datetimes always include .000 milliseconds, ensuring consistency across
language variants.
---
gremlin-go/driver/graphBinaryDeserializer.go | 26 ++++--
gremlin-go/driver/graphBinaryDeserializer_test.go | 104 ++++++++++++++++++++++
gremlin-go/driver/gremlinlang.go | 2 +-
gremlin-go/driver/gremlinlang_test.go | 12 +--
gremlin-go/driver/traversal_test.go | 12 +--
5 files changed, 138 insertions(+), 18 deletions(-)
diff --git a/gremlin-go/driver/graphBinaryDeserializer.go
b/gremlin-go/driver/graphBinaryDeserializer.go
index 8a61541599..dece9bcb3f 100644
--- a/gremlin-go/driver/graphBinaryDeserializer.go
+++ b/gremlin-go/driver/graphBinaryDeserializer.go
@@ -254,7 +254,7 @@ func (d *GraphBinaryDeserializer) readValue(dt dataType,
flag byte) (interface{}
case byteBuffer:
return d.readByteBuffer()
case tType, directionType, mergeType, gTypeType:
- return d.readEnum()
+ return d.readEnum(dt)
default:
return nil,
newError(err0408GetSerializerToReadUnknownTypeError, dt)
}
@@ -479,6 +479,7 @@ func (d *GraphBinaryDeserializer) readVertexProperty()
(*VertexProperty, error)
}
vp := &VertexProperty{
Element: Element{Id: id, Label: label},
+ Key: label,
Value: value,
}
vp.Properties = make([]interface{}, 0)
@@ -575,14 +576,29 @@ func (d *GraphBinaryDeserializer) readByteBuffer()
(*ByteBuffer, error) {
return &ByteBuffer{Data: data}, nil
}
-func (d *GraphBinaryDeserializer) readEnum() (string, error) {
+func (d *GraphBinaryDeserializer) readEnum(dt dataType) (interface{}, error) {
if _, err := d.readByte(); err != nil { // type code (string)
- return "", err
+ return nil, err
}
if _, err := d.readByte(); err != nil { // null flag
- return "", err
+ return nil, err
+ }
+ s, err := d.readString()
+ if err != nil {
+ return nil, err
+ }
+ switch dt {
+ case tType:
+ return t(s), nil
+ case directionType:
+ return direction(s), nil
+ case mergeType:
+ return merge(s), nil
+ case gTypeType:
+ return gType(s), nil
+ default:
+ return s, nil
}
- return d.readString()
}
// ReadStatus reads the response status after the EndOfStream marker.
diff --git a/gremlin-go/driver/graphBinaryDeserializer_test.go
b/gremlin-go/driver/graphBinaryDeserializer_test.go
index 2e9517ed15..044da9a877 100644
--- a/gremlin-go/driver/graphBinaryDeserializer_test.go
+++ b/gremlin-go/driver/graphBinaryDeserializer_test.go
@@ -27,6 +27,7 @@ import (
"time"
"github.com/stretchr/testify/assert"
+ "golang.org/x/text/language"
)
// slowReader simulates a network stream that delivers data in chunks with
delays.
@@ -369,6 +370,109 @@ func TestGraphBinaryDeserializerComplexTypes(t
*testing.T) {
assert.Equal(t, "person", v.Label)
})
+ t.Run("round-trip VertexProperty preserves Key field", func(t
*testing.T) {
+ original := &VertexProperty{
+ Element: Element{Id: int32(1), Label: "name"},
+ Key: "name",
+ Value: "marko",
+ }
+
+ var buf bytes.Buffer
+ ser := newGraphBinarySerializer(newLogHandler(&defaultLogger{},
Error, language.English))
+ err := ser.ser.write(original, &buf)
+ assert.Nil(t, err)
+
+ d := NewGraphBinaryDeserializer(&buf)
+ val, err := d.ReadFullyQualified()
+ assert.Nil(t, err)
+
+ vp := val.(*VertexProperty)
+ assert.Equal(t, "marko", vp.Value)
+ assert.Equal(t, "name", vp.Key, "VertexProperty.Key should
survive round-trip")
+ })
+
+ t.Run("round-trip map with T enum keys preserves types", func(t
*testing.T) {
+ original := map[interface{}]interface{}{
+ T.Id: "1",
+ T.Label: "person",
+ "name": "marko",
+ }
+
+ var buf bytes.Buffer
+ ser := newGraphBinarySerializer(newLogHandler(&defaultLogger{},
Error, language.English))
+ err := ser.ser.write(original, &buf)
+ assert.Nil(t, err)
+
+ d := NewGraphBinaryDeserializer(&buf)
+ val, err := d.ReadFullyQualified()
+ assert.Nil(t, err)
+
+ m := val.(map[interface{}]interface{})
+ assert.Equal(t, "1", m[T.Id], "T.Id key should survive
round-trip")
+ assert.Equal(t, "person", m[T.Label], "T.Label key should
survive round-trip")
+ assert.Equal(t, "marko", m["name"])
+ })
+
+ t.Run("round-trip map with Direction enum keys preserves types", func(t
*testing.T) {
+ original := map[interface{}]interface{}{
+ Direction.Out: "outVertex",
+ Direction.In: "inVertex",
+ }
+
+ var buf bytes.Buffer
+ ser := newGraphBinarySerializer(newLogHandler(&defaultLogger{},
Error, language.English))
+ err := ser.ser.write(original, &buf)
+ assert.Nil(t, err)
+
+ d := NewGraphBinaryDeserializer(&buf)
+ val, err := d.ReadFullyQualified()
+ assert.Nil(t, err)
+
+ m := val.(map[interface{}]interface{})
+ assert.Equal(t, "outVertex", m[Direction.Out], "Direction.Out
key should survive round-trip")
+ assert.Equal(t, "inVertex", m[Direction.In], "Direction.In key
should survive round-trip")
+ })
+
+ t.Run("round-trip map with Merge enum keys preserves types", func(t
*testing.T) {
+ original := map[interface{}]interface{}{
+ Merge.OnCreate: "created",
+ Merge.OnMatch: "matched",
+ }
+
+ var buf bytes.Buffer
+ ser := newGraphBinarySerializer(newLogHandler(&defaultLogger{},
Error, language.English))
+ err := ser.ser.write(original, &buf)
+ assert.Nil(t, err)
+
+ d := NewGraphBinaryDeserializer(&buf)
+ val, err := d.ReadFullyQualified()
+ assert.Nil(t, err)
+
+ m := val.(map[interface{}]interface{})
+ assert.Equal(t, "created", m[Merge.OnCreate], "Merge.OnCreate
key should survive round-trip")
+ assert.Equal(t, "matched", m[Merge.OnMatch], "Merge.OnMatch key
should survive round-trip")
+ })
+
+ t.Run("round-trip map with GType enum keys preserves types", func(t
*testing.T) {
+ original := map[interface{}]interface{}{
+ GType.Int: "integer",
+ GType.String: "string",
+ }
+
+ var buf bytes.Buffer
+ ser := newGraphBinarySerializer(newLogHandler(&defaultLogger{},
Error, language.English))
+ err := ser.ser.write(original, &buf)
+ assert.Nil(t, err)
+
+ d := NewGraphBinaryDeserializer(&buf)
+ val, err := d.ReadFullyQualified()
+ assert.Nil(t, err)
+
+ m := val.(map[interface{}]interface{})
+ assert.Equal(t, "integer", m[GType.Int], "GType.Int key should
survive round-trip")
+ assert.Equal(t, "string", m[GType.String], "GType.String key
should survive round-trip")
+ })
+
t.Run("reads list of integers from chunked stream", func(t *testing.T) {
// List type split across chunks
chunk1 := []byte{
diff --git a/gremlin-go/driver/gremlinlang.go b/gremlin-go/driver/gremlinlang.go
index 92a66b28b6..baec7e6547 100644
--- a/gremlin-go/driver/gremlinlang.go
+++ b/gremlin-go/driver/gremlinlang.go
@@ -173,7 +173,7 @@ func (gl *GremlinLang) argAsString(arg interface{})
(string, error) {
case *BigDecimal:
return fmt.Sprintf("%vM", v.Value()), nil
case time.Time:
- return fmt.Sprintf("datetime(\"%v\")", v.Format(time.RFC3339)),
nil
+ return fmt.Sprintf("datetime(\"%v\")",
v.Format("2006-01-02T15:04:05.000Z07:00")), nil // equivalent to time.RFC3339
but with ms precision instead of seconds
case cardinality, column, direction, operator, order, pick, pop,
barrier, scope, t, merge, gType:
name := reflect.ValueOf(v).Type().Name()
return fmt.Sprintf("%s.%s", strings.ToUpper(name[:1])+name[1:],
v), nil
diff --git a/gremlin-go/driver/gremlinlang_test.go
b/gremlin-go/driver/gremlinlang_test.go
index 30f66696b7..7160056f24 100644
--- a/gremlin-go/driver/gremlinlang_test.go
+++ b/gremlin-go/driver/gremlinlang_test.go
@@ -385,7 +385,7 @@ func Test_GremlinLang(t *testing.T) {
assert: func(g *GraphTraversalSource) *GraphTraversal {
return g.V().Has("date", P.Gt(time.Date(2021,
1, 1, 9, 30, 0, 0, time.UTC)))
},
- equals:
"g.V().has(\"date\",gt(datetime(\"2021-01-01T09:30:00Z\")))",
+ equals:
"g.V().has(\"date\",gt(datetime(\"2021-01-01T09:30:00.000Z\")))",
},
{
assert: func(g *GraphTraversalSource) *GraphTraversal {
@@ -481,31 +481,31 @@ func Test_GremlinLang(t *testing.T) {
assert: func(g *GraphTraversalSource) *GraphTraversal {
return g.V().Has("date", time.Date(2021, 2, 22,
0, 0, 0, 0, time.UTC))
},
- equals:
"g.V().has(\"date\",datetime(\"2021-02-22T00:00:00Z\"))",
+ equals:
"g.V().has(\"date\",datetime(\"2021-02-22T00:00:00.000Z\"))",
},
{
assert: func(g *GraphTraversalSource) *GraphTraversal {
return g.V().Has("date",
P.Within(time.Date(2021, 2, 22, 0, 0, 0, 0, time.UTC), time.Date(2021, 1, 1, 0,
0, 0, 0, time.UTC)))
},
- equals:
"g.V().has(\"date\",within([datetime(\"2021-02-22T00:00:00Z\"),datetime(\"2021-01-01T00:00:00Z\")]))",
+ equals:
"g.V().has(\"date\",within([datetime(\"2021-02-22T00:00:00.000Z\"),datetime(\"2021-01-01T00:00:00.000Z\")]))",
},
{
assert: func(g *GraphTraversalSource) *GraphTraversal {
return g.V().Has("date",
P.Between(time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC), time.Date(2021, 2, 22,
0, 0, 0, 0, time.UTC)))
},
- equals:
"g.V().has(\"date\",between(datetime(\"2021-01-01T00:00:00Z\"),datetime(\"2021-02-22T00:00:00Z\")))",
+ equals:
"g.V().has(\"date\",between(datetime(\"2021-01-01T00:00:00.000Z\"),datetime(\"2021-02-22T00:00:00.000Z\")))",
},
{
assert: func(g *GraphTraversalSource) *GraphTraversal {
return g.V().Has("date",
P.Inside(time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC), time.Date(2021, 2, 22, 0,
0, 0, 0, time.UTC)))
},
- equals:
"g.V().has(\"date\",inside(datetime(\"2021-01-01T00:00:00Z\"),datetime(\"2021-02-22T00:00:00Z\")))",
+ equals:
"g.V().has(\"date\",inside(datetime(\"2021-01-01T00:00:00.000Z\"),datetime(\"2021-02-22T00:00:00.000Z\")))",
},
{
assert: func(g *GraphTraversalSource) *GraphTraversal {
return g.V().Has("date", P.Gt(time.Date(2021,
1, 1, 9, 30, 0, 0, time.UTC)))
},
- equals:
"g.V().has(\"date\",gt(datetime(\"2021-01-01T09:30:00Z\")))",
+ equals:
"g.V().has(\"date\",gt(datetime(\"2021-01-01T09:30:00.000Z\")))",
},
{
assert: func(g *GraphTraversalSource) *GraphTraversal {
diff --git a/gremlin-go/driver/traversal_test.go
b/gremlin-go/driver/traversal_test.go
index 95eec7a5cb..e0795567b6 100644
--- a/gremlin-go/driver/traversal_test.go
+++ b/gremlin-go/driver/traversal_test.go
@@ -396,7 +396,7 @@ func TestTraversal(t *testing.T) {
// Expect each result to contain a id. No additional items.
for _, result := range results {
- key, ok :=
result.GetInterface().(map[interface{}]interface{})["id"]
+ key, ok :=
result.GetInterface().(map[interface{}]interface{})[T.Id]
assert.True(t, ok)
assert.NotNil(t, key)
@@ -417,7 +417,7 @@ func TestTraversal(t *testing.T) {
// Expect each result to contain a key. No additional items.
for _, result := range results {
- key, ok :=
result.GetInterface().(map[interface{}]interface{})["key"]
+ key, ok :=
result.GetInterface().(map[interface{}]interface{})[T.Key]
assert.True(t, ok)
assert.NotNil(t, key)
@@ -438,7 +438,7 @@ func TestTraversal(t *testing.T) {
// Expect each result to contain a value. No additional items.
for _, result := range results {
- key, ok :=
result.GetInterface().(map[interface{}]interface{})["value"]
+ key, ok :=
result.GetInterface().(map[interface{}]interface{})[T.Value]
assert.True(t, ok)
assert.NotNil(t, key)
@@ -459,15 +459,15 @@ func TestTraversal(t *testing.T) {
// Expect each result to contain an id, key, and value. No
additional items.
for _, result := range results {
- id, ok :=
result.GetInterface().(map[interface{}]interface{})["id"]
+ id, ok :=
result.GetInterface().(map[interface{}]interface{})[T.Id]
assert.True(t, ok)
assert.NotNil(t, id)
- key, ok :=
result.GetInterface().(map[interface{}]interface{})["key"]
+ key, ok :=
result.GetInterface().(map[interface{}]interface{})[T.Key]
assert.True(t, ok)
assert.NotNil(t, key)
- value, ok :=
result.GetInterface().(map[interface{}]interface{})["value"]
+ value, ok :=
result.GetInterface().(map[interface{}]interface{})[T.Value]
assert.True(t, ok)
assert.NotNil(t, value)