You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tinkerpop.apache.org by ly...@apache.org on 2022/05/20 16:51:45 UTC
[tinkerpop] branch 3.5-dev updated: Gremlin-Go: Added support for additional types (#1663)
This is an automated email from the ASF dual-hosted git repository.
lyndonb pushed a commit to branch 3.5-dev
in repository https://gitbox.apache.org/repos/asf/tinkerpop.git
The following commit(s) were added to refs/heads/3.5-dev by this push:
new c8db98c5f9 Gremlin-Go: Added support for additional types (#1663)
c8db98c5f9 is described below
commit c8db98c5f9e584171780b450bbebba2b718791b7
Author: Valentyn Kahamlyk <vk...@users.noreply.github.com>
AuthorDate: Fri May 20 09:51:41 2022 -0700
Gremlin-Go: Added support for additional types (#1663)
---
gremlin-go/driver/connection_test.go | 76 ++++++++++++
gremlin-go/driver/graphBinary.go | 218 ++++++++++++++++++++++++++++------
gremlin-go/driver/graphBinary_test.go | 32 ++++-
gremlin-go/driver/serializer.go | 18 ++-
gremlin-go/driver/traversal.go | 41 +++++++
5 files changed, 343 insertions(+), 42 deletions(-)
diff --git a/gremlin-go/driver/connection_test.go b/gremlin-go/driver/connection_test.go
index 39204eb76f..41d6deb466 100644
--- a/gremlin-go/driver/connection_test.go
+++ b/gremlin-go/driver/connection_test.go
@@ -24,6 +24,7 @@ import (
"github.com/google/uuid"
"github.com/stretchr/testify/assert"
"golang.org/x/text/language"
+ "math/big"
"os"
"reflect"
"runtime"
@@ -845,6 +846,81 @@ func TestConnection(t *testing.T) {
resetGraph(t, g)
})
+ t.Run("Test DriverRemoteConnection GraphTraversal with Profile()", func(t *testing.T) {
+ skipTestsIfNotEnabled(t, integrationTestSuiteName, testNoAuthEnable)
+
+ // Initialize graph
+ g := initializeGraph(t, testNoAuthUrl, testNoAuthAuthInfo, testNoAuthTlsConfig)
+ defer g.remoteConnection.Close()
+
+ r, err := g.V().Has("name", "Lyndon").Values("foo").Profile().ToList()
+ assert.Nil(t, err)
+ assert.NotNil(t, r)
+ assert.Equal(t, 1, len(r))
+ metrics := r[0].result.(*TraversalMetrics)
+ assert.NotNil(t, metrics)
+ assert.GreaterOrEqual(t, len(metrics.Metrics), 2)
+
+ resetGraph(t, g)
+ })
+
+ t.Run("Test DriverRemoteConnection GraphTraversal with GremlinType", func(t *testing.T) {
+ skipTestsIfNotEnabled(t, integrationTestSuiteName, testNoAuthEnable)
+
+ // Initialize graph
+ g := initializeGraph(t, testNoAuthUrl, testNoAuthAuthInfo, testNoAuthTlsConfig)
+ defer g.remoteConnection.Close()
+
+ prop := &GremlinType{"java.lang.Object"}
+ i := g.AddV("type_test").Property("data", prop).Iterate()
+ err := <-i
+ assert.Nil(t, err)
+
+ r, err := g.V().HasLabel("type_test").Values("data").Next()
+ assert.Nil(t, err)
+ assert.Equal(t, prop, r.result.(*GremlinType))
+
+ resetGraph(t, g)
+ })
+
+ t.Run("Test DriverRemoteConnection GraphTraversal with BigDecimal", func(t *testing.T) {
+ skipTestsIfNotEnabled(t, integrationTestSuiteName, testNoAuthEnable)
+
+ // Initialize graph
+ g := initializeGraph(t, testNoAuthUrl, testNoAuthAuthInfo, testNoAuthTlsConfig)
+ defer g.remoteConnection.Close()
+
+ prop := &BigDecimal{11, *big.NewInt(int64(22))}
+ i := g.AddV("type_test").Property("data", prop).Iterate()
+ err := <-i
+ assert.Nil(t, err)
+
+ r, err := g.V().HasLabel("type_test").Values("data").Next()
+ assert.Nil(t, err)
+ assert.Equal(t, prop, r.result.(*BigDecimal))
+
+ resetGraph(t, g)
+ })
+
+ t.Run("Test DriverRemoteConnection GraphTraversal with byteBuffer", func(t *testing.T) {
+ skipTestsIfNotEnabled(t, integrationTestSuiteName, testNoAuthEnable)
+
+ // Initialize graph
+ g := initializeGraph(t, testNoAuthUrl, testNoAuthAuthInfo, testNoAuthTlsConfig)
+ defer g.remoteConnection.Close()
+
+ prop := &ByteBuffer{[]byte{byte(127), byte(255)}}
+ i := g.AddV("type_test").Property("data", prop).Iterate()
+ err := <-i
+ assert.Nil(t, err)
+
+ r, err := g.V().HasLabel("type_test").Values("data").Next()
+ assert.Nil(t, err)
+ assert.Equal(t, prop, r.result)
+
+ resetGraph(t, g)
+ })
+
t.Run("Test DriverRemoteConnection To Server Configured with Modern Graph", func(t *testing.T) {
skipTestsIfNotEnabled(t, integrationTestSuiteName, testNoAuthWithAliasEnable)
remote, err := NewDriverRemoteConnection(testNoAuthWithAliasUrl,
diff --git a/gremlin-go/driver/graphBinary.go b/gremlin-go/driver/graphBinary.go
index e717e0c791..dedd19bc3f 100644
--- a/gremlin-go/driver/graphBinary.go
+++ b/gremlin-go/driver/graphBinary.go
@@ -23,12 +23,11 @@ import (
"bytes"
"encoding/binary"
"fmt"
+ "github.com/google/uuid"
"math"
"math/big"
"reflect"
"time"
-
- "github.com/google/uuid"
)
// Version 1.0
@@ -43,6 +42,7 @@ const (
stringType dataType = 0x03
dateType dataType = 0x04
timestampType dataType = 0x05
+ classType dataType = 0x06
doubleType dataType = 0x07
floatType dataType = 0x08
listType dataType = 0x09
@@ -69,13 +69,17 @@ const (
scopeType dataType = 0x1f
tType dataType = 0x20
traverserType dataType = 0x21
+ bigDecimalType dataType = 0x22
bigIntegerType dataType = 0x23
byteType dataType = 0x24
+ byteBuffer dataType = 0x25
shortType dataType = 0x26
booleanType dataType = 0x27
textPType dataType = 0x28
traversalStrategyType dataType = 0x29
bulkSetType dataType = 0x2a
+ metricsType dataType = 0x2c
+ traversalMetricsType dataType = 0x2d
durationType dataType = 0x81
nullType dataType = 0xFE
)
@@ -131,6 +135,24 @@ func listWriter(value interface{}, buffer *bytes.Buffer, typeSerializer *graphBi
return buffer.Bytes(), nil
}
+// Format: {length}{value}
+func byteBufferWriter(value interface{}, buffer *bytes.Buffer, typeSerializer *graphBinaryTypeSerializer) ([]byte, error) {
+ var v ByteBuffer
+ if reflect.TypeOf(value).Kind() == reflect.Ptr {
+ v = *(value.(*ByteBuffer))
+ } else {
+ v = value.(ByteBuffer)
+ }
+
+ err := binary.Write(buffer, binary.BigEndian, int32(len(v.Data)))
+ if err != nil {
+ return nil, err
+ }
+ buffer.Write(v.Data)
+
+ return buffer.Bytes(), nil
+}
+
// Format: {length}{item_0}...{item_n}
// Item format: {type_code}{type_info}{value_flag}{value}
func mapWriter(value interface{}, buffer *bytes.Buffer, typeSerializer *graphBinaryTypeSerializer) ([]byte, error) {
@@ -230,7 +252,7 @@ func bytecodeWriter(value interface{}, buffer *bytes.Buffer, typeSerializer *gra
return buffer.Bytes(), nil
}
-func stringWriter(value interface{}, buffer *bytes.Buffer, typeSerializer *graphBinaryTypeSerializer) ([]byte, error) {
+func stringWriter(value interface{}, buffer *bytes.Buffer, _ *graphBinaryTypeSerializer) ([]byte, error) {
err := binary.Write(buffer, binary.BigEndian, int32(len(value.(string))))
if err != nil {
return nil, err
@@ -239,7 +261,7 @@ func stringWriter(value interface{}, buffer *bytes.Buffer, typeSerializer *graph
return buffer.Bytes(), err
}
-func longWriter(value interface{}, buffer *bytes.Buffer, typeSerializer *graphBinaryTypeSerializer) ([]byte, error) {
+func longWriter(value interface{}, buffer *bytes.Buffer, _ *graphBinaryTypeSerializer) ([]byte, error) {
switch v := value.(type) {
case int:
value = int64(v)
@@ -290,38 +312,15 @@ func getSignedBytesFromBigInt(n *big.Int) []byte {
return []byte{}
}
-func getBigIntFromSignedBytes(b []byte) *big.Int {
- var newBigInt = big.NewInt(0).SetBytes(b)
- var one = big.NewInt(1)
- if len(b) == 0 {
- return newBigInt
- }
- // If the first bit in the first element of the byte array is a 1, we need to interpret the byte array as a two's complement representation
- if b[0]&0x80 == 0x00 {
- newBigInt.SetBytes(b)
- return newBigInt
- }
- // Undo two's complement to byte array and set negative boolean to true
- length := uint((len(b)*8)/8+1) * 8
- b2 := new(big.Int).Sub(newBigInt, new(big.Int).Lsh(one, length)).Bytes()
-
- // Strip the resulting 0xff byte at the start of array
- b2 = b2[1:]
-
- // Strip any redundant 0x00 byte at the start of array
- if b2[0] == 0x00 {
- b2 = b2[1:]
- }
- newBigInt = big.NewInt(0)
- newBigInt.SetBytes(b2)
- newBigInt.Neg(newBigInt)
- return newBigInt
-}
-
// Format: {length}{value_0}...{value_n}
func bigIntWriter(value interface{}, buffer *bytes.Buffer, _ *graphBinaryTypeSerializer) ([]byte, error) {
- v := value.(*big.Int)
- signedBytes := getSignedBytesFromBigInt(v)
+ var v big.Int
+ if reflect.TypeOf(value).Kind() == reflect.Ptr {
+ v = *(value.(*big.Int))
+ } else {
+ v = value.(big.Int)
+ }
+ signedBytes := getSignedBytesFromBigInt(&v)
err := binary.Write(buffer, binary.BigEndian, int32(len(signedBytes)))
if err != nil {
return nil, err
@@ -335,6 +334,32 @@ func bigIntWriter(value interface{}, buffer *bytes.Buffer, _ *graphBinaryTypeSer
return buffer.Bytes(), nil
}
+// Format: {scale}{unscaled_value}
+func bigDecimalWriter(value interface{}, buffer *bytes.Buffer, typeSerializer *graphBinaryTypeSerializer) ([]byte, error) {
+ var v BigDecimal
+ if reflect.TypeOf(value).Kind() == reflect.Ptr {
+ v = *(value.(*BigDecimal))
+ } else {
+ v = value.(BigDecimal)
+ }
+ err := binary.Write(buffer, binary.BigEndian, v.Scale)
+ if err != nil {
+ return nil, err
+ }
+
+ return bigIntWriter(v.UnscaledValue, buffer, typeSerializer)
+}
+
+func classWriter(value interface{}, buffer *bytes.Buffer, typeSerializer *graphBinaryTypeSerializer) ([]byte, error) {
+ var v GremlinType
+ if reflect.TypeOf(value).Kind() == reflect.Ptr {
+ v = *(value.(*GremlinType))
+ } else {
+ v = value.(GremlinType)
+ }
+ return stringWriter(v.Fqcn, buffer, typeSerializer)
+}
+
// Format: {Id}{Label}{properties}
func vertexWriter(value interface{}, buffer *bytes.Buffer, typeSerializer *graphBinaryTypeSerializer) ([]byte, error) {
v := value.(*Vertex)
@@ -675,6 +700,16 @@ func (serializer *graphBinaryTypeSerializer) getType(val interface{}) (dataType,
return textPType, nil
case *Binding, Binding:
return bindingType, nil
+ case *BigDecimal, BigDecimal:
+ return bigDecimalType, nil
+ case *GremlinType, GremlinType:
+ return classType, nil
+ case *Metrics, Metrics:
+ return metricsType, nil
+ case *TraversalMetrics, TraversalMetrics:
+ return traversalMetricsType, nil
+ case *ByteBuffer, ByteBuffer:
+ return byteBuffer, nil
default:
switch reflect.TypeOf(val).Kind() {
case reflect.Map:
@@ -831,12 +866,20 @@ func readBigInt(data *[]byte, i *int) (interface{}, error) {
return newBigInt, nil
}
+func readBigDecimal(data *[]byte, i *int) (interface{}, error) {
+ bigDecimal := &BigDecimal{}
+ bigDecimal.Scale = readIntSafe(data, i)
+ unscaled, err := readBigInt(data, i)
+ if err != nil {
+ return nil, err
+ }
+ bigDecimal.UnscaledValue = *unscaled.(*big.Int)
+ return bigDecimal, nil
+}
+
func readUint32Safe(data *[]byte, i *int) uint32 {
return binary.BigEndian.Uint32(*readTemp(data, i, 4))
}
-func readUint32(data *[]byte, i *int) (interface{}, error) {
- return readUint32Safe(data, i), nil
-}
func readFloat(data *[]byte, i *int) (interface{}, error) {
return math.Float32frombits(binary.BigEndian.Uint32(*readTemp(data, i, 4))), nil
@@ -902,6 +945,16 @@ func readList(data *[]byte, i *int) (interface{}, error) {
return valList, nil
}
+func readByteBuffer(data *[]byte, i *int) (interface{}, error) {
+ r := &ByteBuffer{}
+ sz := readIntSafe(data, i)
+ r.Data = make([]byte, sz)
+ for j := int32(0); j < sz; j++ {
+ r.Data[j] = readByteSafe(data, i)
+ }
+ return r, nil
+}
+
func readMap(data *[]byte, i *int) (interface{}, error) {
sz := readUint32Safe(data, i)
var mapData = make(map[interface{}]interface{})
@@ -1139,6 +1192,97 @@ func bindingReader(data *[]byte, i *int) (interface{}, error) {
return b, nil
}
+// {id}{name}{duration}{counts}{annotations}{nested_metrics}
+func metricsReader(data *[]byte, i *int) (interface{}, error) {
+ metrics := new(Metrics)
+ val, err := readUnqualified(data, i, stringType, false)
+ if err != nil {
+ return nil, err
+ }
+ metrics.Id = val.(string)
+
+ val, err = readUnqualified(data, i, stringType, false)
+ if err != nil {
+ return nil, err
+ }
+ metrics.Name = val.(string)
+
+ dur, err := readLong(data, i)
+ if err != nil {
+ return nil, err
+ }
+ metrics.Duration = dur.(int64)
+
+ counts, err := readMap(data, i)
+ cmap := counts.(map[interface{}]interface{})
+ if err != nil {
+ return nil, err
+ }
+ metrics.Counts = make(map[string]int64, len(cmap))
+ for k := range cmap {
+ metrics.Counts[k.(string)] = cmap[k].(int64)
+ }
+
+ annotations, err := readMap(data, i)
+ if err != nil {
+ return nil, err
+ }
+ amap := annotations.(map[interface{}]interface{})
+ if err != nil {
+ return nil, err
+ }
+ metrics.Annotations = make(map[string]interface{}, len(amap))
+ for k := range amap {
+ metrics.Annotations[k.(string)] = amap[k]
+ }
+
+ nested, err := readList(data, i)
+ if err != nil {
+ return nil, err
+ }
+ list := nested.([]interface{})
+ metrics.NestedMetrics = make([]Metrics, len(list))
+ for i, metric := range list {
+ metrics.NestedMetrics[i] = metric.(Metrics)
+ }
+
+ return metrics, nil
+}
+
+// {id}{name}{duration}{counts}{annotations}{nested_metrics}
+func traversalMetricsReader(data *[]byte, i *int) (interface{}, error) {
+ m := new(TraversalMetrics)
+ dur, err := readLong(data, i)
+ if err != nil {
+ return nil, err
+ }
+ m.Duration = dur.(int64)
+
+ nested, err := readList(data, i)
+ if err != nil {
+ return nil, err
+ }
+ list := nested.([]interface{})
+ m.Metrics = make([]Metrics, len(list))
+ for i, metric := range list {
+ m.Metrics[i] = *metric.(*Metrics)
+ }
+
+ return m, nil
+}
+
+// Format: A String containing the fqcn.
+func readClass(data *[]byte, i *int) (interface{}, error) {
+ gremlinType := new(GremlinType)
+ str, err := readString(data, i)
+ if err != nil {
+ return nil, err
+ }
+ gremlinType.Fqcn = str.(string)
+
+ return gremlinType, nil
+}
+
func readUnqualified(data *[]byte, i *int, dataTyp dataType, nullable bool) (interface{}, error) {
if nullable && readByteSafe(data, i) == valueFlagNull {
return getDefaultValue(dataTyp), nil
diff --git a/gremlin-go/driver/graphBinary_test.go b/gremlin-go/driver/graphBinary_test.go
index 4f429397be..1112076289 100644
--- a/gremlin-go/driver/graphBinary_test.go
+++ b/gremlin-go/driver/graphBinary_test.go
@@ -88,6 +88,16 @@ func TestGraphBinaryV1(t *testing.T) {
assert.Nil(t, err)
assert.Equal(t, str, res)
})
+ t.Run("read-write GremlinType", func(t *testing.T) {
+ pos := 0
+ var buffer bytes.Buffer
+ source := &GremlinType{"test fqcn"}
+ buf, err := classWriter(source, &buffer, nil)
+ assert.Nil(t, err)
+ res, err := readClass(&buf, &pos)
+ assert.Nil(t, err)
+ assert.Equal(t, source, res)
+ })
t.Run("read-write bool", func(t *testing.T) {
pos := 0
var buffer bytes.Buffer
@@ -107,6 +117,16 @@ func TestGraphBinaryV1(t *testing.T) {
assert.Nil(t, err)
assert.True(t, res.(bool))
})
+ t.Run("read-write BigDecimal", func(t *testing.T) {
+ pos := 0
+ var buffer bytes.Buffer
+ source := &BigDecimal{11, *big.NewInt(int64(22))}
+ buf, err := bigDecimalWriter(source, &buffer, nil)
+ assert.Nil(t, err)
+ res, err := readBigDecimal(&buf, &pos)
+ assert.Nil(t, err)
+ assert.Equal(t, source, res)
+ })
t.Run("read-write int", func(t *testing.T) {
pos := 0
var buffer bytes.Buffer
@@ -141,7 +161,7 @@ func TestGraphBinaryV1(t *testing.T) {
pos := 0
var buffer bytes.Buffer
source := big.NewInt(123)
- buf, err := bigIntWriter(source, &buffer, nil)
+ buf, err := bigIntWriter(*source, &buffer, nil)
assert.Nil(t, err)
res, err := readBigInt(&buf, &pos)
assert.Nil(t, err)
@@ -157,6 +177,16 @@ func TestGraphBinaryV1(t *testing.T) {
assert.Nil(t, err)
assert.Equal(t, source, res)
})
+ t.Run("read-write byteBuffer", func(t *testing.T) {
+ pos := 0
+ var buffer bytes.Buffer
+ source := &ByteBuffer{[]byte{byte(127), byte(255)}}
+ buf, err := byteBufferWriter(source, &buffer, nil)
+ assert.Nil(t, err)
+ res, err := readByteBuffer(&buf, &pos)
+ assert.Nil(t, err)
+ assert.Equal(t, source, res)
+ })
t.Run("read-write set", func(t *testing.T) {
pos := 0
var buffer bytes.Buffer
diff --git a/gremlin-go/driver/serializer.go b/gremlin-go/driver/serializer.go
index 097ff23182..187d20e858 100644
--- a/gremlin-go/driver/serializer.go
+++ b/gremlin-go/driver/serializer.go
@@ -222,6 +222,7 @@ func initSerializers() {
serializers = map[dataType]writer{
bytecodeType: bytecodeWriter,
stringType: stringWriter,
+ bigDecimalType: bigDecimalWriter,
bigIntegerType: bigIntWriter,
longType: longWriter,
intType: intWriter,
@@ -271,6 +272,8 @@ func initSerializers() {
bindingType: bindingWriter,
mapType: mapWriter,
listType: listWriter,
+ byteBuffer: byteBufferWriter,
+ classType: classWriter,
}
}
}
@@ -284,16 +287,19 @@ func initDeserializers() {
shortType: readShort,
intType: readInt,
longType: readLong,
+ bigDecimalType: readBigDecimal,
bigIntegerType: readBigInt,
floatType: readFloat,
doubleType: readDouble,
stringType: readString,
// Composite
- listType: readList,
- mapType: readMap,
- setType: readSet,
- uuidType: readUuid,
+ listType: readList,
+ mapType: readMap,
+ setType: readSet,
+ uuidType: readUuid,
+ byteBuffer: readByteBuffer,
+ classType: readClass,
// Date Time
dateType: timeReader,
@@ -311,6 +317,10 @@ func initDeserializers() {
tType: enumReader,
directionType: enumReader,
bindingType: bindingReader,
+
+ // Metrics
+ metricsType: metricsReader,
+ traversalMetricsType: traversalMetricsReader,
}
}
}
diff --git a/gremlin-go/driver/traversal.go b/gremlin-go/driver/traversal.go
index a1e5b9cc19..314476d5ad 100644
--- a/gremlin-go/driver/traversal.go
+++ b/gremlin-go/driver/traversal.go
@@ -19,6 +19,8 @@ under the License.
package gremlingo
+import "math/big"
+
// Traverser is the objects propagating through the traversal.
type Traverser struct {
bulk int64
@@ -525,6 +527,7 @@ type withOptions struct {
Map int32
}
+// WithOptions holds configuration options to be passed to the GraphTraversal.
var WithOptions = withOptions{
Tokens: "~tinkerpop.valueMap.tokens",
None: 0,
@@ -537,3 +540,41 @@ var WithOptions = withOptions{
List: 0,
Map: 1,
}
+
+// Metrics holds metrics data; typically for .profile()-step analysis. Metrics may be nested. Nesting enables
+// the ability to capture explicit metrics for multiple distinct operations. Annotations are used to store
+// miscellaneous notes that might be useful to a developer when examining results, such as index coverage
+// for Steps in a Traversal.
+type Metrics struct {
+ Id string
+ Name string
+ // the duration in nanoseconds.
+ Duration int64
+ Counts map[string]int64
+ Annotations map[string]interface{}
+ NestedMetrics []Metrics
+}
+
+// TraversalMetrics contains the Metrics gathered for a Traversal as the result of the .profile()-step.
+type TraversalMetrics struct {
+ // the duration in nanoseconds.
+ Duration int64
+ Metrics []Metrics
+}
+
+// GremlinType represents the GraphBinary type Class which can be used to serialize a class.
+type GremlinType struct {
+ Fqcn string
+}
+
+// BigDecimal represents an arbitrary-precision signed decimal number, consisting of an arbitrary precision integer
+// unscaled value and a 32-bit integer scale.
+type BigDecimal struct {
+ Scale int32
+ UnscaledValue big.Int
+}
+
+// ByteBuffer represents the GraphBinary type ByteBuffer which can be used to serialize a binary data.
+type ByteBuffer struct {
+ Data []byte
+}