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
+}