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
commit 2553564678a2e6a4223dc09fc645d23c5f7b2d1b Merge: cad9022e76 8aad40518b Author: Cole Greer <[email protected]> AuthorDate: Mon Jan 12 13:11:08 2026 -0800 Merge branch '3.8-dev' CHANGELOG.asciidoc | 5 +- gremlin-examples/gremlin-go/basic_gremlin.go | 17 +++--- gremlin-examples/gremlin-go/connections.go | 31 +++++----- gremlin-examples/gremlin-go/modern_traversals.go | 3 +- gremlin-go/docker-compose.yml | 8 ++- gremlin-go/driver/client.go | 4 +- gremlin-go/driver/request.go | 44 +++++++++++++- gremlin-go/driver/request_test.go | 8 +-- gremlin-go/driver/response.go | 20 +++---- gremlin-go/driver/resultSet.go | 45 ++++++++++++++ gremlin-go/driver/serializer.go | 76 ++++++++++++++++++------ gremlin-go/driver/serializer_test.go | 28 ++++----- gremlin-go/examples/basic_gremlin.go | 23 +++++-- gremlin-go/examples/connections.go | 29 +++++---- gremlin-go/examples/modern_traversals.go | 14 ++++- 15 files changed, 261 insertions(+), 94 deletions(-) diff --cc CHANGELOG.asciidoc index 64eb99ab8d,27c1e7673e..2db6f4de28 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@@ -14,75 -14,7 +14,76 @@@ WITHOUT WARRANTIES OR CONDITIONS OF AN See the License for the specific language governing permissions and limitations under the License. //// -= TinkerPop3 CHANGELOG += TinkerPop CHANGELOG + +== TinkerPop 4.0.0 (Gremlin's Wildest Dreams) + +image::https://raw.githubusercontent.com/apache/tinkerpop/master/docs/static/images/gremlins-wildest-dreams.png[width=185] + +[[release-4-0-0]] +=== TinkerPop 4.0.0 (NOT OFFICIALLY RELEASED YET) + +* Bumped SLF4j to 2.0.16. +* Modified grammar to make `discard()` usage more consistent as a filter step where it can now be used to chain additional traversal steps and be used anonymously. +* Bumped GMavenPlus to 4.1.1 ++* Removed `Meta` field from `ResponseResult` struct in `gremlin-go` + +[[release-4-0-0-beta-1]] +=== TinkerPop 4.0.0-beta.1 (January 17, 2025) + +* Added support for deserialization of `Set` for `gremlin-javascript`. +* Added grammar-based `Translator` for all languages including explicit ones for Java and anonymization. +* Removed old `Translator` infrastructure. +* Removed grammar support for enums to be used as variables. +* Changed `valueMap` in grammar to disallow the `boolean` argument as a variable to avoid ambiguous step calls. +* Added integer overflow checks for `sum()`. +* Modified Gremlin Server to only support instantiation of `authentication.authenticationHandler` with three-arg constructor. +* Removed previously deprecated two-arg constructors for `authentication.authenticationHandler` implementations. +* Removed previously deprecated one-arg constructor for `AbstractAuthenticationHandler`. +* Renamed the traversal discarding `none()` step to `discard()`. +* Added new list filtering step `none()`. +* Replaced `gremlin-groovy` with `gremlin-lang` as the default language in `gremlin-server`. +* Changed `sum()` to retain the type common to the stream rather than always promoting to `long` given the need for it to multiple by the `long` bulk value for the traverser. +* Added support for `Set` in GraphSON and GraphBinary serialization for `gremlin-javascript`, where it previously just converted to array. +* Added `Set` syntax in `gremlin-language`. +* Modified RequestInterceptor to be a `UnaryOperator<HttpRequest>` to abstract the underlying implementation. +* Removed the `gremlin-archetype` module in favor of newer sample applications in each GLV's `examples` folder. +* Bumped to `commons-collection4`. +* Switched to HTTP protocol in `gremlin-python` and replaced GraphSONV2, GraphSONV2 & GraphBinaryV1 with GraphBinaryV4 +* Added support for chunked transfer in `gremlin-python` +* Added TypeScript & ECMAScript module support. +* Improved graph structures type definitions in TypeScript. +* Removed usage of `Bytecode` for Gremlin Server and Java GLV, script engines and `GremlinExecutor` will not be able to handle `Bytecode`. +* Removed `Bytecode` based authorization. +* Added `GremlinLang` which allows to generate gremlin-lang compatible string based on Traversal. +* Removed serialization support for `Bindings` and `Bytecode`. +* Changed `EmbeddedRemoteConnection` to prefer the grammar-based translator. +* Removed `Client.submit(Traversal)` as a mechanism for submitting traversal, prefer `DriverRemoteConnection` instead. +* Removed usage of `Bytecode` from `gremlin-python`. +* Added `auth` module in `gremlin-python` for pluggable authentication. +* Fixed `GremlinLangScriptEngine` handling for some strategies. +* Updated Docker test suite set-up in `gremlin-python` to work with HTTP driver/server. +* Updated `DateTime` serializers for Java and Python according to GraphBinaryV4. +* Defined GraphBinaryV4 specification. +* Defined GraphSONV4 specification. +* Update serializers for `label` of an `Element` as a singleton list of string for GraphBinaryV4. +* Added `bulked` byte to `Response Message` serialization for GraphBinaryV4. +* Added a `bulked` header set by cluster setting, as well as a with `bulked` request option to turn on the bulking of result data. +* Updated `List` and `BulkSet` serializers to implement `bulk` value flag for `List`. +* Renamed `maxContentLength` setting for Gremlin Driver to `maxResponseContentLength` and blocked incoming responses that are too large based on total response size. +* Renamed `maxContentLength` setting for Gremlin Server to `maxRequestContentLength`. +* Added missing strategies to the `TraversalStrategies` global cache as well as `CoreImports` in `gremlin-groovy`. +* Added missing strategies to `strategies.py` in `gremlin-python`. +* Updated `OptionsStrategy` in `gremlin-python` to take options directly as keyword arguments. +* Added static `instance()` method to `ElementIdStrategy` to an instance with the default configuration. +* Updated `ElementIdStrategy.getConfiguration()` to help with serialization. +* Updated `TraversalStrategyProxy` to utilize strategy names instead of strategy classes +* Established mechanism for using customer strategies in remote context by using `TraversalStrategyProxy` in Java, or `TraversalStrategy` in GLVs. +* Removed `minSize` setting for Gremlin Driver connection pool since connections are now short-lived HTTP connections +* Added `idleConnectionTimeout` setting for Gremlin Driver and automatic closing of idle connections +* Enabled TCP Keep-Alive in GremlinServer. +* Updated Python GLV examples to use HTTP and to run as part of the integration tests. +* Fixed `gremlin-python` `to()` and `from_()` methods to wrap Vertex objects with `__.V()` for proper edge creation. == TinkerPop 3.8.0 (Grix Greven) diff --cc gremlin-go/docker-compose.yml index c4de751ec0,c499e94d04..733277fecd --- a/gremlin-go/docker-compose.yml +++ b/gremlin-go/docker-compose.yml @@@ -55,6 -58,9 +55,7 @@@ services - RUN_INTEGRATION_TESTS=true - RUN_INTEGRATION_WITH_ALIAS_TESTS=true - RUN_BASIC_AUTH_INTEGRATION_TESTS=true - - GREMLIN_SOCKET_SERVER_URL=ws://gremlin-socket-server-go - - GREMLIN_SOCKET_SERVER_CONFIG_PATH=/go_app/gremlin-socket-server/conf/test-ws-gremlin.yaml + - VERTEX_LABEL=go-example working_dir: /go_app command: > bash -c "go install github.com/gotesttools/gotestfmt/v2/cmd/gotestfmt@latest diff --cc gremlin-go/driver/client.go index ff625f6dff,705330192f..f5646985e7 --- a/gremlin-go/driver/client.go +++ b/gremlin-go/driver/client.go @@@ -127,12 -149,12 +127,12 @@@ func (client *Client) Close() // SubmitWithOptions submits a Gremlin script to the server with specified RequestOptions and returns a ResultSet. func (client *Client) SubmitWithOptions(traversalString string, requestOptions RequestOptions) (ResultSet, error) { client.logHandler.logf(Debug, submitStartedString, traversalString) - request := makeStringRequest(traversalString, client.traversalSource, requestOptions) - request := MakeStringRequest(traversalString, client.traversalSource, client.session, requestOptions) - result, err := client.connections.write(&request) - if err != nil { - client.logHandler.logf(Error, logErrorGeneric, "Client.Submit()", err.Error()) - } - return result, err ++ request := MakeStringRequest(traversalString, client.traversalSource, requestOptions) + + // TODO interceptors (ie. auth) + + rs, err := client.gremlinClient.send(&request) + return rs, err } // Submit submits a Gremlin script to the server and returns a ResultSet. Submit can optionally accept a map of bindings @@@ -149,6 -171,16 +149,6 @@@ func (client *Client) Submit(traversalS // submitBytecode submits Bytecode to the server to execute and returns a ResultSet. func (client *Client) submitBytecode(bytecode *Bytecode) (ResultSet, error) { client.logHandler.logf(Debug, submitStartedBytecode, *bytecode) - request := makeBytecodeRequest(bytecode, client.traversalSource) - request := MakeBytecodeRequest(bytecode, client.traversalSource, client.session) - return client.connections.write(&request) -} - -func (client *Client) closeSession() error { - message := makeCloseSessionRequest(client.session) - result, err := client.connections.write(&message) - if err != nil { - return err - } - _, err = result.All() - return err ++ request := MakeBytecodeRequest(bytecode, client.traversalSource) + return client.gremlinClient.send(&request) } diff --cc gremlin-go/driver/request.go index 79581b959e,436a8a39fd..b5eebe65d3 --- a/gremlin-go/driver/request.go +++ b/gremlin-go/driver/request.go @@@ -19,16 -19,61 +19,36 @@@ under the License package gremlingo -import ( - "github.com/google/uuid" -) - // request represents a request to the server. type request struct { - requestID uuid.UUID - op string - processor string - args map[string]interface{} + gremlin string + fields map[string]interface{} } - func makeStringRequest(stringGremlin string, traversalSource string, requestOptions RequestOptions) (req request) { -const sessionProcessor = "session" - -const stringOp = "eval" -const stringProcessor = "" - + // MakeStringRequest creates a request from a Gremlin string query for submission to a Gremlin server. + // + // This function is exposed publicly to enable alternative transport protocols (gRPC, HTTP/2, etc.) + // to construct properly formatted requests outside the standard WebSocket client. The returned + // request can then be serialized using SerializeMessage(). + // + // Parameters: + // - stringGremlin: The Gremlin query string to execute + // - traversalSource: The name of the traversal source (typically "g") -// - sessionId: Optional session ID for stateful requests (use "" for stateless) + // - requestOptions: Options such as bindings, timeout, batch size, etc. + // + // Returns: + // - request: A request structure ready for serialization + // + // Example for alternative transports: + // -// req := MakeStringRequest("g.V().count()", "g", "", RequestOptions{}) ++// req := MakeStringRequest("g.V().count()", "g", RequestOptions{}) + // serializer := newGraphBinarySerializer(nil) + // bytes, _ := serializer.(graphBinarySerializer).SerializeMessage(&req) + // // Send bytes over gRPC, HTTP/2, etc. -func MakeStringRequest(stringGremlin string, traversalSource string, sessionId string, requestOptions RequestOptions) (req request) { - newProcessor := stringProcessor - newArgs := map[string]interface{}{ - "gremlin": stringGremlin, - "aliases": map[string]interface{}{ - "g": traversalSource, - }, - } - if sessionId != "" { - newProcessor = sessionProcessor - newArgs["session"] = sessionId - } - var requestId uuid.UUID - if requestOptions.requestID == uuid.Nil { - requestId = uuid.New() - } else { - requestId = requestOptions.requestID ++func MakeStringRequest(stringGremlin string, traversalSource string, requestOptions RequestOptions) (req request) { + newFields := map[string]interface{}{ + "language": "gremlin-lang", + "g": traversalSource, } if requestOptions.bindings != nil { @@@ -57,8 -104,35 +77,28 @@@ } } - func makeBytecodeRequest(bytecodeGremlin *Bytecode, traversalSource string) (req request) { -const bytecodeOp = "bytecode" -const bytecodeProcessor = "traversal" -const authOp = "authentication" -const authProcessor = "traversal" - + // MakeBytecodeRequest creates a request from Gremlin bytecode for submission to a Gremlin server. + // + // This function is exposed publicly to enable alternative transport protocols (gRPC, HTTP/2, etc.) + // to construct properly formatted requests outside the standard WebSocket client. The returned + // request can then be serialized using SerializeMessage(). + // + // Parameters: + // - bytecodeGremlin: The Gremlin bytecode to execute + // - traversalSource: The name of the traversal source (typically "g") -// - sessionId: Optional session ID for stateful requests (use "" for stateless) + // + // Returns: + // - request: A request structure ready for serialization + // + // Example for alternative transports: + // + // bytecode := g.V().HasLabel("person").Bytecode -// req := MakeBytecodeRequest(bytecode, "g", "") ++// req := MakeBytecodeRequest(bytecode, "g") + // serializer := newGraphBinarySerializer(nil) + // bytes, _ := serializer.(graphBinarySerializer).SerializeMessage(&req) + // // Send bytes over gRPC, HTTP/2, etc. -func MakeBytecodeRequest(bytecodeGremlin *Bytecode, traversalSource string, sessionId string) (req request) { - newProcessor := bytecodeProcessor - newArgs := map[string]interface{}{ ++func MakeBytecodeRequest(bytecodeGremlin *Bytecode, traversalSource string) (req request) { + newFields := map[string]interface{}{ "gremlin": *bytecodeGremlin, "aliases": map[string]interface{}{ "g": traversalSource, diff --cc gremlin-go/driver/request_test.go index d6d4b03268,e20a5a06de..c88ef32f69 --- a/gremlin-go/driver/request_test.go +++ b/gremlin-go/driver/request_test.go @@@ -26,29 -28,40 +26,29 @@@ import ) func TestRequest(t *testing.T) { - t.Run("Test makeStringRequest() with custom requestID", func(t *testing.T) { - requestId := uuid.New() - r := MakeStringRequest("g.V()", "g", "", - new(RequestOptionsBuilder).SetRequestId(requestId).Create()) - assert.Equal(t, requestId, r.requestID) - }) - t.Run("Test makeStringRequest() with no bindings", func(t *testing.T) { - r := makeStringRequest("g.V()", "g", *new(RequestOptions)) - r := MakeStringRequest("g.V()", "g", "", *new(RequestOptions)) - assert.NotNil(t, r.requestID) - assert.NotEqual(t, uuid.Nil, r.requestID) ++ r := MakeStringRequest("g.V()", "g", *new(RequestOptions)) + assert.Equal(t, "g.V()", r.gremlin) + assert.Equal(t, "g", r.fields["g"]) + assert.Equal(t, "gremlin-lang", r.fields["language"]) + assert.Nil(t, r.fields["bindings"]) }) t.Run("Test makeStringRequest() with custom evaluationTimeout", func(t *testing.T) { - r := makeStringRequest("g.V()", "g", - r := MakeStringRequest("g.V()", "g", "", ++ r := MakeStringRequest("g.V()", "g", new(RequestOptionsBuilder).SetEvaluationTimeout(1234).Create()) - assert.NotNil(t, r.requestID) - assert.NotEqual(t, uuid.Nil, r.requestID) - assert.Equal(t, 1234, r.args["evaluationTimeout"]) + assert.Equal(t, 1234, r.fields["evaluationTimeout"]) }) t.Run("Test makeStringRequest() with custom batchSize", func(t *testing.T) { - r := makeStringRequest("g.V()", "g", - r := MakeStringRequest("g.V()", "g", "", ++ r := MakeStringRequest("g.V()", "g", new(RequestOptionsBuilder).SetBatchSize(123).Create()) - assert.NotNil(t, r.requestID) - assert.NotEqual(t, uuid.Nil, r.requestID) - assert.Equal(t, 123, r.args["batchSize"]) + assert.Equal(t, 123, r.fields["batchSize"]) }) t.Run("Test makeStringRequest() with custom userAgent", func(t *testing.T) { - r := makeStringRequest("g.V()", "g", - r := MakeStringRequest("g.V()", "g", "", ++ r := MakeStringRequest("g.V()", "g", new(RequestOptionsBuilder).SetUserAgent("TestUserAgent").Create()) - assert.NotNil(t, r.requestID) - assert.NotEqual(t, uuid.Nil, r.requestID) - assert.Equal(t, "TestUserAgent", r.args["userAgent"]) + assert.Equal(t, "TestUserAgent", r.fields["userAgent"]) }) } diff --cc gremlin-go/driver/response.go index dfe89ac07f,6eadf351b5..c2a08d550a --- a/gremlin-go/driver/response.go +++ b/gremlin-go/driver/response.go @@@ -21,21 -21,22 +21,21 @@@ package gremling import "github.com/google/uuid" - // responseStatus contains the status info of the response. - type responseStatus struct { + // ResponseStatus contains the status info of the response. + type ResponseStatus struct { - code uint16 - message string - attributes map[string]interface{} + code uint32 + message string + exception string } - // responseResult contains the result info of the response. - type responseResult struct { - data interface{} + // ResponseResult contains the result info of the response. + type ResponseResult struct { - Meta map[string]interface{} + Data interface{} } - // response represents a response from the server. - type response struct { - responseID uuid.UUID - responseStatus responseStatus - responseResult responseResult + // Response represents a Response from the server. + type Response struct { + ResponseID uuid.UUID + ResponseStatus ResponseStatus + ResponseResult ResponseResult } diff --cc gremlin-go/driver/resultSet.go index 8079744bd3,6472880b77..59e22e66d1 --- a/gremlin-go/driver/resultSet.go +++ b/gremlin-go/driver/resultSet.go @@@ -169,10 -204,56 +169,55 @@@ func (channelResultSet *channelResultSe channelResultSet.sendSignal() } -func newChannelResultSetCapacity(requestID string, container *synchronizedMap, channelSize int) ResultSet { - return &channelResultSet{make(chan *Result, channelSize), requestID, container, "", nil, false, nil, nil, sync.Mutex{}, sync.Mutex{}} +func newChannelResultSetCapacity(channelSize int) ResultSet { + return &channelResultSet{make(chan *Result, channelSize), "", false, nil, nil, sync.Mutex{}, sync.Mutex{}} } -func newChannelResultSet(requestID string, container *synchronizedMap) ResultSet { - return newChannelResultSetCapacity(requestID, container, defaultCapacity) +func newChannelResultSet() ResultSet { + return newChannelResultSetCapacity(defaultCapacity) } + + // NewResultSet creates a new ResultSet from a slice of Result objects. + // This function enables custom transport implementations to create ResultSets from + // results collected via alternative protocols. + // + // The function creates a channel-based ResultSet, pre-populates it with the provided results, + // and closes the channel to indicate completion. + // + // Parameters: -// - requestID: The request identifier for this ResultSet + // - results: A slice of Result objects to include in the ResultSet + // + // Returns: + // - ResultSet: A ResultSet containing all the provided results + // + // Example usage: + // + // var results []*Result + // // Collect results from custom transport + // for _, responseBytes := range responses { + // result, _ := DeserializeResult(responseBytes) + // results = append(results, result) + // } + // resultSet := NewResultSet("request-123", results) + // allResults, _ := resultSet.All() -func NewResultSet(requestID string, results []*Result) ResultSet { ++func NewResultSet(results []*Result) ResultSet { + // Create a channel-based result set with capacity for all results + channelSize := len(results) + if channelSize == 0 { + channelSize = 1 // Ensure at least size 1 + } - rs := newChannelResultSetCapacity(requestID, &synchronizedMap{make(map[string]ResultSet), sync.Mutex{}}, channelSize).(*channelResultSet) ++ rs := newChannelResultSetCapacity(channelSize).(*channelResultSet) + + // Add all results to the channel + for _, result := range results { + rs.channel <- result + } + + // Close the channel to indicate no more results + rs.channelMutex.Lock() + rs.closed = true + close(rs.channel) + rs.channelMutex.Unlock() + + return rs + } diff --cc gremlin-go/driver/serializer.go index 7ef810ab53,28f1c0ff07..efd32618f1 --- a/gremlin-go/driver/serializer.go +++ b/gremlin-go/driver/serializer.go @@@ -22,19 -22,24 +22,19 @@@ package gremling import ( "bytes" "encoding/binary" - "math/big" - "reflect" - "strings" "sync" - - "github.com/google/uuid" ) -const graphBinaryMimeType = "application/vnd.graphbinary-v1.0" +const graphBinaryMimeType = "application/vnd.graphbinary-v4.0" - // serializer interface for serializers. - type serializer interface { - serializeMessage(request *request) ([]byte, error) - deserializeMessage(message []byte) (response, error) + // Serializer interface for serializers. + type Serializer interface { + SerializeMessage(request *request) ([]byte, error) + DeserializeMessage(message []byte) (Response, error) } - // graphBinarySerializer serializes/deserializes message to/from GraphBinary. - type graphBinarySerializer struct { + // GraphBinarySerializer serializes/deserializes message to/from GraphBinary. + type GraphBinarySerializer struct { ser *graphBinaryTypeSerializer } @@@ -63,18 -68,73 +63,40 @@@ func newGraphBinarySerializer(handler * const versionByte byte = 0x81 -func convertArgs(request *request, gs GraphBinarySerializer) (map[string]interface{}, error) { - if request.op != bytecodeProcessor { - return request.args, nil - } - - // Convert to format: - // args["gremlin"]: <serialized args["gremlin"]> - gremlin := request.args["gremlin"] - switch gremlin.(type) { - case Bytecode: - buffer := bytes.Buffer{} - gremlinBuffer, err := gs.ser.write(gremlin, &buffer) - if err != nil { - return nil, err - } - request.args["gremlin"] = gremlinBuffer - return request.args, nil - default: - var typeName string - if gremlin != nil { - typeName = reflect.TypeOf(gremlin).Name() - } - - return nil, newError(err0704ConvertArgsNoSerializerError, typeName) - } -} - + // SerializeMessage serializes a request message into GraphBinary format. + // + // This method is part of the serializer interface and is used internally by the WebSocket driver. + // It is also exposed publicly to enable alternative transport protocols (gRPC, HTTP/2, etc.) to + // serialize requests created with MakeBytecodeRequest() or MakeStringRequest(). + // + // The serialized bytes can be transmitted over any transport protocol that supports binary data. + // + // Parameters: + // - request: The request to serialize (created via MakeBytecodeRequest or MakeStringRequest) + // + // Returns: + // - []byte: The GraphBinary-encoded request ready for transmission + // - error: Any serialization error encountered + // + // Example for alternative transports: + // + // req := MakeBytecodeRequest(bytecode, "g", "") + // serializer := newGraphBinarySerializer(nil) + // bytes, err := serializer.(graphBinarySerializer).SerializeMessage(&req) + // // Send bytes over custom transport ++// +// serializeMessage serializes a request message into GraphBinary. - func (gs graphBinarySerializer) serializeMessage(request *request) ([]byte, error) { + func (gs GraphBinarySerializer) SerializeMessage(request *request) ([]byte, error) { - args, err := convertArgs(request, gs) - if err != nil { - return nil, err - } - finalMessage, err := gs.buildMessage(request.requestID, byte(len(graphBinaryMimeType)), request.op, request.processor, args) + finalMessage, err := gs.buildMessage(request.gremlin, request.fields) if err != nil { return nil, err } return finalMessage, nil } - func (gs *graphBinarySerializer) buildMessage(gremlin string, args map[string]interface{}) ([]byte, error) { -func (gs *GraphBinarySerializer) buildMessage(id uuid.UUID, mimeLen byte, op string, processor string, args map[string]interface{}) ([]byte, error) { ++func (gs *GraphBinarySerializer) buildMessage(gremlin string, args map[string]interface{}) ([]byte, error) { buffer := bytes.Buffer{} - // mime header - buffer.WriteByte(mimeLen) - buffer.WriteString(graphBinaryMimeType) - // Version buffer.WriteByte(versionByte) @@@ -90,9 -172,59 +112,27 @@@ return buffer.Bytes(), nil } - // deserializeMessage deserializes a response message. - func (gs graphBinarySerializer) deserializeMessage(message []byte) (response, error) { - var msg response -func uuidToBigInt(requestID uuid.UUID) big.Int { - var bigInt big.Int - bigInt.SetString(strings.Replace(requestID.String(), "-", "", 4), 16) - return bigInt -} - + // DeserializeMessage deserializes a GraphBinary-encoded response message. + // + // This method is part of the serializer interface and is used internally by the WebSocket driver. + // It is also exposed publicly to enable alternative transport protocols (gRPC, HTTP/2, etc.) to + // deserialize responses received from a Gremlin server. + // + // Parameters: + // - message: The GraphBinary-encoded response bytes + // + // Returns: -// - response: The deserialized response containing results and metadata ++// - response: The deserialized Response containing results and metadata + // - error: Any deserialization error encountered + // + // Example for alternative transports: + // + // // Receive bytes from custom transport + // serializer := newGraphBinarySerializer(nil) + // resp, err := serializer.(graphBinarySerializer).DeserializeMessage(responseBytes) + // results := resp.responseResult.data + func (gs GraphBinarySerializer) DeserializeMessage(message []byte) (Response, error) { + var msg Response if message == nil || len(message) == 0 { gs.ser.logHandler.log(Error, nullInput) @@@ -108,30 -245,22 +148,30 @@@ if err != nil { return msg, err } - msg.ResponseStatus.message = message.(string) + results = append(results, n) } - attr, err := readMapUnqualified(&message, &i) - if err != nil { - return msg, err + if len(results) == 1 { + // unwrap single results - msg.responseResult.data = results[0] ++ msg.ResponseResult.Data = results[0] + } else { - msg.responseResult.data = results ++ msg.ResponseResult.Data = results } - msg.ResponseStatus.attributes = attr.(map[string]interface{}) - meta, err := readMapUnqualified(&message, &i) + code := readUint32Safe(&message, &i) - msg.responseStatus.code = code ++ msg.ResponseStatus.code = code + statusMsg, err := readUnqualified(&message, &i, stringType, true) if err != nil { return msg, err } - msg.ResponseResult.Meta = meta.(map[string]interface{}) - msg.ResponseResult.Data, err = readFullyQualifiedNullable(&message, &i, true) + if statusMsg != nil { - msg.responseStatus.message = statusMsg.(string) ++ msg.ResponseStatus.message = statusMsg.(string) + } + exception, err := readUnqualified(&message, &i, stringType, true) if err != nil { return msg, err } + if exception != nil { - msg.responseStatus.exception = exception.(string) ++ msg.ResponseStatus.exception = exception.(string) + } return msg, nil } diff --cc gremlin-go/driver/serializer_test.go index 8bd16032f9,b8689da16b..756f0af15f --- a/gremlin-go/driver/serializer_test.go +++ b/gremlin-go/driver/serializer_test.go @@@ -33,12 -34,15 +33,12 @@@ const mapDataOrder2 = "[32 97 112 112 1 func TestSerializer(t *testing.T) { t.Run("test serialized request message", func(t *testing.T) { - var u, _ = uuid.Parse("41d2e28a-20a4-4ab0-b379-d810dede3786") testRequest := request{ - requestID: u, - op: "eval", - processor: "", - args: map[string]interface{}{"gremlin": "g.V().count()", "aliases": map[string]interface{}{"g": "g"}}, + gremlin: "g.V().count()", - fields: map[string]interface{}{"aliases": map[string]interface{}{"g": "g"}}, ++ fields: map[string]interface{}{"aliases": map[string]interface{}{"g": "g"}}, } serializer := newGraphBinarySerializer(newLogHandler(&defaultLogger{}, Error, language.English)) - serialized, _ := serializer.serializeMessage(&testRequest) + serialized, _ := serializer.SerializeMessage(&testRequest) stringified := fmt.Sprintf("%v", serialized) if stringified != mapDataOrder1 && stringified != mapDataOrder2 { assert.Fail(t, "Error, expected serialized map data to match one of the provided binary arrays. Can vary based on ordering of keyset, but must map to one of two.") @@@ -48,12 -52,14 +48,12 @@@ t.Run("test serialized response message", func(t *testing.T) { responseByteArray := []byte{129, 0, 251, 37, 42, 74, 117, 221, 71, 191, 183, 78, 86, 53, 0, 12, 132, 100, 0, 0, 0, 200, 0, 0, 0, 0, 0, 0, 0, 0, 1, 3, 0, 0, 0, 0, 4, 104, 111, 115, 116, 3, 0, 0, 0, 0, 16, 47, 49, 50, 55, 46, 48, 46, 48, 46, 49, 58, 54, 50, 48, 51, 53, 0, 0, 0, 0, 9, 0, 0, 0, 0, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0} serializer := newGraphBinarySerializer(newLogHandler(&defaultLogger{}, Error, language.English)) - response, err := serializer.deserializeMessage(responseByteArray) + response, err := serializer.DeserializeMessage(responseByteArray) assert.Nil(t, err) - assert.Equal(t, "fb252a4a-75dd-47bf-b74e-5635000c8464", response.responseID.String()) - assert.Equal(t, uint16(200), response.responseStatus.code) - assert.Equal(t, "", response.responseStatus.message) - assert.Equal(t, []interface{}{int64(0)}, response.responseResult.data) + assert.Equal(t, "fb252a4a-75dd-47bf-b74e-5635000c8464", response.ResponseID.String()) + assert.Equal(t, uint16(200), response.ResponseStatus.code) + assert.Equal(t, "", response.ResponseStatus.message) - assert.Equal(t, map[string]interface{}{"host": "/127.0.0.1:62035"}, response.ResponseStatus.attributes) - assert.Equal(t, map[string]interface{}{}, response.ResponseResult.Meta) + assert.Equal(t, []interface{}{int64(0)}, response.ResponseResult.Data) }) t.Run("test serialized response message w/ custom type", func(t *testing.T) { @@@ -63,23 -69,29 +63,23 @@@ }() responseByteArray := []byte{129, 0, 69, 222, 40, 55, 95, 62, 75, 249, 134, 133, 155, 133, 43, 151, 221, 68, 0, 0, 0, 200, 0, 0, 0, 0, 0, 0, 0, 0, 1, 3, 0, 0, 0, 0, 4, 104, 111, 115, 116, 3, 0, 0, 0, 0, 18, 47, 49, 48, 46, 50, 52, 52, 46, 48, 46, 51, 51, 58, 53, 49, 52, 55, 48, 0, 0, 0, 0, 9, 0, 0, 0, 0, 1, 33, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 29, 106, 97, 110, 117, 115, 103, 114, 97, 112, 104, 46, 82, 101, 108, 97, 116, 105, 111, 110, 73, 100, 101, 110, 116, 105, 102, 105, 101, [...] serializer := newGraphBinarySerializer(newLogHandler(&defaultLogger{}, Error, language.English)) - response, err := serializer.deserializeMessage(responseByteArray) + response, err := serializer.DeserializeMessage(responseByteArray) assert.Nil(t, err) - assert.Equal(t, "45de2837-5f3e-4bf9-8685-9b852b97dd44", response.responseID.String()) - assert.Equal(t, uint16(200), response.responseStatus.code) - assert.Equal(t, "", response.responseStatus.message) - assert.NotNil(t, response.responseResult.data) + assert.Equal(t, "45de2837-5f3e-4bf9-8685-9b852b97dd44", response.ResponseID.String()) + assert.Equal(t, uint16(200), response.ResponseStatus.code) + assert.Equal(t, "", response.ResponseStatus.message) - assert.Equal(t, map[string]interface{}{"host": "/10.244.0.33:51470"}, response.ResponseStatus.attributes) - assert.Equal(t, map[string]interface{}{}, response.ResponseResult.Meta) + assert.NotNil(t, response.ResponseResult.Data) }) } func TestSerializerFailures(t *testing.T) { t.Run("test convertArgs failure", func(t *testing.T) { - var u, _ = uuid.Parse("41d2e28a-20a4-4ab0-b379-d810dede3786") testRequest := request{ - requestID: u, - op: "traversal", - processor: "", - // Invalid Input in args, so should fail - args: map[string]interface{}{"invalidInput": "invalidInput", "aliases": map[string]interface{}{"g": "g"}}, + // Invalid Input in fields, so should fail + fields: map[string]interface{}{"invalidInput": "invalidInput", "aliases": map[string]interface{}{"g": "g"}}, } serializer := newGraphBinarySerializer(newLogHandler(&defaultLogger{}, Error, language.English)) - resp, err := serializer.serializeMessage(&testRequest) + resp, err := serializer.SerializeMessage(&testRequest) assert.Nil(t, resp) assert.NotNil(t, err) assert.True(t, isSameErrorCode(newError(err0704ConvertArgsNoSerializerError), err)) diff --cc gremlin-go/examples/basic_gremlin.go index 9586130204,4220b96a4b..628875f4f7 --- a/gremlin-go/examples/basic_gremlin.go +++ b/gremlin-go/examples/basic_gremlin.go @@@ -25,8 -26,18 +26,18 @@@ import "github.com/apache/tinkerpop/gremlin-go/v3/driver" ) -var serverURL = getEnv("GREMLIN_SERVER_URL", "ws://localhost:8182/gremlin") ++var serverURL = getEnv("GREMLIN_SERVER_URL", "http://localhost:8182/gremlin") + var vertexLabel = getEnv("VERTEX_LABEL", "person") + + func getEnv(key, defaultValue string) string { + if value := os.Getenv(key); value != "" { + return value + } + return defaultValue + } + func main() { - driverRemoteConnection, err := gremlingo.NewDriverRemoteConnection("http://localhost:8182/gremlin") + driverRemoteConnection, err := gremlingo.NewDriverRemoteConnection(serverURL) if err != nil { fmt.Println(err) return diff --cc gremlin-go/examples/connections.go index c263644c62,15c4ba1744..b2b434934a --- a/gremlin-go/examples/connections.go +++ b/gremlin-go/examples/connections.go @@@ -21,8 -21,21 +21,21 @@@ package mai import ( "fmt" + "os" + + "github.com/apache/tinkerpop/gremlin-go/v3/driver" ) -var serverURL = getEnv("GREMLIN_SERVER_URL", "ws://localhost:8182/gremlin") ++var serverURL = getEnv("GREMLIN_SERVER_URL", "http://localhost:8182/gremlin") + var vertexLabel = getEnv("VERTEX_LABEL", "connection") + + func getEnv(key, defaultValue string) string { + if value := os.Getenv(key); value != "" { + return value + } + return defaultValue + } + func main() { withRemote() withConfigs() diff --cc gremlin-go/examples/modern_traversals.go index 514196ba21,b722b2812d..053b857fd3 --- a/gremlin-go/examples/modern_traversals.go +++ b/gremlin-go/examples/modern_traversals.go @@@ -28,9 -29,20 +29,20 @@@ import var __ = gremlingo.T__ var T = gremlingo.T var P = gremlingo.P -var serverURL = getEnv("GREMLIN_SERVER_URL", "ws://localhost:8182/gremlin") ++var serverURL = getEnv("GREMLIN_SERVER_URL", "http://localhost:8182/gremlin") + + func getEnv(key, defaultValue string) string { + if value := os.Getenv(key); value != "" { + return value + } + return defaultValue + } func main() { - driverRemoteConnection, err := gremlingo.NewDriverRemoteConnection("http://localhost:8182/gremlin") + driverRemoteConnection, err := gremlingo.NewDriverRemoteConnection(serverURL, + func(settings *gremlingo.DriverRemoteConnectionSettings) { + settings.TraversalSource = "gmodern" + }) if err != nil { fmt.Println(err) return
