You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tinkerpop.apache.org by xi...@apache.org on 2026/01/27 00:30:28 UTC
(tinkerpop) 01/01: Added streaming deserializer, combined httpProtocol/httpTransport, added interceptors which replaces AuthInfo, added basic & sigv4 auth interceptors, updated connection settings
This is an automated email from the ASF dual-hosted git repository.
xiazcy pushed a commit to branch go-http-streaming
in repository https://gitbox.apache.org/repos/asf/tinkerpop.git
commit c1f1279dd77d4ba7a1860edbaab109b08596764f
Author: Yang Xia <55...@users.noreply.github.com>
AuthorDate: Mon Jan 27 18:28:00 2025 -0800
Added streaming deserializer, combined httpProtocol/httpTransport, added interceptors which replaces AuthInfo, added basic & sigv4 auth interceptors, updated connection settings
---
.github/workflows/build-test.yml | 62 +-
CHANGELOG.asciidoc | 1 +
.../language/translator/GoTranslateVisitor.java | 4 +-
gremlin-go/build/generate.groovy | 3 -
gremlin-go/docker-compose.yml | 13 +-
gremlin-go/driver/anonymousTraversal.go | 2 +-
gremlin-go/driver/auth.go | 104 +++
gremlin-go/driver/authInfo.go | 95 ---
gremlin-go/driver/auth_test.go | 114 ++++
gremlin-go/driver/bytecode.go | 182 ------
gremlin-go/driver/bytecode_test.go | 159 -----
gremlin-go/driver/client.go | 101 ++-
gremlin-go/driver/client_test.go | 22 +-
gremlin-go/driver/connection.go | 5 -
gremlin-go/driver/connection_test.go | 337 +++++-----
gremlin-go/driver/cucumber/cucumberSteps_test.go | 5 +-
gremlin-go/driver/cucumber/cucumberWorld.go | 66 +-
gremlin-go/driver/cucumber/gremlin.go | 3 -
gremlin-go/driver/driverRemoteConnection.go | 51 +-
gremlin-go/driver/driverRemoteConnection_test.go | 22 +-
gremlin-go/driver/error_codes.go | 8 +-
gremlin-go/driver/gValue.go | 55 ++
gremlin-go/driver/gValue_test.go | 87 +++
gremlin-go/driver/graphBinary.go | 603 ++++-------------
gremlin-go/driver/graphBinary_test.go | 52 +-
gremlin-go/driver/graphTraversal.go | 293 ++++-----
gremlin-go/driver/graphTraversalSource.go | 68 +-
gremlin-go/driver/graphTraversalSource_test.go | 30 +-
gremlin-go/driver/gremlinClient.go | 148 -----
gremlin-go/driver/gremlinlang.go | 569 ++++++++++++++++
.../{translator_test.go => gremlinlang_test.go} | 257 ++++----
gremlin-go/driver/httpConnection.go | 277 ++++++++
gremlin-go/driver/httpConnection_test.go | 178 +++++
gremlin-go/driver/httpTransporter.go | 171 -----
gremlin-go/driver/logger.go | 7 +-
gremlin-go/driver/{transporter.go => marker.go} | 38 +-
gremlin-go/driver/performance/performanceSuite.go | 3 -
gremlin-go/driver/request.go | 106 ---
gremlin-go/driver/resources/error-messages/en.json | 1 +
.../driver/resources/logger-messages/en.json | 7 +-
gremlin-go/driver/response.go | 3 -
gremlin-go/driver/resultSet.go | 4 +-
gremlin-go/driver/serializer.go | 189 ++++--
gremlin-go/driver/serializer_test.go | 75 +--
gremlin-go/driver/strategies.go | 127 ++--
gremlin-go/driver/strategies_test.go | 97 +--
gremlin-go/driver/streamingDeserializer.go | 617 ++++++++++++++++++
gremlin-go/driver/streamingDeserializer_test.go | 402 ++++++++++++
gremlin-go/driver/translator.go | 365 -----------
gremlin-go/driver/traversal.go | 95 ++-
gremlin-go/driver/traversal_test.go | 723 ++++++++++-----------
gremlin-go/examples/go.mod | 4 +-
gremlin-go/examples/go.sum | 2 +
gremlin-go/go.mod | 30 +-
gremlin-go/go.sum | 61 +-
gremlin-go/pom.xml | 7 +-
.../python/gremlin_python/process/traversal.py | 2 +-
57 files changed, 3979 insertions(+), 3133 deletions(-)
diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml
index fbbe3e2f15..9e933ee9a9 100644
--- a/.github/workflows/build-test.yml
+++ b/.github/workflows/build-test.yml
@@ -354,40 +354,28 @@ jobs:
run: |
mvn clean install -pl $EXCLUDE_MODULES -q -DskipTests -Dci
mvn verify -pl :neo4j-gremlin -DincludeNeo4j
-# go:
-# name: go
-# timeout-minutes: 20
-# needs: cache-gremlin-server-docker-image
-# runs-on: ubuntu-latest
-# steps:
-# - name: Checkout
-# uses: actions/checkout@v5
-# - name: Setup Go
-# uses: actions/setup-go@v5
-# with:
-# go-version: '1.25'
-# - name: Get Cached Server Base Image
-# uses: actions/cache@v4
-# id: gremlin-server-test-docker-image
-# with:
-# path: |
-# ./gremlin-server/*
-# ~/.m2/repository/org/apache/tinkerpop/*
-# key: ${{ github.sha }}
-# - name: Load Docker Image
-# working-directory: ./gremlin-server
-# run: docker load --input gremlin-server.tar
-# - name: Build with Maven
-# working-directory: .
-# run: |
-# touch gremlin-go/.glv
-# EXCLUDE="${EXCLUDE_MODULES/,-:gremlin-python},$EXCLUDE_FOR_GLV"
-# mvn clean install -pl $EXCLUDE -q -DskipTests -Dci
-# mvn verify -pl :gremlin-go
-# - name: Upload to Codecov
-# uses: codecov/codecov-action@v5
-# with:
-# working-directory: ./gremlin-go
-# - name: Go-Vet
-# working-directory: ./gremlin-go
-# run: go vet ./...
+ go:
+ name: go
+ timeout-minutes: 20
+ needs: cache-gremlin-server-docker-image
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v5
+ - name: Setup Go
+ uses: actions/setup-go@v5
+ with:
+ go-version: '1.25'
+ - name: Build with Maven
+ working-directory: .
+ run: |
+ touch gremlin-go/.glv
+ EXCLUDE="${EXCLUDE_MODULES/,-:gremlin-python},$EXCLUDE_FOR_GLV"
+ mvn clean install -pl $EXCLUDE -q -DskipTests -Dci
+ mvn verify -pl :gremlin-go
+ - name: Upload to Codecov
+ uses: codecov/codecov-action@v5
+ with:
+ working-directory: ./gremlin-go
+ - name: Go-Vet
+ working-directory: ./gremlin-go
+ run: go vet ./...
diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc
index 151445b057..55ad51af72 100644
--- a/CHANGELOG.asciidoc
+++ b/CHANGELOG.asciidoc
@@ -27,6 +27,7 @@ image::https://raw.githubusercontent.com/apache/tinkerpop/master/docs/static/ima
* 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`
+* Replace `Bytecode` with `GremlinLang` & update serialization to GraphBinary 4 for `gremlin-go`
[[release-4-0-0-beta-1]]
=== TinkerPop 4.0.0-beta.1 (January 17, 2025)
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/translator/GoTranslateVisitor.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/translator/GoTranslateVisitor.java
index 69f368b935..0cd864b751 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/translator/GoTranslateVisitor.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/translator/GoTranslateVisitor.java
@@ -23,6 +23,7 @@ import org.antlr.v4.runtime.tree.ParseTree;
import org.antlr.v4.runtime.tree.TerminalNode;
import org.apache.commons.lang3.StringUtils;
import org.apache.tinkerpop.gremlin.language.grammar.GremlinParser;
+import org.apache.tinkerpop.gremlin.process.traversal.TraversalStrategy;
import org.apache.tinkerpop.gremlin.process.traversal.strategy.decoration.OptionsStrategy;
import org.apache.tinkerpop.gremlin.structure.VertexProperty;
import org.apache.tinkerpop.gremlin.util.DatetimeHelper;
@@ -41,7 +42,8 @@ public class GoTranslateVisitor extends AbstractTranslateVisitor {
private final static List<String> STRATEGY_WITH_MAP_OPTS = Collections.unmodifiableList(Arrays.asList(
"OptionsStrategy",
"ReferenceElementStrategy", "ComputerFinalizationStrategy", "ProfileStrategy",
- "ComputerVerificationStrategy", "StandardVerificationStrategy", "VertexProgramRestrictionStrategy"));
+ "ComputerVerificationStrategy", "StandardVerificationStrategy", "VertexProgramRestrictionStrategy",
+ "MessagePassingReductionStrategy"));
private final static List<String> STRATEGY_WITH_STRING_SLICE = Collections.unmodifiableList(Arrays.asList(
"ReservedKeysVerificationStrategy", "ProductiveByStrategy"));
diff --git a/gremlin-go/build/generate.groovy b/gremlin-go/build/generate.groovy
index adade62ee6..df47f27c56 100644
--- a/gremlin-go/build/generate.groovy
+++ b/gremlin-go/build/generate.groovy
@@ -121,9 +121,6 @@ radishGremlinFile.withWriter('UTF-8') { Writer writer ->
' traversalFn := traversalFns[0]\n' +
' translationMap[scenarioName] = traversalFns[1:]\n' +
' traversal := traversalFn(g, parameters)\n' +
- ' for key, value := range sideEffects {\n' +
- ' traversal.Bytecode.AddSource("withSideEffect", key, value)\n' +
- ' }\n' +
' return traversal, nil\n' +
' } else {\n' +
' return nil, errors.New("scenario for traversal not recognized")\n' +
diff --git a/gremlin-go/docker-compose.yml b/gremlin-go/docker-compose.yml
index 733277fecd..fd26f534c4 100644
--- a/gremlin-go/docker-compose.yml
+++ b/gremlin-go/docker-compose.yml
@@ -59,12 +59,13 @@ services:
working_dir: /go_app
command: >
bash -c "go install github.com/gotesttools/gotestfmt/v2/cmd/gotestfmt@latest
- && go test -v -json ./... -race -covermode=atomic -coverprofile=\"coverage.out\" -coverpkg=./... | gotestfmt
- && echo 'Running examples...'
- && go run examples/basic_gremlin.go
- && go run examples/connections.go
- && go run examples/modern_traversals.go
- && echo 'All examples completed successfully'"
+ && go test -v -json ./... -race -covermode=atomic -coverprofile=\"coverage.out\" -coverpkg=./... | gotestfmt"
+# TODO uncomment once all examples are properly updated to go HTTP
+# && echo 'Running examples...'
+# && go run examples/basic_gremlin.go
+# && go run examples/connections.go
+# && go run examples/modern_traversals.go
+# && echo 'All examples completed successfully'"
depends_on:
gremlin-server-test:
condition: service_healthy
diff --git a/gremlin-go/driver/anonymousTraversal.go b/gremlin-go/driver/anonymousTraversal.go
index 1e049b3dff..45166b8cfc 100644
--- a/gremlin-go/driver/anonymousTraversal.go
+++ b/gremlin-go/driver/anonymousTraversal.go
@@ -337,7 +337,7 @@ type anonymousTraversal struct {
var T__ AnonymousTraversal = &anonymousTraversal{
func() *GraphTraversal {
- return NewGraphTraversal(nil, NewBytecode(nil), nil)
+ return NewGraphTraversal(nil, NewGremlinLang(nil), nil)
},
}
diff --git a/gremlin-go/driver/auth.go b/gremlin-go/driver/auth.go
new file mode 100644
index 0000000000..70cff17a6c
--- /dev/null
+++ b/gremlin-go/driver/auth.go
@@ -0,0 +1,104 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements. See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership. The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License. You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied. See the License for the
+specific language governing permissions and limitations
+under the License.
+*/
+
+package gremlingo
+
+import (
+ "context"
+ "encoding/base64"
+ "sync"
+ "time"
+
+ "github.com/aws/aws-sdk-go-v2/aws"
+ v4 "github.com/aws/aws-sdk-go-v2/aws/signer/v4"
+ "github.com/aws/aws-sdk-go-v2/config"
+)
+
+// BasicAuth returns a RequestInterceptor that adds Basic authentication header.
+func BasicAuth(username, password string) RequestInterceptor {
+ encoded := base64.StdEncoding.EncodeToString([]byte(username + ":" + password))
+ return func(req *HttpRequest) error {
+ req.Headers.Set(HeaderAuthorization, "Basic "+encoded)
+ return nil
+ }
+}
+
+// Sigv4Auth returns a RequestInterceptor that signs requests using AWS SigV4.
+// It uses the default AWS credential chain (env vars, shared config, IAM role, etc.)
+func Sigv4Auth(region, service string) RequestInterceptor {
+ return Sigv4AuthWithCredentials(region, service, nil)
+}
+
+// Sigv4AuthWithCredentials returns a RequestInterceptor that signs requests using AWS SigV4
+// with the provided credentials provider. If provider is nil, uses default credential chain.
+//
+// Caches the signer and credentials provider for efficiency.
+func Sigv4AuthWithCredentials(region, service string, credentialsProvider aws.CredentialsProvider) RequestInterceptor {
+ // Create signer once - it's stateless and safe to reuse
+ signer := v4.NewSigner()
+
+ // Cache for resolved credentials provider (lazy initialization)
+ var cachedProvider aws.CredentialsProvider
+ var providerOnce sync.Once
+ var providerErr error
+
+ return func(req *HttpRequest) error {
+ ctx := context.Background()
+
+ // Resolve credentials provider once if not provided
+ provider := credentialsProvider
+ if provider == nil {
+ providerOnce.Do(func() {
+ cfg, err := config.LoadDefaultConfig(ctx, config.WithRegion(region))
+ if err != nil {
+ providerErr = err
+ return
+ }
+ cachedProvider = cfg.Credentials
+ })
+ if providerErr != nil {
+ return providerErr
+ }
+ provider = cachedProvider
+ }
+
+ // Retrieve credentials (the provider handles caching internally)
+ creds, err := provider.Retrieve(ctx)
+ if err != nil {
+ return err
+ }
+
+ stdReq, err := req.ToStdRequest()
+ if err != nil {
+ return err
+ }
+ stdReq.Body = nil // Body is handled separately via payload hash
+
+ if err := signer.SignHTTP(ctx, creds, stdReq, req.PayloadHash(), service, region, time.Now()); err != nil {
+ return err
+ }
+
+ // Copy signed headers back to HttpRequest
+ for k, v := range stdReq.Header {
+ req.Headers[k] = v
+ }
+
+ return nil
+ }
+}
diff --git a/gremlin-go/driver/authInfo.go b/gremlin-go/driver/authInfo.go
deleted file mode 100644
index 385cb63782..0000000000
--- a/gremlin-go/driver/authInfo.go
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
-Licensed to the Apache Software Foundation (ASF) under one
-or more contributor license agreements. See the NOTICE file
-distributed with this work for additional information
-regarding copyright ownership. The ASF licenses this file
-to you under the Apache License, Version 2.0 (the
-"License"); you may not use this file except in compliance
-with the License. You may obtain a copy of the License at
-
-http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing,
-software distributed under the License is distributed on an
-"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-KIND, either express or implied. See the License for the
-specific language governing permissions and limitations
-under the License.
-*/
-
-package gremlingo
-
-import "net/http"
-
-// AuthInfoProvider is an interface that allows authentication information to be specified.
-type AuthInfoProvider interface {
- GetHeader() http.Header
- GetBasicAuth() (ok bool, username, password string)
-}
-
-// AuthInfo is an option struct that allows authentication information to be specified statically.
-// Authentication can be provided via http.Header directly.
-// Basic authentication can also be used via the BasicAuthInfo function.
-type AuthInfo struct {
- Header http.Header
- Username string
- Password string
-}
-
-var _ AuthInfoProvider = (*AuthInfo)(nil)
-
-// GetHeader provides a safe way to get a header from the AuthInfo even if it is nil.
-// This way we don't need any additional logic in the transport layer.
-func (authInfo *AuthInfo) GetHeader() http.Header {
- if authInfo == nil {
- return nil
- } else {
- return authInfo.Header
- }
-}
-
-// GetBasicAuth provides a safe way to check if basic auth info is available from the AuthInfo even if it is nil.
-// This way we don't need any additional logic in the transport layer.
-func (authInfo *AuthInfo) GetBasicAuth() (bool, string, string) {
- if authInfo == nil || (authInfo.Username == "" && authInfo.Password == "") {
- return false, "", ""
- }
- return true, authInfo.Username, authInfo.Password
-}
-
-// BasicAuthInfo provides a way to generate AuthInfo. Enter username and password and get the AuthInfo back.
-func BasicAuthInfo(username string, password string) *AuthInfo {
- return &AuthInfo{Username: username, Password: password}
-}
-
-// HeaderAuthInfo provides a way to generate AuthInfo with only Header information.
-func HeaderAuthInfo(header http.Header) *AuthInfo {
- return &AuthInfo{Header: header}
-}
-
-// DynamicAuth is an AuthInfoProvider that allows dynamic credential generation.
-type DynamicAuth struct {
- fn func() AuthInfoProvider
-}
-
-var (
- _ AuthInfoProvider = (*DynamicAuth)(nil)
-
- // NoopAuthInfo is a no-op AuthInfoProvider that can be used to disable authentication.
- NoopAuthInfo = NewDynamicAuth(func() AuthInfoProvider { return &AuthInfo{} })
-)
-
-// NewDynamicAuth provides a way to generate dynamic credentials with the specified generator function.
-func NewDynamicAuth(f func() AuthInfoProvider) *DynamicAuth {
- return &DynamicAuth{fn: f}
-}
-
-// GetHeader calls the stored function to get the header dynamically.
-func (d *DynamicAuth) GetHeader() http.Header {
- return d.fn().GetHeader()
-}
-
-// GetHeader calls the stored function to get basic authentication dynamically.
-func (d *DynamicAuth) GetBasicAuth() (bool, string, string) {
- return d.fn().GetBasicAuth()
-}
diff --git a/gremlin-go/driver/auth_test.go b/gremlin-go/driver/auth_test.go
new file mode 100644
index 0000000000..c0cce3ac9a
--- /dev/null
+++ b/gremlin-go/driver/auth_test.go
@@ -0,0 +1,114 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements. See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership. The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License. You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied. See the License for the
+specific language governing permissions and limitations
+under the License.
+*/
+
+package gremlingo
+
+import (
+ "context"
+ "encoding/base64"
+ "strings"
+ "testing"
+
+ "github.com/aws/aws-sdk-go-v2/aws"
+ "github.com/stretchr/testify/assert"
+)
+
+func createMockRequest() *HttpRequest {
+ req, _ := NewHttpRequest("POST", "https://localhost:8182/gremlin")
+ req.Headers.Set("Content-Type", graphBinaryMimeType)
+ req.Headers.Set("Accept", graphBinaryMimeType)
+ req.Body = []byte(`{"gremlin":"g.V()"}`)
+ return req
+}
+
+func TestBasicAuth(t *testing.T) {
+ t.Run("adds authorization header", func(t *testing.T) {
+ req := createMockRequest()
+ assert.Empty(t, req.Headers.Get(HeaderAuthorization))
+
+ interceptor := BasicAuth("username", "password")
+ err := interceptor(req)
+
+ assert.NoError(t, err)
+ authHeader := req.Headers.Get(HeaderAuthorization)
+ assert.True(t, strings.HasPrefix(authHeader, "Basic "))
+
+ // Verify encoding
+ encoded := strings.TrimPrefix(authHeader, "Basic ")
+ decoded, err := base64.StdEncoding.DecodeString(encoded)
+ assert.NoError(t, err)
+ assert.Equal(t, "username:password", string(decoded))
+ })
+}
+
+// mockCredentialsProvider implements aws.CredentialsProvider for testing
+type mockCredentialsProvider struct {
+ accessKey string
+ secretKey string
+ sessionToken string
+}
+
+func (m *mockCredentialsProvider) Retrieve(ctx context.Context) (aws.Credentials, error) {
+ return aws.Credentials{
+ AccessKeyID: m.accessKey,
+ SecretAccessKey: m.secretKey,
+ SessionToken: m.sessionToken,
+ }, nil
+}
+
+func TestSigv4Auth(t *testing.T) {
+ t.Run("adds signed headers", func(t *testing.T) {
+ req := createMockRequest()
+ assert.Empty(t, req.Headers.Get("Authorization"))
+ assert.Empty(t, req.Headers.Get("X-Amz-Date"))
+
+ provider := &mockCredentialsProvider{
+ accessKey: "MOCK_ACCESS_KEY",
+ secretKey: "MOCK_SECRET_KEY",
+ }
+ interceptor := Sigv4AuthWithCredentials("us-west-2", "neptune-db", provider)
+ err := interceptor(req)
+
+ assert.NoError(t, err)
+ assert.NotEmpty(t, req.Headers.Get("X-Amz-Date"))
+ authHeader := req.Headers.Get("Authorization")
+ assert.True(t, strings.HasPrefix(authHeader, "AWS4-HMAC-SHA256 Credential=MOCK_ACCESS_KEY"))
+ assert.Contains(t, authHeader, "us-west-2/neptune-db/aws4_request")
+ assert.Contains(t, authHeader, "Signature=")
+ })
+
+ t.Run("adds session token when provided", func(t *testing.T) {
+ req := createMockRequest()
+ assert.Empty(t, req.Headers.Get("X-Amz-Security-Token"))
+
+ provider := &mockCredentialsProvider{
+ accessKey: "MOCK_ACCESS_KEY",
+ secretKey: "MOCK_SECRET_KEY",
+ sessionToken: "MOCK_SESSION_TOKEN",
+ }
+ interceptor := Sigv4AuthWithCredentials("us-west-2", "neptune-db", provider)
+ err := interceptor(req)
+
+ assert.NoError(t, err)
+ assert.Equal(t, "MOCK_SESSION_TOKEN", req.Headers.Get("X-Amz-Security-Token"))
+ authHeader := req.Headers.Get("Authorization")
+ assert.True(t, strings.HasPrefix(authHeader, "AWS4-HMAC-SHA256 Credential="))
+ assert.Contains(t, authHeader, "Signature=")
+ })
+}
diff --git a/gremlin-go/driver/bytecode.go b/gremlin-go/driver/bytecode.go
deleted file mode 100644
index eb912befd8..0000000000
--- a/gremlin-go/driver/bytecode.go
+++ /dev/null
@@ -1,182 +0,0 @@
-/*
-Licensed to the Apache Software Foundation (ASF) under one
-or more contributor license agreements. See the NOTICE file
-distributed with this work for additional information
-regarding copyright ownership. The ASF licenses this file
-to you under the Apache License, Version 2.0 (the
-"License"); you may not use this file except in compliance
-with the License. You may obtain a copy of the License at
-
-http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing,
-software distributed under the License is distributed on an
-"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-KIND, either express or implied. See the License for the
-specific language governing permissions and limitations
-under the License.
-*/
-
-package gremlingo
-
-import (
- "fmt"
- "reflect"
-)
-
-// Bytecode a list of ordered instructions for traversal that can be serialized between environments and machines.
-type Bytecode struct {
- sourceInstructions []instruction
- stepInstructions []instruction
- bindings map[string]interface{}
-}
-
-// NewBytecode creates a new Bytecode to be used in traversals.
-func NewBytecode(bc *Bytecode) *Bytecode {
- sourceInstructions := make([]instruction, 0)
- stepInstructions := make([]instruction, 0)
- bindingMap := make(map[string]interface{})
- if bc != nil {
- sourceInstructions = append(sourceInstructions, bc.sourceInstructions...)
- stepInstructions = append(stepInstructions, bc.stepInstructions...)
- }
-
- return &Bytecode{
- sourceInstructions: sourceInstructions,
- stepInstructions: stepInstructions,
- bindings: bindingMap,
- }
-}
-
-func (bytecode *Bytecode) createInstruction(operator string, args ...interface{}) (*instruction, error) {
- instruction := &instruction{
- operator: operator,
- arguments: make([]interface{}, 0),
- }
-
- for _, arg := range args {
- converted, err := bytecode.convertArgument(arg)
- if err != nil {
- return nil, err
- }
- instruction.arguments = append(instruction.arguments, converted)
- }
-
- return instruction, nil
-}
-
-// AddSource add a traversal source instruction to the bytecode.
-func (bytecode *Bytecode) AddSource(sourceName string, args ...interface{}) error {
- instruction, err := bytecode.createInstruction(sourceName, args...)
- if err != nil {
- return err
- }
-
- bytecode.sourceInstructions = append(bytecode.sourceInstructions, *instruction)
- return err
-}
-
-// AddStep adds a traversal instruction to the bytecode
-func (bytecode *Bytecode) AddStep(stepName string, args ...interface{}) error {
- instruction, err := bytecode.createInstruction(stepName, args...)
- if err != nil {
- return err
- }
-
- bytecode.stepInstructions = append(bytecode.stepInstructions, *instruction)
- return err
-}
-
-func (bytecode *Bytecode) convertArgument(arg interface{}) (interface{}, error) {
- if arg == nil {
- return nil, nil
- }
- t := reflect.TypeOf(arg)
- if t == nil {
- return nil, nil
- }
- switch t.Kind() {
- case reflect.Map:
- newMap := make(map[interface{}]interface{})
- iter := reflect.ValueOf(arg).MapRange()
- for iter.Next() {
- k := iter.Key().Interface()
- v := iter.Value().Interface()
- convertedKey, err := bytecode.convertArgument(k)
- if err != nil {
- return nil, err
- }
- convertedValue, err := bytecode.convertArgument(v)
- if err != nil {
- return nil, err
- }
- newMap[convertedKey] = convertedValue
- }
- return newMap, nil
- case reflect.Slice:
- newSlice := make([]interface{}, 0)
- oldSlice := reflect.ValueOf(arg)
- for i := 0; i < oldSlice.Len(); i++ {
- converted, err := bytecode.convertArgument(oldSlice.Index(i).Interface())
- if err != nil {
- return nil, err
- }
- newSlice = append(newSlice, converted)
- }
- return newSlice, nil
- default:
- switch v := arg.(type) {
- case *Binding:
- convertedValue, err := bytecode.convertArgument(v.Value)
- if err != nil {
- return nil, err
- }
- bytecode.bindings[v.Key] = v.Value
- return &Binding{
- Key: v.Key,
- Value: convertedValue,
- }, nil
- case *GraphTraversal:
- if v.graph != nil {
- return nil, newError(err1001ConvertArgumentChildTraversalNotFromAnonError)
- }
- for k, val := range v.Bytecode.bindings {
- bytecode.bindings[k] = val
- }
- return v.Bytecode, nil
- default:
- return arg, nil
- }
- }
-
-}
-
-type instruction struct {
- operator string
- arguments []interface{}
-}
-
-// Binding associates a string variable with a value
-type Binding struct {
- Key string
- Value interface{}
-}
-
-// String returns the key value binding in string format
-func (b *Binding) String() string {
- return fmt.Sprintf("binding[%v=%v]", b.Key, b.Value)
-}
-
-// Bindings are used to associate a variable with a value. They enable the creation of Binding, usually used with
-// Lambda scripts to avoid continued recompilation costs. Bindings allow a remote engine to cache traversals that
-// will be reused over and over again save that some parameterization may change.
-// Used as g.V().Out(&Bindings{}.Of("key", value))
-type Bindings struct{}
-
-// Of creates a Binding
-func (*Bindings) Of(key string, value interface{}) *Binding {
- return &Binding{
- Key: key,
- Value: value,
- }
-}
diff --git a/gremlin-go/driver/bytecode_test.go b/gremlin-go/driver/bytecode_test.go
deleted file mode 100644
index 902620b554..0000000000
--- a/gremlin-go/driver/bytecode_test.go
+++ /dev/null
@@ -1,159 +0,0 @@
-/*
-Licensed to the Apache Software Foundation (ASF) under one
-or more contributor license agreements. See the NOTICE file
-distributed with this work for additional information
-regarding copyright ownership. The ASF licenses this file
-to you under the Apache License, Version 2.0 (the
-"License"); you may not use this file except in compliance
-with the License. You may obtain a copy of the License at
-
-http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing,
-software distributed under the License is distributed on an
-"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-KIND, either express or implied. See the License for the
-specific language governing permissions and limitations
-under the License.
-*/
-
-package gremlingo
-
-import (
- "testing"
-
- "github.com/stretchr/testify/assert"
-)
-
-func TestBytecode(t *testing.T) {
- t.Run("Constructor", func(t *testing.T) {
- bc1 := NewBytecode(nil)
- assert.NotNil(t, bc1.bindings)
- assert.NotNil(t, bc1.sourceInstructions)
- assert.NotNil(t, bc1.stepInstructions)
- assert.Empty(t, bc1.bindings)
- assert.Empty(t, bc1.sourceInstructions)
- assert.Empty(t, bc1.stepInstructions)
-
- sourceInstructions := []instruction{{
- operator: "mockSource",
- arguments: nil,
- }}
- stepInstructions := []instruction{{
- operator: "mockStep",
- arguments: nil,
- }}
- bindingMap := make(map[string]interface{})
- bindingMap["mock"] = 123
- bc1.sourceInstructions = sourceInstructions
- bc1.stepInstructions = stepInstructions
- bc1.bindings = bindingMap
-
- bc2 := NewBytecode(bc1)
- assert.NotNil(t, bc2.bindings)
- assert.NotNil(t, bc2.sourceInstructions)
- assert.NotNil(t, bc2.stepInstructions)
- assert.Empty(t, bc2.bindings)
- assert.Equal(t, sourceInstructions, bc2.sourceInstructions)
- assert.Equal(t, stepInstructions, bc2.stepInstructions)
- })
-
- t.Run("AddSource", func(t *testing.T) {
- expectedSourceInstructions := []instruction{{
- operator: "mockSource",
- arguments: []interface{}{123},
- }}
- bc := NewBytecode(nil)
- err := bc.AddSource("mockSource", 123)
- assert.Nil(t, err)
- assert.Equal(t, expectedSourceInstructions, bc.sourceInstructions)
- })
-
- t.Run("addStep", func(t *testing.T) {
- expectedStepInstructions := []instruction{{
- operator: "mockStep",
- arguments: []interface{}{123},
- }}
- bc := NewBytecode(nil)
- err := bc.AddStep("mockStep", 123)
- assert.Nil(t, err)
- assert.Equal(t, expectedStepInstructions, bc.stepInstructions)
- })
-
- t.Run("convertArgument", func(t *testing.T) {
- bc := NewBytecode(nil)
-
- t.Run("map", func(t *testing.T) {
- testMap := make(map[string]int)
- testMap["test"] = 123
- converted, err := bc.convertArgument(testMap)
- assert.Nil(t, err)
- for k, v := range converted.(map[interface{}]interface{}) {
- key := k.(string)
- value := v.(int)
- assert.Equal(t, "test", key)
- assert.Equal(t, 123, value)
- }
- })
-
- t.Run("slice", func(t *testing.T) {
- testSlice := []int{1, 2, 3}
- converted, err := bc.convertArgument(testSlice)
- assert.Nil(t, err)
- for i, value := range converted.([]interface{}) {
- assert.Equal(t, testSlice[i], value)
- }
- })
-
- t.Run("binding", func(t *testing.T) {
- testKey := "testKey"
- testValue := "testValue"
- testBinding := &Binding{
- Key: testKey,
- Value: testValue,
- }
- converted, err := bc.convertArgument(testBinding)
- assert.Nil(t, err)
- assert.Equal(t, testBinding, converted)
- assert.Equal(t, testValue, bc.bindings[testKey])
- })
- })
-
- t.Run("Test Bytecode traversal argument conversion without Graph", func(t *testing.T) {
- bc := Bytecode{}
- traversal := &GraphTraversal{
- &Traversal{},
- }
- traversal.graph = nil
- traversal.Bytecode = &Bytecode{}
- traversalBytecode, err := bc.convertArgument(traversal)
- assert.Nil(t, err)
- assert.Equal(t, traversal.Bytecode, traversalBytecode)
- })
-
- t.Run("Test Bytecode traversal argument conversion with Graph", func(t *testing.T) {
- // This should fail.
- bc := Bytecode{}
- traversal := &GraphTraversal{
- &Traversal{},
- }
- traversal.graph = &Graph{}
- traversal.Bytecode = &Bytecode{}
- traversalBytecode, err := bc.convertArgument(traversal)
- assert.Equal(t, newError(err1001ConvertArgumentChildTraversalNotFromAnonError), err)
- assert.Nil(t, traversalBytecode)
- })
-
- t.Run("Test Bytecode traversal argument multiple bindings", func(t *testing.T) {
- bc := Bytecode{}
- bc.bindings = map[string]interface{}{}
- testTraversal := &GraphTraversal{
- &Traversal{},
- }
- testTraversal.Bytecode = &Bytecode{}
- testTraversal.Bytecode.bindings = map[string]interface{}{"mock": "123"}
- traversalBytecode, err := bc.convertArgument(testTraversal)
- assert.Nil(t, err)
- assert.Equal(t, testTraversal.Bytecode, traversalBytecode)
- })
-}
diff --git a/gremlin-go/driver/client.go b/gremlin-go/driver/client.go
index f5646985e7..e98f3321e1 100644
--- a/gremlin-go/driver/client.go
+++ b/gremlin-go/driver/client.go
@@ -21,40 +21,37 @@ package gremlingo
import (
"crypto/tls"
+ "reflect"
"runtime"
"time"
"golang.org/x/text/language"
)
-const keepAliveIntervalDefault = 5 * time.Second
-const writeDeadlineDefault = 3 * time.Second
const connectionTimeoutDefault = 5 * time.Second
-// ReadBufferSize and WriteBufferSize specify I/O buffer sizes in bytes. The default is 1MB.
-// If a buffer size is set zero, then the transporter default size is used. The I/O buffer
-// sizes do not limit the size of the messages that can be sent or received.
-const readBufferSizeDefault = 1048576
-const writeBufferSizeDefault = 1048576
-
// ClientSettings is used to modify a Client's settings on initialization.
type ClientSettings struct {
TraversalSource string
LogVerbosity LogVerbosity
Logger Logger
Language language.Tag
- AuthInfo AuthInfoProvider
TlsConfig *tls.Config
- KeepAliveInterval time.Duration
- WriteDeadline time.Duration
ConnectionTimeout time.Duration
EnableCompression bool
- ReadBufferSize int
- WriteBufferSize int
// Maximum number of concurrent connections. Default: number of runtime processors
MaximumConcurrentConnections int
EnableUserAgentOnConnect bool
+
+ // RequestInterceptors are functions that modify HTTP requests before sending.
+ RequestInterceptors []RequestInterceptor
+}
+
+// protocol defines the interface for HTTP communication with Gremlin server
+type protocol interface {
+ send(request *request) (ResultSet, error)
+ close()
}
// Client is used to connect and interact with a Gremlin-supported server.
@@ -63,7 +60,7 @@ type Client struct {
traversalSource string
logHandler *logHandler
connectionSettings *connectionSettings
- gremlinClient *gremlinClient
+ protocol protocol
}
// NewClient creates a Client and configures it with the given parameters.
@@ -74,15 +71,10 @@ func NewClient(url string, configurations ...func(settings *ClientSettings)) (*C
LogVerbosity: Info,
Logger: &defaultLogger{},
Language: language.English,
- AuthInfo: &AuthInfo{},
TlsConfig: &tls.Config{},
- KeepAliveInterval: keepAliveIntervalDefault,
- WriteDeadline: writeDeadlineDefault,
ConnectionTimeout: connectionTimeoutDefault,
EnableCompression: false,
EnableUserAgentOnConnect: true,
- ReadBufferSize: readBufferSizeDefault,
- WriteBufferSize: writeBufferSizeDefault,
MaximumConcurrentConnections: runtime.NumCPU(),
}
@@ -91,27 +83,27 @@ func NewClient(url string, configurations ...func(settings *ClientSettings)) (*C
}
connSettings := &connectionSettings{
- authInfo: settings.AuthInfo,
tlsConfig: settings.TlsConfig,
- keepAliveInterval: settings.KeepAliveInterval,
- writeDeadline: settings.WriteDeadline,
connectionTimeout: settings.ConnectionTimeout,
enableCompression: settings.EnableCompression,
- readBufferSize: settings.ReadBufferSize,
- writeBufferSize: settings.WriteBufferSize,
enableUserAgentOnConnect: settings.EnableUserAgentOnConnect,
}
logHandler := newLogHandler(settings.Logger, settings.LogVerbosity, settings.Language)
- gc := newGremlinClient(logHandler, url, connSettings)
+ conn := newHttpConnection(logHandler, url, connSettings)
+
+ // Add user-provided interceptors
+ for _, interceptor := range settings.RequestInterceptors {
+ conn.AddInterceptor(interceptor)
+ }
client := &Client{
url: url,
traversalSource: settings.TraversalSource,
logHandler: logHandler,
connectionSettings: connSettings,
- gremlinClient: gc,
+ protocol: conn,
}
return client, nil
@@ -121,17 +113,20 @@ func NewClient(url string, configurations ...func(settings *ClientSettings)) (*C
// This is idempotent due to the underlying close() methods being idempotent as well.
func (client *Client) Close() {
client.logHandler.logf(Info, closeClient, client.url)
- client.gremlinClient.close()
+ if client.protocol != nil {
+ client.protocol.close()
+ }
+}
+
+func (client *Client) errorCallback() {
+ client.logHandler.log(Error, errorCallback)
}
// 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)
-
- // TODO interceptors (ie. auth)
-
- rs, err := client.gremlinClient.send(&request)
+ rs, err := client.protocol.send(&request)
return rs, err
}
@@ -146,9 +141,43 @@ func (client *Client) Submit(traversalString string, bindings ...map[string]inte
return client.SubmitWithOptions(traversalString, requestOptionsBuilder.Create())
}
-// 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)
- return client.gremlinClient.send(&request)
+// submitGremlinLang submits GremlinLang to the server to execute and returns a ResultSet.
+func (client *Client) submitGremlinLang(gremlinLang *GremlinLang) (ResultSet, error) {
+ client.logHandler.logf(Debug, submitStartedString, *gremlinLang)
+ requestOptionsBuilder := new(RequestOptionsBuilder)
+ if len(gremlinLang.GetParameters()) > 0 {
+ requestOptionsBuilder.SetBindings(gremlinLang.GetParameters())
+ }
+ if len(gremlinLang.optionsStrategies) > 0 {
+ requestOptionsBuilder = applyOptionsConfig(requestOptionsBuilder, gremlinLang.optionsStrategies[0].configuration)
+ }
+
+ request := MakeStringRequest(gremlinLang.GetGremlin(), client.traversalSource, requestOptionsBuilder.Create())
+ return client.protocol.send(&request)
+}
+
+func applyOptionsConfig(builder *RequestOptionsBuilder, config map[string]interface{}) *RequestOptionsBuilder {
+ builderValue := reflect.ValueOf(builder)
+
+ // Map configuration keys to setter method names
+ setterMap := map[string]string{
+ "requestId": "SetRequestId",
+ "evaluationTimeout": "SetEvaluationTimeout",
+ "batchSize": "SetBatchSize",
+ "userAgent": "SetUserAgent",
+ "bindings": "SetBindings",
+ "materializeProperties": "SetMaterializeProperties",
+ }
+
+ for key, value := range config {
+ if methodName, exists := setterMap[key]; exists {
+ method := builderValue.MethodByName(methodName)
+ if method.IsValid() {
+ args := []reflect.Value{reflect.ValueOf(value)}
+ method.Call(args)
+ }
+ }
+ }
+
+ return builder
}
diff --git a/gremlin-go/driver/client_test.go b/gremlin-go/driver/client_test.go
index 6068f89ecc..fdfd1204da 100644
--- a/gremlin-go/driver/client_test.go
+++ b/gremlin-go/driver/client_test.go
@@ -30,7 +30,6 @@ func TestClient(t *testing.T) {
// Integration test variables.
testNoAuthUrl := getEnvOrDefaultString("GREMLIN_SERVER_URL", noAuthUrl)
testNoAuthEnable := getEnvOrDefaultBool("RUN_INTEGRATION_TESTS", true)
- testNoAuthAuthInfo := &AuthInfo{}
testNoAuthTlsConfig := &tls.Config{}
t.Run("Test client.SubmitWithOptions()", func(t *testing.T) {
@@ -38,7 +37,6 @@ func TestClient(t *testing.T) {
client, err := NewClient(testNoAuthUrl,
func(settings *ClientSettings) {
settings.TlsConfig = testNoAuthTlsConfig
- settings.AuthInfo = testNoAuthAuthInfo
})
assert.NoError(t, err)
assert.NotNil(t, client)
@@ -58,11 +56,10 @@ func TestClient(t *testing.T) {
client, err := NewClient(testNoAuthUrl,
func(settings *ClientSettings) {
settings.TlsConfig = testNoAuthTlsConfig
- settings.AuthInfo = testNoAuthAuthInfo
})
assert.NoError(t, err)
assert.NotNil(t, client)
- resultSet, err := client.Submit("2+2")
+ resultSet, err := client.Submit("g.inject(2)")
assert.NoError(t, err)
assert.NotNil(t, resultSet)
@@ -74,7 +71,6 @@ func TestClient(t *testing.T) {
client, err := NewClient(testNoAuthUrl,
func(settings *ClientSettings) {
settings.TlsConfig = testNoAuthTlsConfig
- settings.AuthInfo = testNoAuthAuthInfo
settings.TraversalSource = testServerModernGraphAlias
})
assert.NoError(t, err)
@@ -97,16 +93,15 @@ func TestClient(t *testing.T) {
client, err := NewClient(testNoAuthUrl,
func(settings *ClientSettings) {
settings.TlsConfig = testNoAuthTlsConfig
- settings.AuthInfo = testNoAuthAuthInfo
settings.TraversalSource = testServerModernGraphAlias
})
assert.NoError(t, err)
assert.NotNil(t, client)
defer client.Close()
- bindings := map[string]interface{}{"x": 2}
+ bindings := map[string]interface{}{"x": 1}
- resultSet, err := client.Submit("x + x", bindings)
+ resultSet, err := client.Submit("g.V(x).values(\"name\")", bindings)
assert.NoError(t, err)
assert.NotNil(t, resultSet)
@@ -114,7 +109,7 @@ func TestClient(t *testing.T) {
assert.NoError(t, err)
assert.True(t, ok)
- assert.Equal(t, int64(4), result.Data)
+ assert.Equal(t, "marko", result.Data)
})
t.Run("Test client.SubmitWithOptions() with bindings", func(t *testing.T) {
@@ -122,16 +117,15 @@ func TestClient(t *testing.T) {
client, err := NewClient(testNoAuthUrl,
func(settings *ClientSettings) {
settings.TlsConfig = testNoAuthTlsConfig
- settings.AuthInfo = testNoAuthAuthInfo
settings.TraversalSource = testServerModernGraphAlias
})
assert.NoError(t, err)
assert.NotNil(t, client)
defer client.Close()
- bindings := map[string]interface{}{"x": 2}
+ bindings := map[string]interface{}{"x": 1}
- resultSet, err := client.SubmitWithOptions("x + x", new(RequestOptionsBuilder).SetBindings(bindings).Create())
+ resultSet, err := client.SubmitWithOptions("g.V(x).values(\"name\")", new(RequestOptionsBuilder).SetBindings(bindings).Create())
assert.NoError(t, err)
assert.NotNil(t, resultSet)
@@ -139,7 +133,7 @@ func TestClient(t *testing.T) {
assert.NoError(t, err)
assert.True(t, ok)
- assert.Equal(t, int64(4), result.Data)
+ assert.Equal(t, "marko", result.Data)
})
t.Run("Test client.submit() with materializeProperties", func(t *testing.T) {
@@ -147,7 +141,6 @@ func TestClient(t *testing.T) {
client, err := NewClient(testNoAuthUrl,
func(settings *ClientSettings) {
settings.TlsConfig = testNoAuthTlsConfig
- settings.AuthInfo = testNoAuthAuthInfo
settings.TraversalSource = testServerModernGraphAlias
})
@@ -170,7 +163,6 @@ func TestClient(t *testing.T) {
client, err := NewClient(testNoAuthUrl,
func(settings *ClientSettings) {
settings.TlsConfig = testNoAuthTlsConfig
- settings.AuthInfo = testNoAuthAuthInfo
settings.TraversalSource = testServerCrewGraphAlias
})
diff --git a/gremlin-go/driver/connection.go b/gremlin-go/driver/connection.go
index 9dbb1ec8ea..f04997eeb0 100644
--- a/gremlin-go/driver/connection.go
+++ b/gremlin-go/driver/connection.go
@@ -25,13 +25,8 @@ import (
)
type connectionSettings struct {
- authInfo AuthInfoProvider
tlsConfig *tls.Config
- keepAliveInterval time.Duration
- writeDeadline time.Duration
connectionTimeout time.Duration
enableCompression bool
- readBufferSize int
- writeBufferSize int
enableUserAgentOnConnect bool
}
diff --git a/gremlin-go/driver/connection_test.go b/gremlin-go/driver/connection_test.go
index c0b80fde03..8dee3d6567 100644
--- a/gremlin-go/driver/connection_test.go
+++ b/gremlin-go/driver/connection_test.go
@@ -22,7 +22,6 @@ package gremlingo
import (
"crypto/tls"
"fmt"
- "github.com/stretchr/testify/assert"
"math/big"
"os"
"reflect"
@@ -30,6 +29,9 @@ import (
"strconv"
"sync"
"testing"
+ "time"
+
+ "github.com/stretchr/testify/assert"
)
const personLabel = "Person"
@@ -47,21 +49,17 @@ const nonRoutableIPForConnectionTimeout = "http://10.255.255.1/"
// transaction is enabled on the same port as no auth url
const noAuthUrl = "http://localhost:45940/gremlin"
-const basicAuthWithSsl = "wss://localhost:45941/gremlin"
+const noAuthSslUrl = "https://localhost:45941/gremlin"
+const basicAuthWithSsl = "https://localhost:45941/gremlin"
var testNames = []string{"Lyndon", "Yang", "Simon", "Rithin", "Alexey", "Valentyn"}
func newDefaultConnectionSettings() *connectionSettings {
return &connectionSettings{
- authInfo: &AuthInfo{},
tlsConfig: &tls.Config{},
- keepAliveInterval: keepAliveIntervalDefault,
- writeDeadline: writeDeadlineDefault,
connectionTimeout: connectionTimeoutDefault,
enableCompression: false,
enableUserAgentOnConnect: true,
- readBufferSize: readBufferSizeDefault,
- writeBufferSize: writeBufferSizeDefault,
}
}
@@ -88,11 +86,10 @@ func addTestData(t *testing.T, g *GraphTraversalSource) {
assert.Nil(t, <-promise)
}
-func getTestGraph(t *testing.T, url string, auth AuthInfoProvider, tls *tls.Config) *GraphTraversalSource {
+func getTestGraph(t *testing.T, url string, tls *tls.Config) *GraphTraversalSource {
remote, err := NewDriverRemoteConnection(url,
func(settings *DriverRemoteConnectionSettings) {
settings.TlsConfig = tls
- settings.AuthInfo = auth
settings.TraversalSource = testServerGraphAlias
})
assert.Nil(t, err)
@@ -102,8 +99,8 @@ func getTestGraph(t *testing.T, url string, auth AuthInfoProvider, tls *tls.Conf
return g
}
-func initializeGraph(t *testing.T, url string, auth AuthInfoProvider, tls *tls.Config) *GraphTraversalSource {
- g := getTestGraph(t, url, auth, tls)
+func initializeGraph(t *testing.T, url string, tls *tls.Config) *GraphTraversalSource {
+ g := getTestGraph(t, url, tls)
// Drop the graph and check that it is empty.
dropGraph(t, g)
@@ -242,11 +239,6 @@ func getEnvOrDefaultBool(key string, defaultValue bool) bool {
return defaultValue
}
-func getBasicAuthInfo() *AuthInfo {
- return BasicAuthInfo(getEnvOrDefaultString("GREMLIN_GO_BASIC_AUTH_USERNAME", "stephen"),
- getEnvOrDefaultString("GREMLIN_GO_BASIC_AUTH_PASSWORD", "password"))
-}
-
func skipTestsIfNotEnabled(t *testing.T, testSuiteName string, testSuiteEnabled bool) {
if !testSuiteEnabled {
t.Skipf("Skipping %s because %s tests are not enabled.", t.Name(), testSuiteName)
@@ -257,29 +249,62 @@ func TestConnection(t *testing.T) {
// Integration test variables.
testNoAuthUrl := getEnvOrDefaultString("GREMLIN_SERVER_URL", noAuthUrl)
testNoAuthEnable := getEnvOrDefaultBool("RUN_INTEGRATION_TESTS", true)
- testNoAuthAuthInfo := &AuthInfo{}
testNoAuthTlsConfig := &tls.Config{}
// No authentication integration test with graphs loaded and alias configured server
testNoAuthWithAliasUrl := getEnvOrDefaultString("GREMLIN_SERVER_URL", noAuthUrl)
testNoAuthWithAliasEnable := getEnvOrDefaultBool("RUN_INTEGRATION_WITH_ALIAS_TESTS", true)
- testNoAuthWithAliasAuthInfo := &AuthInfo{}
testNoAuthWithAliasTlsConfig := &tls.Config{}
// Basic authentication integration test variables.
testBasicAuthUrl := getEnvOrDefaultString("GREMLIN_SERVER_BASIC_AUTH_URL", basicAuthWithSsl)
testBasicAuthEnable := getEnvOrDefaultBool("RUN_BASIC_AUTH_INTEGRATION_TESTS", false)
- testBasicAuthAuthInfo := getBasicAuthInfo()
+ testBasicAuthUsername := getEnvOrDefaultString("GREMLIN_GO_BASIC_AUTH_USERNAME", "stephen")
+ testBasicAuthPassword := getEnvOrDefaultString("GREMLIN_GO_BASIC_AUTH_PASSWORD", "password")
testBasicAuthTlsConfig := &tls.Config{InsecureSkipVerify: true}
+ // this test is used to test the ws->http POC changes via manual execution with a local TP 4.0 gremlin server running on 8182
+ t.Run("Test client.submit()", func(t *testing.T) {
+ skipTestsIfNotEnabled(t, integrationTestSuiteName, testNoAuthEnable)
+
+ tlsConf := tls.Config{
+ InsecureSkipVerify: true,
+ }
+
+ client, err := NewClient(testNoAuthUrl,
+ //client, err := NewClient(noAuthSslUrl,
+ func(settings *ClientSettings) {
+ settings.TlsConfig = &tlsConf
+ settings.EnableCompression = true
+ settings.TraversalSource = testServerModernGraphAlias
+ })
+ assert.Nil(t, err)
+ assert.NotNil(t, client)
+ defer client.Close()
+
+ // synchronous
+ for i := 0; i < 5; i++ {
+ submitCount(i, client, t)
+ }
+
+ // async
+ var wg sync.WaitGroup
+ for i := 0; i < 5; i++ {
+ wg.Add(1)
+ go func(i int) {
+ defer wg.Done()
+ submitCount(i, client, t)
+ }(i)
+ }
+ wg.Wait()
+ })
+
t.Run("Test client.submit() with concurrency", func(t *testing.T) {
skipTestsIfNotEnabled(t, integrationTestSuiteName, testNoAuthEnable)
client, err := NewClient(testNoAuthUrl,
func(settings *ClientSettings) {
settings.TlsConfig = testNoAuthTlsConfig
- settings.AuthInfo = testNoAuthAuthInfo
- settings.WriteBufferSize = 1024
settings.EnableCompression = true
settings.TraversalSource = testServerModernGraphAlias
})
@@ -308,7 +333,7 @@ func TestConnection(t *testing.T) {
skipTestsIfNotEnabled(t, integrationTestSuiteName, testNoAuthEnable)
// Initialize graph
- g := initializeGraph(t, testNoAuthUrl, testNoAuthAuthInfo, testNoAuthTlsConfig)
+ g := initializeGraph(t, testNoAuthUrl, testNoAuthTlsConfig)
defer g.remoteConnection.Close()
// Read test data out of the graph and check that it is correct.
@@ -323,7 +348,7 @@ func TestConnection(t *testing.T) {
skipTestsIfNotEnabled(t, integrationTestSuiteName, testNoAuthEnable)
// Initialize graph
- g := initializeGraph(t, testNoAuthUrl, testNoAuthAuthInfo, testNoAuthTlsConfig)
+ g := initializeGraph(t, testNoAuthUrl, testNoAuthTlsConfig)
defer g.remoteConnection.Close()
readWithNextAndHasNext(t, g)
@@ -334,7 +359,7 @@ func TestConnection(t *testing.T) {
skipTestsIfNotEnabled(t, integrationTestSuiteName, testNoAuthEnable)
// Initialize graph
- g := initializeGraph(t, testNoAuthUrl, testNoAuthAuthInfo, testNoAuthTlsConfig)
+ g := initializeGraph(t, testNoAuthUrl, testNoAuthTlsConfig)
defer g.remoteConnection.Close()
resultSet, err := g.V().HasLabel(personLabel).Properties(nameKey).GetResultSet()
@@ -358,7 +383,7 @@ func TestConnection(t *testing.T) {
skipTestsIfNotEnabled(t, integrationTestSuiteName, testNoAuthEnable)
// Initialize graph
- g := getTestGraph(t, testNoAuthUrl, testNoAuthAuthInfo, testNoAuthTlsConfig)
+ g := getTestGraph(t, testNoAuthUrl, testNoAuthTlsConfig)
defer g.remoteConnection.Close()
// Drop the graph.
@@ -403,7 +428,7 @@ func TestConnection(t *testing.T) {
skipTestsIfNotEnabled(t, integrationTestSuiteName, testNoAuthEnable)
// Initialize graph
- g := initializeGraph(t, testNoAuthUrl, testNoAuthAuthInfo, testNoAuthTlsConfig)
+ g := initializeGraph(t, testNoAuthUrl, testNoAuthTlsConfig)
defer g.remoteConnection.Close()
// Read test data out of the graph and check that it is correct.
@@ -419,7 +444,7 @@ func TestConnection(t *testing.T) {
skipTestsIfNotEnabled(t, integrationTestSuiteName, testNoAuthEnable)
// Initialize graph
- g := initializeGraph(t, testNoAuthUrl, testNoAuthAuthInfo, testNoAuthTlsConfig)
+ g := initializeGraph(t, testNoAuthUrl, testNoAuthTlsConfig)
defer g.remoteConnection.Close()
// Run traversal and test Next/HasNext calls
@@ -445,7 +470,7 @@ func TestConnection(t *testing.T) {
skipTestsIfNotEnabled(t, integrationTestSuiteName, testNoAuthEnable)
// Initialize graph
- g := initializeGraph(t, testNoAuthUrl, testNoAuthAuthInfo, testNoAuthTlsConfig)
+ g := initializeGraph(t, testNoAuthUrl, testNoAuthTlsConfig)
defer g.remoteConnection.Close()
readUsingAnonymousTraversal(t, g)
@@ -474,7 +499,9 @@ func TestConnection(t *testing.T) {
remote, err := NewDriverRemoteConnection(testBasicAuthUrl,
func(settings *DriverRemoteConnectionSettings) {
settings.TlsConfig = testBasicAuthTlsConfig
- settings.AuthInfo = testBasicAuthAuthInfo
+ settings.RequestInterceptors = []RequestInterceptor{
+ BasicAuth(testBasicAuthUsername, testBasicAuthPassword),
+ }
})
assert.Nil(t, err)
assert.NotNil(t, remote)
@@ -500,7 +527,7 @@ func TestConnection(t *testing.T) {
skipTestsIfNotEnabled(t, integrationTestSuiteName, testNoAuthEnable)
// Initialize graph
- g := initializeGraph(t, testNoAuthUrl, testNoAuthAuthInfo, testNoAuthTlsConfig)
+ g := initializeGraph(t, testNoAuthUrl, testNoAuthTlsConfig)
defer g.remoteConnection.Close()
r, err := g.WithSack(1).V().Has("name", "Lyndon").Values("foo").Sack(Operator.Sum).Sack().ToList()
@@ -514,48 +541,30 @@ 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].Data.(*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.Data.(*GremlinType))
-
- resetGraph(t, g)
- })
+ // TODO re-enable after profile() step is updated in server
+ //t.Run("Test DriverRemoteConnection GraphTraversal with Profile()", func(t *testing.T) {
+ // skipTestsIfNotEnabled(t, integrationTestSuiteName, testNoAuthEnable)
+ //
+ // // Initialize graph
+ // g := initializeGraph(t, testNoAuthUrl, 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].Data.(*TraversalMetrics)
+ // assert.NotNil(t, metrics)
+ // assert.GreaterOrEqual(t, len(metrics.Metrics), 2)
+ //
+ // 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)
+ g := initializeGraph(t, testNoAuthUrl, testNoAuthTlsConfig)
defer g.remoteConnection.Close()
prop := &BigDecimal{11, big.NewInt(int64(22))}
@@ -563,9 +572,10 @@ func TestConnection(t *testing.T) {
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.Data.(*BigDecimal))
+ // TODO revisit BigDecimal implementation
+ //r, err := g.V().HasLabel("type_test").Values("data").Next()
+ //assert.Nil(t, err)
+ //assert.Equal(t, prop, r.Data.(*BigDecimal))
resetGraph(t, g)
})
@@ -574,7 +584,7 @@ func TestConnection(t *testing.T) {
skipTestsIfNotEnabled(t, integrationTestSuiteName, testNoAuthEnable)
// Initialize graph
- g := initializeGraph(t, testNoAuthUrl, testNoAuthAuthInfo, testNoAuthTlsConfig)
+ g := initializeGraph(t, testNoAuthUrl, testNoAuthTlsConfig)
defer g.remoteConnection.Close()
prop := &ByteBuffer{[]byte{byte(127), byte(255)}}
@@ -594,7 +604,6 @@ func TestConnection(t *testing.T) {
remote, err := NewDriverRemoteConnection(testNoAuthWithAliasUrl,
func(settings *DriverRemoteConnectionSettings) {
settings.TlsConfig = testNoAuthWithAliasTlsConfig
- settings.AuthInfo = testNoAuthWithAliasAuthInfo
settings.TraversalSource = testServerModernGraphAlias
})
assert.Nil(t, err)
@@ -610,60 +619,11 @@ func TestConnection(t *testing.T) {
}
})
- t.Run("Test Client.Submit() Simple String Query with Bindings", func(t *testing.T) {
- skipTestsIfNotEnabled(t, integrationTestSuiteName, testNoAuthEnable)
-
- client, err := NewClient(testNoAuthUrl,
- func(settings *ClientSettings) {
- settings.TlsConfig = testNoAuthTlsConfig
- settings.AuthInfo = testNoAuthAuthInfo
- })
- assert.Nil(t, err)
- assert.NotNil(t, client)
- defer client.Close()
-
- resultSet, err := client.Submit("g.inject(x).math('_+_')", map[string]interface{}{"x": 2})
- assert.Nil(t, err)
- assert.NotNil(t, resultSet)
- result, ok, err := resultSet.One()
- assert.Nil(t, err)
- assert.True(t, ok)
- assert.NotNil(t, result)
- res, err := result.GetInt()
- assert.Nil(t, err)
- assert.Equal(t, 4, res)
- })
-
- t.Run("Test Bindings To Server Configured with Modern Graph", func(t *testing.T) {
- skipTestsIfNotEnabled(t, integrationTestSuiteName, testNoAuthWithAliasEnable)
- remote, err := NewDriverRemoteConnection(testNoAuthWithAliasUrl,
- func(settings *DriverRemoteConnectionSettings) {
- settings.TlsConfig = testNoAuthWithAliasTlsConfig
- settings.AuthInfo = testNoAuthWithAliasAuthInfo
- settings.TraversalSource = testServerModernGraphAlias
- })
- assert.Nil(t, err)
- assert.NotNil(t, remote)
- defer remote.Close()
- g := Traversal_().With(remote)
-
- r, err := g.V((&Bindings{}).Of("x", 1)).Out("created").Map(&Lambda{Script: "it.get().value('name').length()", Language: ""}).Sum().ToList()
- assert.Nil(t, err)
- for _, res := range r {
- assert.Equal(t, int32(3), res.GetInterface())
- }
- r, err = g.V((&Bindings{}).Of("x", 4)).Out("created").Map(&Lambda{Script: "it.get().value('name').length()", Language: ""}).Sum().ToList()
- assert.Nil(t, err)
- for _, res := range r {
- assert.Equal(t, int32(9), res.GetInterface())
- }
- })
-
t.Run("Test DriverRemoteConnection Invalid GraphTraversal", func(t *testing.T) {
skipTestsIfNotEnabled(t, integrationTestSuiteName, testNoAuthEnable)
// Initialize graph
- g := initializeGraph(t, testNoAuthUrl, testNoAuthAuthInfo, testNoAuthTlsConfig)
+ g := initializeGraph(t, testNoAuthUrl, testNoAuthTlsConfig)
// Drop the graph.
dropGraph(t, g)
@@ -681,76 +641,10 @@ func TestConnection(t *testing.T) {
dropGraph(t, g)
})
- t.Run("Test per-request arguments", func(t *testing.T) {
- skipTestsIfNotEnabled(t, integrationTestSuiteName, testNoAuthEnable)
-
- g := getTestGraph(t, testNoAuthUrl, testNoAuthAuthInfo, testNoAuthTlsConfig)
- defer g.remoteConnection.Close()
-
- reqArgsTests := []struct {
- msg string
- traversal *GraphTraversal
- nilErr bool
- }{
- {
- "Traversal must time out (With)",
- g.
- With("evaluationTimeout", 10).
- Inject(1).
- SideEffect(&Lambda{"Thread.sleep(5000)", "gremlin-groovy"}),
- false,
- },
- {
- "Traversal must finish (With)",
- g.
- With("evaluationTimeout", 10000).
- Inject(1).
- SideEffect(&Lambda{"Thread.sleep(5000)", "gremlin-groovy"}),
- true,
- },
- {
- "evaluationTimeout is overridden and traversal must time out (With)",
- g.
- With("evaluationTimeout", 10000).With("evaluationTimeout", 10).
- Inject(1).
- SideEffect(&Lambda{"Thread.sleep(5000)", "gremlin-groovy"}),
- false,
- },
- {
- "Traversal must time out (OptionsStrategy)",
- g.
- WithStrategies(OptionsStrategy(map[string]interface{}{"evaluationTimeout": 10})).
- Inject(1).
- SideEffect(&Lambda{"Thread.sleep(5000)", "gremlin-groovy"}),
- false,
- },
- {
- "Traversal must finish (OptionsStrategy)",
- g.
- WithStrategies(OptionsStrategy(map[string]interface{}{"evaluationTimeout": 10000})).
- Inject(1).
- SideEffect(&Lambda{"Thread.sleep(5000)", "gremlin-groovy"}),
- true,
- },
- }
-
- gotErrs := make([]<-chan error, len(reqArgsTests))
-
- // Run tests in parallel.
- for i, tt := range reqArgsTests {
- gotErrs[i] = tt.traversal.Iterate()
- }
-
- // Check error promises.
- for i, tt := range reqArgsTests {
- assert.Equal(t, <-gotErrs[i] == nil, tt.nilErr, tt.msg)
- }
- })
-
t.Run("Get all properties when materializeProperties is all", func(t *testing.T) {
skipTestsIfNotEnabled(t, integrationTestSuiteName, testNoAuthEnable)
- g := getModernGraph(t, testNoAuthUrl, &AuthInfo{}, &tls.Config{})
+ g := getModernGraph(t, testNoAuthUrl, &tls.Config{})
defer g.remoteConnection.Close()
// vertex contains 2 properties, name and age
@@ -763,7 +657,7 @@ func TestConnection(t *testing.T) {
t.Run("Skip properties when materializeProperties is tokens", func(t *testing.T) {
skipTestsIfNotEnabled(t, integrationTestSuiteName, testNoAuthEnable)
- g := getModernGraph(t, testNoAuthUrl, &AuthInfo{}, &tls.Config{})
+ g := getModernGraph(t, testNoAuthUrl, &tls.Config{})
defer g.remoteConnection.Close()
// vertex contains 2 properties, name and age
@@ -776,7 +670,7 @@ func TestConnection(t *testing.T) {
t.Run("Get all properties when no materializeProperties", func(t *testing.T) {
skipTestsIfNotEnabled(t, integrationTestSuiteName, testNoAuthEnable)
- g := getModernGraph(t, testNoAuthUrl, &AuthInfo{}, &tls.Config{})
+ g := getModernGraph(t, testNoAuthUrl, &tls.Config{})
defer g.remoteConnection.Close()
r, err := g.V().Has("person", "name", "marko").Next()
@@ -788,7 +682,7 @@ func TestConnection(t *testing.T) {
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{})
+ g := getModernGraph(t, testNoAuthUrl, &tls.Config{})
defer g.remoteConnection.Close()
vertices, err := g.With("materializeProperties", MaterializeProperties.Tokens).V().ToList()
@@ -889,3 +783,62 @@ func submitCount(i int, client *Client, t *testing.T) {
assert.Equal(t, 6+i, c)
_, _ = fmt.Fprintf(os.Stdout, "Received result : %s\n", result)
}
+
+func TestStreamingResultDelivery(t *testing.T) {
+ testNoAuthWithAliasEnable := getEnvOrDefaultBool("RUN_INTEGRATION_WITH_ALIAS_TESTS", true)
+ skipTestsIfNotEnabled(t, integrationTestSuiteName, testNoAuthWithAliasEnable)
+ remote, err := NewDriverRemoteConnection(getEnvOrDefaultString("GREMLIN_SERVER_URL", noAuthUrl),
+ func(settings *DriverRemoteConnectionSettings) {
+ settings.TlsConfig = &tls.Config{}
+ settings.TraversalSource = "ggrateful"
+ })
+ assert.Nil(t, err)
+ assert.NotNil(t, remote)
+ g := Traversal_().With(remote)
+ defer g.remoteConnection.Close()
+
+ t.Run("first result arrives before all results", func(t *testing.T) {
+ start := time.Now()
+ rs, err := g.V().Properties().GetResultSet()
+ assert.Nil(t, err)
+
+ // First result should arrive quickly
+ _, ok, err := rs.One()
+ firstResultTime := time.Since(start)
+ assert.Nil(t, err)
+ assert.True(t, ok)
+
+ // Drain remaining
+ _, err = rs.All()
+ assert.Nil(t, err)
+ totalTime := time.Since(start)
+
+ t.Logf("First result: %v, Total: %v, Ratio: %.2f%%",
+ firstResultTime, totalTime, float64(firstResultTime)/float64(totalTime)*100)
+ })
+
+ t.Run("results arrive incrementally", func(t *testing.T) {
+ rs, err := g.V().Properties().GetResultSet()
+ assert.Nil(t, err)
+
+ var timestamps []time.Time
+ start := time.Now()
+
+ for {
+ _, ok, err := rs.One()
+ assert.Nil(t, err)
+ if !ok {
+ break
+ }
+ timestamps = append(timestamps, time.Now())
+ }
+
+ if len(timestamps) < 2 {
+ t.Skip("need more results to test incremental delivery")
+ }
+
+ firstHalf := timestamps[len(timestamps)/2].Sub(start)
+ total := timestamps[len(timestamps)-1].Sub(start)
+ t.Logf("Half results at: %v, All results at: %v", firstHalf, total)
+ })
+}
diff --git a/gremlin-go/driver/cucumber/cucumberSteps_test.go b/gremlin-go/driver/cucumber/cucumberSteps_test.go
index 0942ae1384..532f950823 100644
--- a/gremlin-go/driver/cucumber/cucumberSteps_test.go
+++ b/gremlin-go/driver/cucumber/cucumberSteps_test.go
@@ -430,7 +430,7 @@ func (tg *tinkerPopGraph) nothingShouldHappenBecause(arg1 *godog.DocString) erro
func (tg *tinkerPopGraph) chooseGraph(graphName string) error {
tg.graphName = graphName
data := tg.graphDataMap[graphName]
- tg.g = gremlingo.Traversal_().With(data.connection)
+ tg.g = gremlingo.Traversal_().With(data.connection).With("language", "gremlin-lang")
if graphName == "empty" {
err := tg.cleanEmptyDataGraph(tg.g)
if err != nil {
@@ -951,11 +951,8 @@ func InitializeScenario(ctx *godog.ScenarioContext) {
ctx.Step(`^the result should have a count of (\d+)$`, tg.theResultShouldHaveACountOf)
ctx.Step(`^the traversal of$`, tg.theTraversalOf)
ctx.Step(`^using the parameter (.+) defined as "(.+)"$`, tg.usingTheParameterDefined)
-<<<<<<< HEAD
-=======
ctx.Step(`^using the parameter (.+) of P\.(.+)\("(.+)"\)$`, tg.usingTheParameterOfP)
ctx.Step(`^using the side effect (.+) defined as"(.+)"$`, tg.usingTheSideEffectDefined)
->>>>>>> 3.8-dev
ctx.Step(`^the traversal will raise an error$`, tg.theTraversalWillRaiseAnError)
ctx.Step(`^the traversal will raise an error with message (\w+) text of "(.+)"$`, tg.theTraversalWillRaiseAnErrorWithMessageContainingTextOf)
}
diff --git a/gremlin-go/driver/cucumber/cucumberWorld.go b/gremlin-go/driver/cucumber/cucumberWorld.go
index 6e6d008af0..dcbec09ef4 100644
--- a/gremlin-go/driver/cucumber/cucumberWorld.go
+++ b/gremlin-go/driver/cucumber/cucumberWorld.go
@@ -21,11 +21,12 @@ package gremlingo
import (
"fmt"
- gremlingo "github.com/apache/tinkerpop/gremlin-go/v3/driver"
- "github.com/cucumber/godog"
"os"
"reflect"
"strconv"
+
+ gremlingo "github.com/apache/tinkerpop/gremlin-go/v3/driver"
+ "github.com/cucumber/godog"
)
type CucumberWorld struct {
@@ -108,7 +109,7 @@ func (t *CucumberWorld) loadAllDataGraph() {
if err != nil {
panic(fmt.Sprintf("Failed to create connection '%v'", err))
}
- g := gremlingo.Traversal_().With(connection)
+ g := gremlingo.Traversal_().With(connection).With("language", "gremlin-lang")
t.graphDataMap[name] = &DataGraph{
name: name,
connection: connection,
@@ -129,7 +130,7 @@ func (t *CucumberWorld) loadEmptyDataGraph() {
func (t *CucumberWorld) reloadEmptyData() {
graphData := t.getDataGraphFromMap("empty")
- g := gremlingo.Traversal_().With(graphData.connection)
+ g := gremlingo.Traversal_().With(graphData.connection).With("language", "gremlin-lang")
graphData.vertices = getVertices(g)
graphData.edges = getEdges(g)
}
@@ -164,8 +165,11 @@ func getVertices(g *gremlingo.GraphTraversalSource) map[string]*gremlingo.Vertex
func getEdges(g *gremlingo.GraphTraversalSource) map[string]*gremlingo.Edge {
edgeMap := make(map[string]*gremlingo.Edge)
- resE, err := g.E().Group().By(gremlingo.T__.Project("o", "l", "i").
- By(gremlingo.T__.OutV().Values("name")).By(gremlingo.T__.Label()).By(gremlingo.T__.InV().Values("name"))).
+ resE, err := g.E().Group().
+ By(gremlingo.T__.Project("o", "l", "i").
+ By(gremlingo.T__.OutV().Values("name")).
+ By(gremlingo.T__.Label()).
+ By(gremlingo.T__.InV().Values("name"))).
By(gremlingo.T__.Tail()).Next()
if err != nil {
return nil
@@ -190,38 +194,44 @@ func getEdgeKey(edgeKeyMap map[interface{}]interface{}) string {
}
func getVertexProperties(g *gremlingo.GraphTraversalSource) map[string]*gremlingo.VertexProperty {
- vertexPropertyMap := make(map[string]*gremlingo.VertexProperty)
- res, err := g.V().Properties().Group().By(&gremlingo.Lambda{
- Script: "{ it -> \n" +
- " def val = it.value()\n" +
- " if (val instanceof Integer)\n" +
- " val = 'd[' + val + '].i'\n" +
- " else if (val instanceof Float)\n" +
- " val = 'd[' + val + '].f'\n" +
- " else if (val instanceof Double)\n" +
- " val = 'd[' + val + '].d'\n" +
- " return it.element().value('name') + '-' + it.key() + '->' + val\n" +
- "}",
- Language: "",
- }).By(gremlingo.T__.Tail()).Next()
+ vertexPropsMap := make(map[string]*gremlingo.VertexProperty)
+ res, err := g.V().Properties().Group().
+ By(gremlingo.T__.Project("n", "k", "v").
+ By(gremlingo.T__.Element().Values("name")).
+ By(gremlingo.T__.Key()).
+ By(gremlingo.T__.Value())).
+ By(gremlingo.T__.Tail()).Next()
if res == nil {
return nil
}
if err != nil {
return nil
}
- v := reflect.ValueOf(res.GetInterface())
- if v.Kind() != reflect.Map {
- fmt.Printf("Expecting to get a map as a result, got %v instead.", v.Kind())
+ valMap := reflect.ValueOf(res.GetInterface())
+ if valMap.Kind() != reflect.Map {
+ fmt.Printf("Expecting to get a map as a result, got %v instead.", valMap.Kind())
return nil
}
- keys := v.MapKeys()
+ keys := valMap.MapKeys()
for _, k := range keys {
- convKey := k.Convert(v.Type().Key())
- val := v.MapIndex(convKey)
- vertexPropertyMap[k.Interface().(string)] = val.Interface().(*gremlingo.VertexProperty)
+ convKey := k.Convert(valMap.Type().Key())
+ val := valMap.MapIndex(convKey)
+ keyMap := reflect.ValueOf(k.Interface()).Elem().Interface().(map[interface{}]interface{})
+ vertexPropsMap[getVPKey(keyMap)] = val.Interface().(*gremlingo.VertexProperty)
+ }
+ return vertexPropsMap
+}
+
+func getVPKey(vpKeyMap map[interface{}]interface{}) string {
+ k := vpKeyMap["k"]
+ v := vpKeyMap["v"]
+ if k == "weight" {
+ v = fmt.Sprint("d[", v, "].d")
+ } else if k == "age" || k == "since" || k == "skill" {
+ v = fmt.Sprint("d[", v, "].i")
}
- return vertexPropertyMap
+ // Format as n-k->v
+ return fmt.Sprint(vpKeyMap["n"], "-", k, "->", v)
}
// This function is used to isolate connection problems to each scenario, and used in the Before context hook to prevent
diff --git a/gremlin-go/driver/cucumber/gremlin.go b/gremlin-go/driver/cucumber/gremlin.go
index 50b0f53c09..60e12c57a4 100644
--- a/gremlin-go/driver/cucumber/gremlin.go
+++ b/gremlin-go/driver/cucumber/gremlin.go
@@ -2078,9 +2078,6 @@ var translationMap = map[string][]func(g *gremlingo.GraphTraversalSource, p map[
traversalFn := traversalFns[0]
translationMap[scenarioName] = traversalFns[1:]
traversal := traversalFn(g, parameters)
- for key, value := range sideEffects {
- traversal.Bytecode.AddSource("withSideEffect", key, value)
- }
return traversal, nil
} else {
return nil, errors.New("scenario for traversal not recognized")
diff --git a/gremlin-go/driver/driverRemoteConnection.go b/gremlin-go/driver/driverRemoteConnection.go
index 86de97784a..93d0b42683 100644
--- a/gremlin-go/driver/driverRemoteConnection.go
+++ b/gremlin-go/driver/driverRemoteConnection.go
@@ -33,22 +33,16 @@ type DriverRemoteConnectionSettings struct {
LogVerbosity LogVerbosity
Logger Logger
Language language.Tag
- AuthInfo AuthInfoProvider
TlsConfig *tls.Config
- KeepAliveInterval time.Duration
- WriteDeadline time.Duration
ConnectionTimeout time.Duration
EnableCompression bool
EnableUserAgentOnConnect bool
- ReadBufferSize int
- WriteBufferSize int
- // Minimum amount of concurrent active traversals on a connection to trigger creation of a new connection
- NewConnectionThreshold int
// Maximum number of concurrent connections. Default: number of runtime processors
MaximumConcurrentConnections int
- // Initial amount of instantiated connections. Default: 1
- InitialConcurrentConnections int
+
+ // RequestInterceptors are functions that modify HTTP requests before sending.
+ RequestInterceptors []RequestInterceptor
}
// DriverRemoteConnection is a remote connection.
@@ -70,15 +64,10 @@ func NewDriverRemoteConnection(
LogVerbosity: Info,
Logger: &defaultLogger{},
Language: language.English,
- AuthInfo: &AuthInfo{},
TlsConfig: &tls.Config{},
- KeepAliveInterval: keepAliveIntervalDefault,
- WriteDeadline: writeDeadlineDefault,
ConnectionTimeout: connectionTimeoutDefault,
EnableCompression: false,
EnableUserAgentOnConnect: true,
- ReadBufferSize: readBufferSizeDefault,
- WriteBufferSize: writeBufferSizeDefault,
MaximumConcurrentConnections: runtime.NumCPU(),
}
@@ -87,27 +76,27 @@ func NewDriverRemoteConnection(
}
connSettings := &connectionSettings{
- authInfo: settings.AuthInfo,
tlsConfig: settings.TlsConfig,
- keepAliveInterval: settings.KeepAliveInterval,
- writeDeadline: settings.WriteDeadline,
connectionTimeout: settings.ConnectionTimeout,
enableCompression: settings.EnableCompression,
- readBufferSize: settings.ReadBufferSize,
- writeBufferSize: settings.WriteBufferSize,
enableUserAgentOnConnect: settings.EnableUserAgentOnConnect,
}
logHandler := newLogHandler(settings.Logger, settings.LogVerbosity, settings.Language)
- httpProt := newGremlinClient(logHandler, url, connSettings)
+ conn := newHttpConnection(logHandler, url, connSettings)
+
+ // Add user-provided interceptors
+ for _, interceptor := range settings.RequestInterceptors {
+ conn.AddInterceptor(interceptor)
+ }
client := &Client{
url: url,
traversalSource: settings.TraversalSource,
logHandler: logHandler,
connectionSettings: connSettings,
- gremlinClient: httpProt,
+ protocol: conn,
}
return &DriverRemoteConnection{client: client, isClosed: false, settings: settings}, nil
@@ -135,22 +124,10 @@ func (driver *DriverRemoteConnection) Submit(traversalString string) (ResultSet,
return driver.SubmitWithOptions(traversalString, *new(RequestOptions))
}
-// submitBytecode sends a Bytecode traversal to the server.
-func (driver *DriverRemoteConnection) submitBytecode(bytecode *Bytecode) (ResultSet, error) {
+// submitGremlinLang sends a GremlinLang traversal to the server.
+func (driver *DriverRemoteConnection) submitGremlinLang(gremlinLang *GremlinLang) (ResultSet, error) {
if driver.isClosed {
- return nil, newError(err0203SubmitBytecodeToClosedConnectionError)
+ return nil, newError(err0203SubmitGremlinLangToClosedConnectionError)
}
- return driver.client.submitBytecode(bytecode)
-}
-
-func (driver *DriverRemoteConnection) commit() (ResultSet, error) {
- bc := &Bytecode{}
- bc.AddSource("tx", "commit")
- return driver.submitBytecode(bc)
-}
-
-func (driver *DriverRemoteConnection) rollback() (ResultSet, error) {
- bc := &Bytecode{}
- bc.AddSource("tx", "rollback")
- return driver.submitBytecode(bc)
+ return driver.client.submitGremlinLang(gremlinLang)
}
diff --git a/gremlin-go/driver/driverRemoteConnection_test.go b/gremlin-go/driver/driverRemoteConnection_test.go
index d702d1809e..bdae0c0aa8 100644
--- a/gremlin-go/driver/driverRemoteConnection_test.go
+++ b/gremlin-go/driver/driverRemoteConnection_test.go
@@ -20,7 +20,6 @@ under the License.
package gremlingo
import (
- "net/http"
"testing"
"github.com/stretchr/testify/assert"
@@ -28,20 +27,11 @@ import (
func TestAuthentication(t *testing.T) {
- t.Run("Test BasicAuthInfo.", func(t *testing.T) {
- header := BasicAuthInfo("Lyndon", "Bauto")
- assert.Nil(t, header.GetHeader())
- b, _, _ := header.GetBasicAuth()
- assert.True(t, b)
- })
-
- t.Run("Test GetHeader.", func(t *testing.T) {
- header := &AuthInfo{}
- assert.Nil(t, header.GetHeader())
- header = nil
- assert.Nil(t, header.GetHeader())
- httpHeader := http.Header{}
- header = &AuthInfo{Header: httpHeader}
- assert.Equal(t, httpHeader, header.GetHeader())
+ t.Run("Test BasicAuth interceptor", func(t *testing.T) {
+ interceptor := BasicAuth("user", "pass")
+ req, _ := NewHttpRequest("POST", "http://localhost:8182/gremlin")
+ err := interceptor(req)
+ assert.Nil(t, err)
+ assert.Contains(t, req.Headers.Get(HeaderAuthorization), "Basic ")
})
}
diff --git a/gremlin-go/driver/error_codes.go b/gremlin-go/driver/error_codes.go
index 61b335ecb5..03a4b1cf05 100644
--- a/gremlin-go/driver/error_codes.go
+++ b/gremlin-go/driver/error_codes.go
@@ -32,8 +32,13 @@ import (
type errorCode string
const (
+ // connection.go errors
+ err0101ConnectionCloseError errorCode = "E0101_CONNECTION_CLOSE_ERROR"
+ err0102WriteConnectionClosedError errorCode = "E0102_CONNECTION_WRITE_CLOSED_ERROR"
+
// driverRemoteConnection.go errors
- err0203SubmitBytecodeToClosedConnectionError errorCode = "E0203_DRIVER_REMOTE_CONNECTION_SUBMITBYTECODE_TO_CLOSED_CONNECTION_ERROR"
+ err0203SubmitGremlinLangToClosedConnectionError errorCode = "E0203_DRIVER_REMOTE_CONNECTION_SUBMITGREMLINLANG_TO_CLOSED_CONNECTION_ERROR"
+ err0203SubmitBytecodeToClosedConnectionError errorCode = "E0203_DRIVER_REMOTE_CONNECTION_SUBMITBYTECODE_TO_CLOSED_CONNECTION_ERROR"
// graph.go errors
err0301GetPathObjectInvalidPathUnequalLengthsError errorCode = "E0301_GRAPH_GETPATHOBJECT_UNEQUAL_LABELS_OBJECTS_LENGTH_ERROR"
@@ -53,6 +58,7 @@ const (
// response handling errors
err0501ResponseHandlerResultSetNotCreatedError errorCode = "E0501_PROTOCOL_RESPONSEHANDLER_NO_RESULTSET_ON_DATA_RECEIVE"
+ err0502ResponseHandlerReadLoopError errorCode = "E0502_PROTOCOL_RESPONSEHANDLER_READ_LOOP_ERROR"
err0502ResponseHandlerError errorCode = "E0502_PROTOCOL_RESPONSEHANDLER_ERROR"
err0503ResponseHandlerAuthError errorCode = "E0503_PROTOCOL_RESPONSEHANDLER_AUTH_ERROR"
diff --git a/gremlin-go/driver/gValue.go b/gremlin-go/driver/gValue.go
new file mode 100644
index 0000000000..7bdba213eb
--- /dev/null
+++ b/gremlin-go/driver/gValue.go
@@ -0,0 +1,55 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements. See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership. The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License. You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied. See the License for the
+specific language governing permissions and limitations
+under the License.
+*/
+
+package gremlingo
+
+import (
+ "fmt"
+ "strings"
+)
+
+// GValue is a variable or literal value that is used in a Traversal. It is composed of a key-value pair where the key
+// is the name given to the variable and the value is the object that the variable resolved to.
+type GValue struct {
+ name string
+ value interface{}
+}
+
+// NewGValue creates a new GValue to be used in traversals. The GValue name cannot begin with "_".
+func NewGValue(name string, value interface{}) GValue {
+ if strings.HasPrefix(name, "_") {
+ panic(fmt.Sprintf("invalid GValue name '%v'. Should not start with _.", name))
+ }
+ return GValue{name, value}
+}
+
+// Name returns the name of the GValue.
+func (gv GValue) Name() string {
+ return gv.name
+}
+
+// IsNil determines if the value held is of a nil value.
+func (gv GValue) IsNil() bool {
+ return gv.value == nil
+}
+
+// Value returns the value held by the GValue.
+func (gv GValue) Value() interface{} {
+ return gv.value
+}
diff --git a/gremlin-go/driver/gValue_test.go b/gremlin-go/driver/gValue_test.go
new file mode 100644
index 0000000000..cd4235278d
--- /dev/null
+++ b/gremlin-go/driver/gValue_test.go
@@ -0,0 +1,87 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements. See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership. The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License. You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied. See the License for the
+specific language governing permissions and limitations
+under the License.
+*/
+
+package gremlingo
+
+import (
+ "github.com/stretchr/testify/assert"
+ "testing"
+)
+
+func TestGValue(t *testing.T) {
+
+ t.Run("test simple gValue", func(t *testing.T) {
+ gVal := NewGValue("intVal", 2)
+ assert.Equal(t, "intVal", gVal.Name())
+ assert.Equal(t, 2, gVal.Value())
+ assert.False(t, gVal.IsNil())
+ })
+
+ t.Run("test gValue allow parameter reuse with arrays", func(t *testing.T) {
+ g := NewGraphTraversalSource(nil, nil)
+ val := [3]int{1, 2, 3}
+ param := NewGValue("ids", val)
+ gl := g.Inject(param).V(param).GremlinLang
+ assert.Equal(t, "g.inject(ids).V(ids)", gl.GetGremlin())
+ assert.Equal(t, val, gl.parameters["ids"])
+ })
+
+ t.Run("test gValue allow parameter reuse with slices", func(t *testing.T) {
+ g := NewGraphTraversalSource(nil, nil)
+ val := []int{1, 2, 3}
+ param := NewGValue("ids", val)
+ gl := g.Inject(param).V(param).GremlinLang
+ assert.Equal(t, "g.inject(ids).V(ids)", gl.GetGremlin())
+ assert.Equal(t, val, gl.parameters["ids"])
+ })
+
+ t.Run("test gValue allow parameter reuse with maps", func(t *testing.T) {
+ g := NewGraphTraversalSource(nil, nil)
+ val := map[string]int{"foo": 1, "bar": 2}
+ param := NewGValue("ids", val)
+ gl := g.Inject(param).V(param).GremlinLang
+ assert.Equal(t, "g.inject(ids).V(ids)", gl.GetGremlin())
+ assert.Equal(t, val, gl.parameters["ids"])
+ })
+
+ t.Run("test gValue name not duplicated", func(t *testing.T) {
+ g := NewGraphTraversalSource(nil, nil)
+ param1 := NewGValue("ids", [2]int{1, 2})
+ param2 := NewGValue("ids", [2]int{2, 3})
+ assert.Panics(t, func() { g.Inject(param1).V(param2) }, "parameter with name ids already exists.")
+ })
+
+ t.Run("test invalid name that starts with _", func(t *testing.T) {
+ g := NewGraphTraversalSource(nil, nil)
+ assert.Panics(t, func() { g.Inject(NewGValue("_ids", [2]int{1, 2})) },
+ "invalid GValue name _1. Should not start with _.")
+ })
+
+ t.Run("test name is valid identifier", func(t *testing.T) {
+ g := NewGraphTraversalSource(nil, nil)
+ assert.Panics(t, func() { g.Inject(NewGValue("1a", [2]int{1, 2})) },
+ "invalid parameter name '1a'")
+ })
+
+ t.Run("test name is not a number", func(t *testing.T) {
+ g := NewGraphTraversalSource(nil, nil)
+ assert.Panics(t, func() { g.Inject(NewGValue("1", [2]int{1, 2})) },
+ "invalid parameter name '1'")
+ })
+}
diff --git a/gremlin-go/driver/graphBinary.go b/gremlin-go/driver/graphBinary.go
index 5d66ad454e..c28aa633dc 100644
--- a/gremlin-go/driver/graphBinary.go
+++ b/gremlin-go/driver/graphBinary.go
@@ -22,6 +22,7 @@ package gremlingo
import (
"bytes"
"encoding/binary"
+ "errors"
"fmt"
"math"
"math/big"
@@ -31,6 +32,8 @@ import (
"github.com/google/uuid"
)
+var ErrIncompleteData = errors.New("incomplete data")
+
// Version 1.0
// dataType graphBinary types.
@@ -38,56 +41,35 @@ type dataType uint8
// dataType defined as constants.
const (
- customType dataType = 0x00
- intType dataType = 0x01
- longType dataType = 0x02
- stringType dataType = 0x03
- dateType dataType = 0x04
- timestampType dataType = 0x05
- classType dataType = 0x06
- doubleType dataType = 0x07
- floatType dataType = 0x08
- listType dataType = 0x09
- mapType dataType = 0x0a
- setType dataType = 0x0b
- uuidType dataType = 0x0c
- edgeType dataType = 0x0d
- pathType dataType = 0x0e
- propertyType dataType = 0x0f
- vertexType dataType = 0x11
- vertexPropertyType dataType = 0x12
- barrierType dataType = 0x13
- bindingType dataType = 0x14
- cardinalityType dataType = 0x16
- bytecodeType dataType = 0x15
- columnType dataType = 0x17
- directionType dataType = 0x18
- operatorType dataType = 0x19
- orderType dataType = 0x1a
- pickType dataType = 0x1b
- popType dataType = 0x1c
- lambdaType dataType = 0x1d
- pType dataType = 0x1e
- 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
- mergeType dataType = 0x2e
- dtType dataType = 0x2f
- gTypeType dataType = 0x30
- metricsType dataType = 0x2c
- traversalMetricsType dataType = 0x2d
- durationType dataType = 0x81
- offsetDateTimeType dataType = 0x88
- nullType dataType = 0xFE
+ customType dataType = 0x00
+ intType dataType = 0x01
+ longType dataType = 0x02
+ stringType dataType = 0x03
+ datetimeType dataType = 0x04
+ doubleType dataType = 0x07
+ floatType dataType = 0x08
+ listType dataType = 0x09
+ mapType dataType = 0x0a
+ setType dataType = 0x0b
+ uuidType dataType = 0x0c
+ edgeType dataType = 0x0d
+ pathType dataType = 0x0e
+ propertyType dataType = 0x0f
+ vertexType dataType = 0x11
+ vertexPropertyType dataType = 0x12
+ directionType dataType = 0x18
+ tType dataType = 0x20
+ bigDecimalType dataType = 0x22
+ bigIntegerType dataType = 0x23
+ byteType dataType = 0x24
+ byteBuffer dataType = 0x25
+ shortType dataType = 0x26
+ booleanType dataType = 0x27
+ mergeType dataType = 0x2e
+ gTypeType dataType = 0x30
+ durationType dataType = 0x81
+ markerType dataType = 0xfd
+ nullType dataType = 0xFE
)
var nullBytes = []byte{nullType.getCodeByte(), 0x01}
@@ -193,72 +175,6 @@ func mapWriter(value interface{}, buffer *bytes.Buffer, typeSerializer *graphBin
return buffer.Bytes(), nil
}
-func instructionWriter(instructions []instruction, buffer *bytes.Buffer, typeSerializer *graphBinaryTypeSerializer) error {
- // Write {steps_length}, i.e number of steps.
- err := binary.Write(buffer, binary.BigEndian, int32(len(instructions)))
- if err != nil {
- return err
- }
-
- // Write {step_0} to {step_n}.
- for _, instruction := range instructions {
- // Write {name} of {step_i}.
- // Note: {name} follows string writing, therefore write string length followed by actual string.
- _, err = typeSerializer.writeValue(instruction.operator, buffer, false)
- if err != nil {
- return err
- }
-
- // Write {values_length} of {step_i}.
- err = binary.Write(buffer, binary.BigEndian, int32(len(instruction.arguments)))
- if err != nil {
- return err
- }
-
- // Write {values_0} to {values_n}.
- for _, argument := range instruction.arguments {
- _, err = typeSerializer.write(argument, buffer)
- if err != nil {
- return err
- }
- }
- }
- return nil
-}
-
-// Format: {steps_length}{step_0}…{step_n}{sources_length}{source_0}…{source_n}
-// Where:
-//
-// {steps_length} is an Int value describing the amount of steps.
-// {step_i} is composed of {name}{values_length}{value_0}…{value_n}, where:
-// {name} is a String. This is also known as the operator.
-// {values_length} is an Int describing the amount values.
-// {value_i} is a fully qualified typed value composed of {type_code}{type_info}{value_flag}{value} describing the step argument.
-func bytecodeWriter(value interface{}, buffer *bytes.Buffer, typeSerializer *graphBinaryTypeSerializer) ([]byte, error) {
- var bc Bytecode
- switch typedVal := value.(type) {
- case *GraphTraversal:
- bc = *typedVal.Bytecode
- case Bytecode:
- bc = typedVal
- case *Bytecode:
- bc = *typedVal
- default:
- return nil, newError(err0402BytecodeWriterError)
- }
-
- // Write {steps_length} and {step_0} through {step_n}, then {sources_length} and {source_0} through {source_n}
- err := instructionWriter(bc.stepInstructions, buffer, typeSerializer)
- if err != nil {
- return nil, err
- }
- err = instructionWriter(bc.sourceInstructions, buffer, typeSerializer)
- if err != nil {
- return nil, err
- }
- return buffer.Bytes(), nil
-}
-
func stringWriter(value interface{}, buffer *bytes.Buffer, _ *graphBinaryTypeSerializer) ([]byte, error) {
err := binary.Write(buffer, binary.BigEndian, int32(len(value.(string))))
if err != nil {
@@ -368,16 +284,6 @@ func bigDecimalWriter(value interface{}, buffer *bytes.Buffer, typeSerializer *g
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)
@@ -387,7 +293,7 @@ func vertexWriter(value interface{}, buffer *bytes.Buffer, typeSerializer *graph
}
// Not fully qualified.
- _, err = typeSerializer.writeValue(v.Label, buffer, false)
+ _, err = typeSerializer.writeValue([1]string{v.Label}, buffer, false)
if err != nil {
return nil, err
}
@@ -405,7 +311,7 @@ func edgeWriter(value interface{}, buffer *bytes.Buffer, typeSerializer *graphBi
}
// Not fully qualified
- _, err = typeSerializer.writeValue(e.Label, buffer, false)
+ _, err = typeSerializer.writeValue([1]string{e.Label}, buffer, false)
if err != nil {
return nil, err
}
@@ -417,7 +323,7 @@ func edgeWriter(value interface{}, buffer *bytes.Buffer, typeSerializer *graphBi
}
// Not fully qualified.
- _, err = typeSerializer.writeValue(e.InV.Label, buffer, false)
+ _, err = typeSerializer.writeValue([1]string{e.InV.Label}, buffer, false)
if err != nil {
return nil, err
}
@@ -428,7 +334,7 @@ func edgeWriter(value interface{}, buffer *bytes.Buffer, typeSerializer *graphBi
}
// Not fully qualified.
- _, err = typeSerializer.writeValue(e.OutV.Label, buffer, false)
+ _, err = typeSerializer.writeValue([1]string{e.OutV.Label}, buffer, false)
if err != nil {
return nil, err
}
@@ -467,7 +373,7 @@ func vertexPropertyWriter(value interface{}, buffer *bytes.Buffer, typeSerialize
}
// Not fully qualified.
- _, err = typeSerializer.writeValue(vp.Label, buffer, false)
+ _, err = typeSerializer.writeValue([1]string{vp.Label}, buffer, false)
if err != nil {
return nil, err
}
@@ -502,22 +408,12 @@ func setWriter(value interface{}, buffer *bytes.Buffer, typeSerializer *graphBin
return listWriter(slice, buffer, typeSerializer)
}
-func timeWriter(value interface{}, buffer *bytes.Buffer, _ *graphBinaryTypeSerializer) ([]byte, error) {
- t := value.(time.Time)
- err := binary.Write(buffer, binary.BigEndian, t.UnixMilli())
- if err != nil {
- return nil, err
- }
- return buffer.Bytes(), nil
-}
-
-func offsetDateTimeWriter(value interface{}, buffer *bytes.Buffer, _ *graphBinaryTypeSerializer) ([]byte, error) {
+func dateTimeWriter(value interface{}, buffer *bytes.Buffer, _ *graphBinaryTypeSerializer) ([]byte, error) {
t := value.(time.Time)
err := binary.Write(buffer, binary.BigEndian, int32(t.Year()))
if err != nil {
return nil, err
}
-
err = binary.Write(buffer, binary.BigEndian, byte(t.Month()))
if err != nil {
return nil, err
@@ -527,11 +423,11 @@ func offsetDateTimeWriter(value interface{}, buffer *bytes.Buffer, _ *graphBinar
return nil, err
}
// construct time of day in nanoseconds
- h := int64(t.Hour())
- m := int64(t.Minute())
- s := int64(t.Second())
- ns := (h * 60 * 60 * 1e9) + (m * 60 * 1e9) + (s * 1e9) + int64(t.Nanosecond())
- err = binary.Write(buffer, binary.BigEndian, ns)
+ h := t.Hour()
+ m := t.Minute()
+ s := t.Second()
+ ns := (h * 60 * 60 * 1e9) + (m * 60 * 1e9) + (s * 1e9) + t.Nanosecond()
+ err = binary.Write(buffer, binary.BigEndian, int64(ns))
if err != nil {
return nil, err
}
@@ -540,7 +436,6 @@ func offsetDateTimeWriter(value interface{}, buffer *bytes.Buffer, _ *graphBinar
if err != nil {
return nil, err
}
-
return buffer.Bytes(), nil
}
@@ -559,40 +454,15 @@ func durationWriter(value interface{}, buffer *bytes.Buffer, _ *graphBinaryTypeS
return buffer.Bytes(), nil
}
-const (
- valueFlagNull byte = 1
- valueFlagNone byte = 0
-)
-
func enumWriter(value interface{}, buffer *bytes.Buffer, typeSerializer *graphBinaryTypeSerializer) ([]byte, error) {
_, err := typeSerializer.write(reflect.ValueOf(value).String(), buffer)
return buffer.Bytes(), err
}
-// Format: {language}{script}{arguments_length}
-func lambdaWriter(value interface{}, buffer *bytes.Buffer, typeSerializer *graphBinaryTypeSerializer) ([]byte, error) {
- lambda := value.(*Lambda)
- if lambda.Language == "" {
- lambda.Language = "gremlin-groovy"
- }
- _, err := typeSerializer.writeValue(lambda.Language, buffer, false)
- if err != nil {
- return nil, err
- }
-
- _, err = typeSerializer.writeValue(lambda.Script, buffer, false)
- if err != nil {
- return nil, err
- }
-
- // It's hard to know how many parameters there are without extensive string parsing.
- // Instead, we can set -1 which means unknown.
- err = binary.Write(buffer, binary.BigEndian, int32(-1))
- if err != nil {
- return nil, err
- }
-
- return buffer.Bytes(), nil
+func markerWriter(value interface{}, buffer *bytes.Buffer, _ *graphBinaryTypeSerializer) ([]byte, error) {
+ m := value.(Marker)
+ err := binary.Write(buffer, binary.BigEndian, m.GetValue())
+ return buffer.Bytes(), err
}
// Format: {strategy_class}{configuration}
@@ -607,84 +477,13 @@ func traversalStrategyWriter(value interface{}, buffer *bytes.Buffer, typeSerial
return mapWriter(ts.configuration, buffer, typeSerializer)
}
-func pWriter(value interface{}, buffer *bytes.Buffer, typeSerializer *graphBinaryTypeSerializer) ([]byte, error) {
- var v p
- if reflect.TypeOf(value).Kind() == reflect.Ptr {
- v = *(value.(*p))
- } else {
- v = value.(p)
- }
- _, err := typeSerializer.writeValue(v.operator, buffer, false)
- if err != nil {
- return nil, err
- }
-
- err = binary.Write(buffer, binary.BigEndian, int32(len(v.values)))
- if err != nil {
- return nil, err
- }
-
- for _, pValue := range v.values {
- _, err := typeSerializer.write(pValue, buffer)
- if err != nil {
- return nil, err
- }
- }
- return buffer.Bytes(), err
-}
-
-func textPWriter(value interface{}, buffer *bytes.Buffer, typeSerializer *graphBinaryTypeSerializer) ([]byte, error) {
- var v textP
- if reflect.TypeOf(value).Kind() == reflect.Ptr {
- v = *(value.(*textP))
- } else {
- v = value.(textP)
- }
- _, err := typeSerializer.writeValue(v.operator, buffer, false)
- if err != nil {
- return nil, err
- }
-
- err = binary.Write(buffer, binary.BigEndian, int32(len(v.values)))
- if err != nil {
- return nil, err
- }
-
- for _, pValue := range v.values {
- _, err := typeSerializer.write(pValue, buffer)
- if err != nil {
- return nil, err
- }
- }
- return buffer.Bytes(), err
-}
-
-// Format: {key}{value}
-func bindingWriter(value interface{}, buffer *bytes.Buffer, typeSerializer *graphBinaryTypeSerializer) ([]byte, error) {
- var v Binding
- if reflect.TypeOf(value).Kind() == reflect.Ptr {
- v = *(value.(*Binding))
- } else {
- v = value.(Binding)
- }
-
- // Not fully qualified.
- _, err := typeSerializer.writeValue(v.Key, buffer, false)
- if err != nil {
- return nil, err
- }
-
- _, err = typeSerializer.write(v.Value, buffer)
- if err != nil {
- return nil, err
- }
- return buffer.Bytes(), nil
-}
+const (
+ valueFlagNull byte = 1
+ valueFlagNone byte = 0
+)
func (serializer *graphBinaryTypeSerializer) getType(val interface{}) (dataType, error) {
switch val.(type) {
- case *Bytecode, Bytecode, *GraphTraversal:
- return bytecodeType, nil
case string:
return stringType, nil
case uint, uint64, *big.Int:
@@ -713,58 +512,24 @@ func (serializer *graphBinaryTypeSerializer) getType(val interface{}) (dataType,
return propertyType, nil
case *VertexProperty:
return vertexPropertyType, nil
- case *Lambda:
- return lambdaType, nil
- case *traversalStrategy:
- return traversalStrategyType, nil
case *Path:
return pathType, nil
case Set:
return setType, nil
case time.Time:
- return offsetDateTimeType, nil // default serialization of Time will become OffsetDateTime
+ return datetimeType, nil
case time.Duration:
return durationType, nil
- case cardinality:
- return cardinalityType, nil
- case column:
- return columnType, nil
case direction:
return directionType, nil
- case operator:
- return operatorType, nil
- case order:
- return orderType, nil
- case pick:
- return pickType, nil
- case pop:
- return popType, nil
case t:
return tType, nil
- case barrier:
- return barrierType, nil
- case scope:
- return scopeType, nil
case merge:
return mergeType, nil
- case dt:
- return dtType, nil
case gType:
return gTypeType, nil
- case p, Predicate:
- return pType, nil
- case textP, TextPredicate:
- 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:
@@ -851,12 +616,12 @@ func (serializer *graphBinaryTypeSerializer) writeValueFlagNone(buffer *bytes.Bu
// readers
-func readTemp(data *[]byte, i *int, len int) *[]byte {
- tmp := make([]byte, len)
- for j := 0; j < len; j++ {
- tmp[j] = (*data)[j+*i]
+func readTemp(data *[]byte, i *int, length int) *[]byte {
+ if *i+length > len(*data) {
+ panic(ErrIncompleteData)
}
- *i += len
+ tmp := (*data)[*i : *i+length]
+ *i += length
return &tmp
}
@@ -893,21 +658,30 @@ func readLong(data *[]byte, i *int) (interface{}, error) {
}
func readBigInt(data *[]byte, i *int) (interface{}, error) {
- sz := readIntSafe(data, i)
- b := readTemp(data, i, int(sz))
+ // Length lookahead - check if complete BigInt is available
+ if *i+4 > len(*data) {
+ panic(ErrIncompleteData)
+ }
+ sz := int(binary.BigEndian.Uint32((*data)[*i : *i+4]))
+ if sz > 0 && *i+4+sz > len(*data) {
+ panic(ErrIncompleteData)
+ }
+ *i += 4
+ if sz == 0 {
+ return big.NewInt(0), nil
+ }
+ b := (*data)[*i : *i+sz]
+ *i += sz
- var newBigInt = big.NewInt(0).SetBytes(*b)
+ var newBigInt = big.NewInt(0).SetBytes(b)
var one = big.NewInt(1)
- if len(*b) == 0 {
- return newBigInt, nil
- }
// 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)
+ if b[0]&0x80 == 0x00 {
+ newBigInt.SetBytes(b)
return newBigInt, nil
}
// Undo two's complement to byte array and set negative boolean to true
- length := uint((len(*b)*8)/8+1) * 8
+ 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
@@ -947,12 +721,21 @@ func readDouble(data *[]byte, i *int) (interface{}, error) {
}
func readString(data *[]byte, i *int) (interface{}, error) {
- sz := int(readUint32Safe(data, i))
+ // Length lookahead - check if complete string is available
+ if *i+4 > len(*data) {
+ panic(ErrIncompleteData)
+ }
+ sz := int(binary.BigEndian.Uint32((*data)[*i : *i+4]))
+ if sz > 0 && *i+4+sz > len(*data) {
+ panic(ErrIncompleteData) // Don't advance index - wait for more data
+ }
+ *i += 4 // Now safe to advance past length
if sz == 0 {
return "", nil
}
+ result := string((*data)[*i : *i+sz])
*i += sz
- return string((*data)[*i-sz : *i]), nil
+ return result, nil
}
func readDataType(data *[]byte, i *int) dataType {
@@ -963,8 +746,6 @@ func getDefaultValue(dataType dataType) interface{} {
switch dataType {
case intType, bigIntegerType, longType, shortType, byteType, booleanType, floatType, doubleType:
return 0
- case traverserType, stringType:
- return ""
case uuidType:
return uuid.Nil
case vertexType:
@@ -979,7 +760,7 @@ func getDefaultValue(dataType dataType) interface{} {
return Path{}
case setType:
return SimpleSet{}
- case dateType, timestampType:
+ case datetimeType:
return time.Time{}
case durationType:
return time.Duration(0)
@@ -989,26 +770,46 @@ func getDefaultValue(dataType dataType) interface{} {
}
// Composite
-func readList(data *[]byte, i *int) (interface{}, error) {
+func readList(data *[]byte, i *int, flag byte) (interface{}, error) {
sz := readIntSafe(data, i)
var valList []interface{}
- for j := int32(0); j < sz; j++ {
- val, err := readFullyQualifiedNullable(data, i, true)
- if err != nil {
- return nil, err
+ if flag == 0x02 {
+ for j := int32(0); j < sz; j++ {
+ val, err := readFullyQualifiedNullable(data, i, true)
+ if err != nil {
+ return nil, err
+ }
+ bulk := readLongSafe(data, i)
+ for k := int64(0); k < bulk; k++ {
+ valList = append(valList, val)
+ }
+ }
+ } else {
+ for j := int32(0); j < sz; j++ {
+ val, err := readFullyQualifiedNullable(data, i, true)
+ if err != nil {
+ return nil, err
+ }
+ valList = append(valList, val)
}
- valList = append(valList, val)
}
return valList, nil
}
func readByteBuffer(data *[]byte, i *int) (interface{}, error) {
+ // Length lookahead - check if complete ByteBuffer is available
+ if *i+4 > len(*data) {
+ panic(ErrIncompleteData)
+ }
+ sz := int(binary.BigEndian.Uint32((*data)[*i : *i+4]))
+ if sz > 0 && *i+4+sz > len(*data) {
+ panic(ErrIncompleteData)
+ }
+ *i += 4
r := &ByteBuffer{}
- sz := readIntSafe(data, i)
r.Data = make([]byte, sz)
- for j := int32(0); j < sz; j++ {
- r.Data[j] = readByteSafe(data, i)
- }
+ copy(r.Data, (*data)[*i:*i+sz])
+ *i += sz
return r, nil
}
@@ -1064,8 +865,8 @@ func readMapUnqualified(data *[]byte, i *int) (interface{}, error) {
return mapData, nil
}
-func readSet(data *[]byte, i *int) (interface{}, error) {
- list, err := readList(data, i)
+func readSet(data *[]byte, i *int, flag byte) (interface{}, error) {
+ list, err := readList(data, i, flag)
if err != nil {
return nil, err
}
@@ -1073,15 +874,16 @@ func readSet(data *[]byte, i *int) (interface{}, error) {
}
func readUuid(data *[]byte, i *int) (interface{}, error) {
- id, _ := uuid.FromBytes(*readTemp(data, i, 16))
+ // Bounds check - UUID is fixed 16 bytes
+ if *i+16 > len(*data) {
+ panic(ErrIncompleteData)
+ }
+ id, _ := uuid.FromBytes((*data)[*i : *i+16])
+ *i += 16
return id, nil
}
-func timeReader(data *[]byte, i *int) (interface{}, error) {
- return time.UnixMilli(readLongSafe(data, i)), nil
-}
-
-func offsetDateTimeReader(data *[]byte, i *int) (interface{}, error) {
+func dateTimeReader(data *[]byte, i *int) (interface{}, error) {
year := readIntSafe(data, i)
month := readByteSafe(data, i)
day := readByteSafe(data, i)
@@ -1137,11 +939,11 @@ func vertexReaderReadingProperties(data *[]byte, i *int, readProperties bool) (i
if err != nil {
return nil, err
}
- label, err := readUnqualified(data, i, stringType, false)
+ label, err := readList(data, i, 0)
if err != nil {
return nil, err
}
- v.Label = label.(string)
+ v.Label = label.([]interface{})[0].(string)
if readProperties {
props, err := readFullyQualifiedNullable(data, i, true)
if err != nil {
@@ -1164,11 +966,11 @@ func edgeReader(data *[]byte, i *int) (interface{}, error) {
if err != nil {
return nil, err
}
- label, err := readUnqualified(data, i, stringType, false)
+ label, err := readList(data, i, 0)
if err != nil {
return nil, err
}
- e.Label = label.(string)
+ e.Label = label.([]interface{})[0].(string)
v, err := vertexReaderReadingProperties(data, i, false)
if err != nil {
return nil, err
@@ -1216,11 +1018,11 @@ func vertexPropertyReader(data *[]byte, i *int) (interface{}, error) {
if err != nil {
return nil, err
}
- label, err := readUnqualified(data, i, stringType, false)
+ label, err := readList(data, i, 0)
if err != nil {
return nil, err
}
- vp.Label = label.(string)
+ vp.Label = label.([]interface{})[0].(string)
vp.Value, err = readFullyQualifiedNullable(data, i, true)
if err != nil {
return nil, err
@@ -1260,35 +1062,6 @@ func pathReader(data *[]byte, i *int) (interface{}, error) {
return path, err
}
-// {bulk int}{fully qualified value}
-func traverserReader(data *[]byte, i *int) (interface{}, error) {
- var err error
- traverser := new(Traverser)
- traverser.bulk = readLongSafe(data, i)
- traverser.value, err = readFullyQualifiedNullable(data, i, true)
- if err != nil {
- return nil, err
- }
- return traverser, nil
-}
-
-// {int32 length}{fully qualified item_0}{int64 repetition_0}...{fully qualified item_n}{int64 repetition_n}
-func bulkSetReader(data *[]byte, i *int) (interface{}, error) {
- sz := int(readIntSafe(data, i))
- var valList []interface{}
- for j := 0; j < sz; j++ {
- val, err := readFullyQualifiedNullable(data, i, true)
- if err != nil {
- return nil, err
- }
- rep := readLongSafe(data, i)
- for k := 0; k < int(rep); k++ {
- valList = append(valList, val)
- }
- }
- return valList, nil
-}
-
// {type code (always string so ignore)}{nil code (always false so ignore)}{int32 size}{string enum}
func enumReader(data *[]byte, i *int) (interface{}, error) {
typeCode := readDataType(data, i)
@@ -1299,113 +1072,14 @@ func enumReader(data *[]byte, i *int) (interface{}, error) {
return readString(data, i)
}
-// {unqualified key}{fully qualified value}
-func bindingReader(data *[]byte, i *int) (interface{}, error) {
- b := new(Binding)
- val, err := readUnqualified(data, i, stringType, false)
+func markerReader(data *[]byte, i *int) (interface{}, error) {
+ m, err := Of(readByteSafe(data, i))
if err != nil {
return nil, err
}
- b.Key = val.(string)
-
- b.Value, err = readFullyQualifiedNullable(data, i, true)
- if err != nil {
- return nil, err
- }
- 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
@@ -1425,9 +1099,16 @@ func readFullyQualifiedNullable(data *[]byte, i *int, nullable bool) (interface{
}
return nil, nil
} else if nullable {
- if readByteSafe(data, i) == valueFlagNull {
+ flag := readByteSafe(data, i)
+ if flag == valueFlagNull {
return getDefaultValue(dataTyp), nil
}
+ if dataTyp == listType {
+ return readList(data, i, flag)
+ }
+ if dataTyp == setType {
+ return readSet(data, i, flag)
+ }
}
deserializer, ok := deserializers[dataTyp]
if !ok {
diff --git a/gremlin-go/driver/graphBinary_test.go b/gremlin-go/driver/graphBinary_test.go
index c47b327db4..eb44538fe1 100644
--- a/gremlin-go/driver/graphBinary_test.go
+++ b/gremlin-go/driver/graphBinary_test.go
@@ -23,15 +23,16 @@ import (
"bytes"
"encoding/binary"
"fmt"
- "github.com/stretchr/testify/assert"
- "golang.org/x/text/language"
"math/big"
"reflect"
"testing"
"time"
+
+ "github.com/stretchr/testify/assert"
+ "golang.org/x/text/language"
)
-func TestGraphBinaryV1(t *testing.T) {
+func TestGraphBinaryV4(t *testing.T) {
t.Run("graphBinaryTypeSerializer tests", func(t *testing.T) {
serializer := graphBinaryTypeSerializer{newLogHandler(&defaultLogger{}, Error, language.English)}
@@ -83,16 +84,6 @@ 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
@@ -198,7 +189,7 @@ func TestGraphBinaryV1(t *testing.T) {
source := []interface{}{int32(111), "str"}
buf, err := listWriter(source, &buffer, nil)
assert.Nil(t, err)
- res, err := readList(&buf, &pos)
+ res, err := readList(&buf, &pos, 0x00)
assert.Nil(t, err)
assert.Equal(t, source, res)
})
@@ -218,7 +209,7 @@ func TestGraphBinaryV1(t *testing.T) {
source := NewSimpleSet(int32(111), "str")
buf, err := setWriter(source, &buffer, nil)
assert.Nil(t, err)
- res, err := readSet(&buf, &pos)
+ res, err := readSet(&buf, &pos, 0x00)
assert.Nil(t, err)
assert.Equal(t, source, res)
})
@@ -306,19 +297,20 @@ func TestGraphBinaryV1(t *testing.T) {
pos := 0
var buffer bytes.Buffer
source := time.Date(2022, 5, 10, 9, 51, 0, 0, time.Local)
- buf, err := timeWriter(source, &buffer, nil)
+ buf, err := dateTimeWriter(source, &buffer, nil)
assert.Nil(t, err)
- res, err := timeReader(&buf, &pos)
+ res, err := dateTimeReader(&buf, &pos)
assert.Nil(t, err)
- assert.Equal(t, source, res)
+ // ISO format
+ assert.Equal(t, source.Format(time.RFC3339Nano), res.(time.Time).Format(time.RFC3339Nano))
})
t.Run("read-write local datetime", func(t *testing.T) {
pos := 0
var buffer bytes.Buffer
source := time.Date(2022, 5, 10, 9, 51, 0, 0, time.Local)
- buf, err := offsetDateTimeWriter(source, &buffer, nil)
+ buf, err := dateTimeWriter(source, &buffer, nil)
assert.Nil(t, err)
- res, err := offsetDateTimeReader(&buf, &pos)
+ res, err := dateTimeReader(&buf, &pos)
assert.Nil(t, err)
// ISO format
assert.Equal(t, source.Format(time.RFC3339Nano), res.(time.Time).Format(time.RFC3339Nano))
@@ -327,9 +319,9 @@ func TestGraphBinaryV1(t *testing.T) {
pos := 0
var buffer bytes.Buffer
source := time.Date(2022, 5, 10, 9, 51, 0, 0, time.UTC)
- buf, err := offsetDateTimeWriter(source, &buffer, nil)
+ buf, err := dateTimeWriter(source, &buffer, nil)
assert.Nil(t, err)
- res, err := offsetDateTimeReader(&buf, &pos)
+ res, err := dateTimeReader(&buf, &pos)
assert.Nil(t, err)
// ISO format
assert.Equal(t, source.Format(time.RFC3339Nano), res.(time.Time).Format(time.RFC3339Nano))
@@ -338,9 +330,9 @@ func TestGraphBinaryV1(t *testing.T) {
pos := 0
var buffer bytes.Buffer
source := time.Date(2022, 5, 10, 9, 51, 34, 123456789, GetTimezoneFromOffset(-36000))
- buf, err := offsetDateTimeWriter(source, &buffer, nil)
+ buf, err := dateTimeWriter(source, &buffer, nil)
assert.Nil(t, err)
- res, err := offsetDateTimeReader(&buf, &pos)
+ res, err := dateTimeReader(&buf, &pos)
assert.Nil(t, err)
// ISO format
assert.Equal(t, source.Format(time.RFC3339Nano), res.(time.Time).Format(time.RFC3339Nano))
@@ -356,4 +348,16 @@ func TestGraphBinaryV1(t *testing.T) {
assert.Equal(t, newError(err0703ReadMapNonStringKeyError, intType), err)
})
})
+
+ t.Run("read-write marker", func(t *testing.T) {
+ pos := 0
+ var buffer bytes.Buffer
+ source := EndOfStream()
+ buf, err := markerWriter(source, &buffer, nil)
+ assert.Nil(t, err)
+ res, err := markerReader(&buf, &pos)
+ assert.Nil(t, err)
+ assert.Equal(t, source, res)
+ })
+
}
diff --git a/gremlin-go/driver/graphTraversal.go b/gremlin-go/driver/graphTraversal.go
index dc136bef82..8db90e6b0c 100644
--- a/gremlin-go/driver/graphTraversal.go
+++ b/gremlin-go/driver/graphTraversal.go
@@ -36,12 +36,15 @@ type GraphTraversal struct {
// NewGraphTraversal make a new GraphTraversal.
// why is this taking a non exported field as an exported function - remove or replace?
-func NewGraphTraversal(graph *Graph, bytecode *Bytecode, remote *DriverRemoteConnection) *GraphTraversal {
+func NewGraphTraversal(graph *Graph, gremlinLang *GremlinLang, remote *DriverRemoteConnection) *GraphTraversal {
+ if gremlinLang == nil {
+ gremlinLang = NewGremlinLang(nil)
+ }
gt := &GraphTraversal{
Traversal: &Traversal{
- graph: graph,
- Bytecode: bytecode,
- remote: remote,
+ graph: graph,
+ GremlinLang: gremlinLang,
+ remote: remote,
},
}
return gt
@@ -49,7 +52,7 @@ func NewGraphTraversal(graph *Graph, bytecode *Bytecode, remote *DriverRemoteCon
// Clone make a copy of a traversal that is reset for iteration.
func (g *GraphTraversal) Clone() *GraphTraversal {
- return NewGraphTraversal(g.graph, NewBytecode(g.Bytecode), g.remote)
+ return NewGraphTraversal(g.graph, NewGremlinLang(g.GremlinLang), g.remote)
}
// V adds the v step to the GraphTraversal.
@@ -59,279 +62,279 @@ func (g *GraphTraversal) V(args ...interface{}) *GraphTraversal {
args[i] = v.Id
}
}
- g.Bytecode.AddStep("V", args...)
+ g.GremlinLang.AddStep("V", args...)
return g
}
// E adds the e step to the GraphTraversal.
func (g *GraphTraversal) E(args ...interface{}) *GraphTraversal {
- g.Bytecode.AddStep("E", args...)
+ g.GremlinLang.AddStep("E", args...)
return g
}
// AddE adds the addE step to the GraphTraversal.
func (g *GraphTraversal) AddE(args ...interface{}) *GraphTraversal {
- g.Bytecode.AddStep("addE", args...)
+ g.GremlinLang.AddStep("addE", args...)
return g
}
// AddV adds the addV step to the GraphTraversal.
func (g *GraphTraversal) AddV(args ...interface{}) *GraphTraversal {
- g.Bytecode.AddStep("addV", args...)
+ g.GremlinLang.AddStep("addV", args...)
return g
}
// Aggregate adds the aggregate step to the GraphTraversal.
func (g *GraphTraversal) Aggregate(args ...interface{}) *GraphTraversal {
- g.Bytecode.AddStep("aggregate", args...)
+ g.GremlinLang.AddStep("aggregate", args...)
return g
}
// All adds the all step to the GraphTraversal.
func (g *GraphTraversal) All(args ...interface{}) *GraphTraversal {
- g.Bytecode.AddStep("all", args...)
+ g.GremlinLang.AddStep("all", args...)
return g
}
// And adds the and step to the GraphTraversal.
func (g *GraphTraversal) And(args ...interface{}) *GraphTraversal {
- g.Bytecode.AddStep("and", args...)
+ g.GremlinLang.AddStep("and", args...)
return g
}
// Any adds the any step to the GraphTraversal.
func (g *GraphTraversal) Any(args ...interface{}) *GraphTraversal {
- g.Bytecode.AddStep("any", args...)
+ g.GremlinLang.AddStep("any", args...)
return g
}
// As adds the as step to the GraphTraversal.
func (g *GraphTraversal) As(args ...interface{}) *GraphTraversal {
- g.Bytecode.AddStep("as", args...)
+ g.GremlinLang.AddStep("as", args...)
return g
}
// AsBool adds the asBool step to the GraphTraversal.
func (g *GraphTraversal) AsBool(args ...interface{}) *GraphTraversal {
- g.Bytecode.AddStep("asBool", args...)
+ g.GremlinLang.AddStep("asBool", args...)
return g
}
// AsDate adds the asDate step to the GraphTraversal.
func (g *GraphTraversal) AsDate(args ...interface{}) *GraphTraversal {
- g.Bytecode.AddStep("asDate", args...)
+ g.GremlinLang.AddStep("asDate", args...)
return g
}
// AsNumber adds the asNumber step to the GraphTraversal.
func (g *GraphTraversal) AsNumber(args ...interface{}) *GraphTraversal {
- g.Bytecode.AddStep("asNumber", args...)
+ g.GremlinLang.AddStep("asNumber", args...)
return g
}
// AsString adds the asString step to the GraphTraversal.
func (g *GraphTraversal) AsString(args ...interface{}) *GraphTraversal {
- g.Bytecode.AddStep("asString", args...)
+ g.GremlinLang.AddStep("asString", args...)
return g
}
// Barrier adds the barrier step to the GraphTraversal.
func (g *GraphTraversal) Barrier(args ...interface{}) *GraphTraversal {
// Force int32 serialization for valid number values for server compatibility
- g.Bytecode.AddStep("barrier", int32Args(args)...)
+ g.GremlinLang.AddStep("barrier", int32Args(args)...)
return g
}
// Both adds the both step to the GraphTraversal.
func (g *GraphTraversal) Both(args ...interface{}) *GraphTraversal {
- g.Bytecode.AddStep("both", args...)
+ g.GremlinLang.AddStep("both", args...)
return g
}
// BothE adds the bothE step to the GraphTraversal.
func (g *GraphTraversal) BothE(args ...interface{}) *GraphTraversal {
- g.Bytecode.AddStep("bothE", args...)
+ g.GremlinLang.AddStep("bothE", args...)
return g
}
// BothV adds the bothV step to the GraphTraversal.
func (g *GraphTraversal) BothV(args ...interface{}) *GraphTraversal {
- g.Bytecode.AddStep("bothV", args...)
+ g.GremlinLang.AddStep("bothV", args...)
return g
}
// Branch adds the branch step to the GraphTraversal.
func (g *GraphTraversal) Branch(args ...interface{}) *GraphTraversal {
- g.Bytecode.AddStep("branch", args...)
+ g.GremlinLang.AddStep("branch", args...)
return g
}
// By adds the by step to the GraphTraversal.
func (g *GraphTraversal) By(args ...interface{}) *GraphTraversal {
- g.Bytecode.AddStep("by", args...)
+ g.GremlinLang.AddStep("by", args...)
return g
}
// Call adds the call step to the GraphTraversal.
func (g *GraphTraversal) Call(args ...interface{}) *GraphTraversal {
- g.Bytecode.AddStep("call", args...)
+ g.GremlinLang.AddStep("call", args...)
return g
}
// Cap adds the cap step to the GraphTraversal.
func (g *GraphTraversal) Cap(args ...interface{}) *GraphTraversal {
- g.Bytecode.AddStep("cap", args...)
+ g.GremlinLang.AddStep("cap", args...)
return g
}
// Choose adds the choose step to the GraphTraversal.
func (g *GraphTraversal) Choose(args ...interface{}) *GraphTraversal {
- g.Bytecode.AddStep("choose", args...)
+ g.GremlinLang.AddStep("choose", args...)
return g
}
// Coalesce adds the coalesce step to the GraphTraversal.
func (g *GraphTraversal) Coalesce(args ...interface{}) *GraphTraversal {
- g.Bytecode.AddStep("coalesce", args...)
+ g.GremlinLang.AddStep("coalesce", args...)
return g
}
// Coin adds the coint step to the GraphTraversal.
func (g *GraphTraversal) Coin(args ...interface{}) *GraphTraversal {
- g.Bytecode.AddStep("coin", args...)
+ g.GremlinLang.AddStep("coin", args...)
return g
}
// Combine adds the combine step to the GraphTraversal.
func (g *GraphTraversal) Combine(args ...interface{}) *GraphTraversal {
- g.Bytecode.AddStep("combine", args...)
+ g.GremlinLang.AddStep("combine", args...)
return g
}
// Concat adds the concat step to the GraphTraversal.
func (g *GraphTraversal) Concat(args ...interface{}) *GraphTraversal {
- g.Bytecode.AddStep("concat", args...)
+ g.GremlinLang.AddStep("concat", args...)
return g
}
// Conjoin adds the conjoin step to the GraphTraversal.
func (g *GraphTraversal) Conjoin(args ...interface{}) *GraphTraversal {
- g.Bytecode.AddStep("conjoin", args...)
+ g.GremlinLang.AddStep("conjoin", args...)
return g
}
// ConnectedComponent adds the connectedComponent step to the GraphTraversal.
func (g *GraphTraversal) ConnectedComponent(args ...interface{}) *GraphTraversal {
- g.Bytecode.AddStep("connectedComponent", args...)
+ g.GremlinLang.AddStep("connectedComponent", args...)
return g
}
// Constant adds the constant step to the GraphTraversal.
func (g *GraphTraversal) Constant(args ...interface{}) *GraphTraversal {
// Force int32 serialization for valid number values for server compatibility
- g.Bytecode.AddStep("constant", int32Args(args)...)
+ g.GremlinLang.AddStep("constant", int32Args(args)...)
return g
}
// Count adds the count step to the GraphTraversal.
func (g *GraphTraversal) Count(args ...interface{}) *GraphTraversal {
- g.Bytecode.AddStep("count", args...)
+ g.GremlinLang.AddStep("count", args...)
return g
}
// CyclicPath adds the cyclicPath step to the GraphTraversal.
func (g *GraphTraversal) CyclicPath(args ...interface{}) *GraphTraversal {
- g.Bytecode.AddStep("cyclicPath", args...)
+ g.GremlinLang.AddStep("cyclicPath", args...)
return g
}
// DateAdd adds the dateAdd step to the GraphTraversal.
func (g *GraphTraversal) DateAdd(args ...interface{}) *GraphTraversal {
- g.Bytecode.AddStep("dateAdd", int32Args(args)...)
+ g.GremlinLang.AddStep("dateAdd", int32Args(args)...)
return g
}
// DateDiff adds the dateDiff step to the GraphTraversal.
func (g *GraphTraversal) DateDiff(args ...interface{}) *GraphTraversal {
- g.Bytecode.AddStep("dateDiff", args...)
+ g.GremlinLang.AddStep("dateDiff", args...)
return g
}
// Dedup adds the dedup step to the GraphTraversal.
func (g *GraphTraversal) Dedup(args ...interface{}) *GraphTraversal {
- g.Bytecode.AddStep("dedup", args...)
+ g.GremlinLang.AddStep("dedup", args...)
return g
}
// Difference adds the difference step to the GraphTraversal.
func (g *GraphTraversal) Difference(args ...interface{}) *GraphTraversal {
- g.Bytecode.AddStep("difference", args...)
+ g.GremlinLang.AddStep("difference", args...)
return g
}
// Discard adds the discard step to the GraphTraversal.
func (g *GraphTraversal) Discard(args ...interface{}) *GraphTraversal {
- g.Bytecode.AddStep("discard", args...)
+ g.GremlinLang.AddStep("discard", args...)
return g
}
// Disjunct adds the disjunct step to the GraphTraversal.
func (g *GraphTraversal) Disjunct(args ...interface{}) *GraphTraversal {
- g.Bytecode.AddStep("disjunct", args...)
+ g.GremlinLang.AddStep("disjunct", args...)
return g
}
// Drop adds the drop step to the GraphTraversal.
func (g *GraphTraversal) Drop(args ...interface{}) *GraphTraversal {
- g.Bytecode.AddStep("drop", args...)
+ g.GremlinLang.AddStep("drop", args...)
return g
}
// Element adds the element step to the GraphTraversal.
func (g *GraphTraversal) Element(args ...interface{}) *GraphTraversal {
- g.Bytecode.AddStep("element", args...)
+ g.GremlinLang.AddStep("element", args...)
return g
}
// ElementMap adds the elementMap step to the GraphTraversal.
func (g *GraphTraversal) ElementMap(args ...interface{}) *GraphTraversal {
- g.Bytecode.AddStep("elementMap", args...)
+ g.GremlinLang.AddStep("elementMap", args...)
return g
}
// Emit adds the emit step to the GraphTraversal.
func (g *GraphTraversal) Emit(args ...interface{}) *GraphTraversal {
- g.Bytecode.AddStep("emit", args...)
+ g.GremlinLang.AddStep("emit", args...)
return g
}
// Fail adds the fail step to the GraphTraversal.
func (g *GraphTraversal) Fail(args ...interface{}) *GraphTraversal {
- g.Bytecode.AddStep("fail", args...)
+ g.GremlinLang.AddStep("fail", args...)
return g
}
// Filter adds the filter step to the GraphTraversal.
func (g *GraphTraversal) Filter(args ...interface{}) *GraphTraversal {
- g.Bytecode.AddStep("filter", args...)
+ g.GremlinLang.AddStep("filter", args...)
return g
}
// FlatMap adds the flatMap step to the GraphTraversal.
func (g *GraphTraversal) FlatMap(args ...interface{}) *GraphTraversal {
- g.Bytecode.AddStep("flatMap", args...)
+ g.GremlinLang.AddStep("flatMap", args...)
return g
}
// Fold adds the fold step to the GraphTraversal.
func (g *GraphTraversal) Fold(args ...interface{}) *GraphTraversal {
- g.Bytecode.AddStep("fold", args...)
+ g.GremlinLang.AddStep("fold", args...)
return g
}
// Format adds the format step to the GraphTraversal.
func (g *GraphTraversal) Format(args ...interface{}) *GraphTraversal {
- g.Bytecode.AddStep("format", args...)
+ g.GremlinLang.AddStep("format", args...)
return g
}
@@ -339,192 +342,192 @@ func (g *GraphTraversal) Format(args ...interface{}) *GraphTraversal {
func (g *GraphTraversal) From(args ...interface{}) *GraphTraversal {
for i := 0; i < len(args); i++ {
if v, ok := args[i].(*Vertex); ok {
- args[i] = v.Id
+ args[i] = T__.V(v.Id)
}
}
- g.Bytecode.AddStep("from", args...)
+ g.GremlinLang.AddStep("from", args...)
return g
}
// Group adds the group step to the GraphTraversal.
func (g *GraphTraversal) Group(args ...interface{}) *GraphTraversal {
- g.Bytecode.AddStep("group", args...)
+ g.GremlinLang.AddStep("group", args...)
return g
}
// GroupCount adds the groupCount step to the GraphTraversal.
func (g *GraphTraversal) GroupCount(args ...interface{}) *GraphTraversal {
- g.Bytecode.AddStep("groupCount", args...)
+ g.GremlinLang.AddStep("groupCount", args...)
return g
}
// Has adds the has step to the GraphTraversal.
func (g *GraphTraversal) Has(args ...interface{}) *GraphTraversal {
- g.Bytecode.AddStep("has", args...)
+ g.GremlinLang.AddStep("has", args...)
return g
}
// HasId adds the hasId step to the GraphTraversal.
func (g *GraphTraversal) HasId(args ...interface{}) *GraphTraversal {
- g.Bytecode.AddStep("hasId", args...)
+ g.GremlinLang.AddStep("hasId", args...)
return g
}
// HasKey adds the hasKey step to the GraphTraversal.
func (g *GraphTraversal) HasKey(args ...interface{}) *GraphTraversal {
- g.Bytecode.AddStep("hasKey", args...)
+ g.GremlinLang.AddStep("hasKey", args...)
return g
}
// HasLabel adds the hasLabel step to the GraphTraversal.
func (g *GraphTraversal) HasLabel(args ...interface{}) *GraphTraversal {
- g.Bytecode.AddStep("hasLabel", args...)
+ g.GremlinLang.AddStep("hasLabel", args...)
return g
}
// HasNot adds the hasNot step to the GraphTraversal.
func (g *GraphTraversal) HasNot(args ...interface{}) *GraphTraversal {
- g.Bytecode.AddStep("hasNot", args...)
+ g.GremlinLang.AddStep("hasNot", args...)
return g
}
// HasValue adds the hasValue step to the GraphTraversal.
func (g *GraphTraversal) HasValue(args ...interface{}) *GraphTraversal {
- g.Bytecode.AddStep("hasValue", args...)
+ g.GremlinLang.AddStep("hasValue", args...)
return g
}
// Id adds the id step to the GraphTraversal.
func (g *GraphTraversal) Id(args ...interface{}) *GraphTraversal {
- g.Bytecode.AddStep("id", args...)
+ g.GremlinLang.AddStep("id", args...)
return g
}
// Identity adds the identity step to the GraphTraversal.
func (g *GraphTraversal) Identity(args ...interface{}) *GraphTraversal {
- g.Bytecode.AddStep("identity", args...)
+ g.GremlinLang.AddStep("identity", args...)
return g
}
// InE adds the inE step to the GraphTraversal.
func (g *GraphTraversal) InE(args ...interface{}) *GraphTraversal {
- g.Bytecode.AddStep("inE", args...)
+ g.GremlinLang.AddStep("inE", args...)
return g
}
// InV adds the inV step to the GraphTraversal.
func (g *GraphTraversal) InV(args ...interface{}) *GraphTraversal {
- g.Bytecode.AddStep("inV", args...)
+ g.GremlinLang.AddStep("inV", args...)
return g
}
// In adds the in step to the GraphTraversal.
func (g *GraphTraversal) In(args ...interface{}) *GraphTraversal {
- g.Bytecode.AddStep("in", args...)
+ g.GremlinLang.AddStep("in", args...)
return g
}
// Index adds the index step to the GraphTraversal.
func (g *GraphTraversal) Index(args ...interface{}) *GraphTraversal {
- g.Bytecode.AddStep("index", args...)
+ g.GremlinLang.AddStep("index", args...)
return g
}
// Inject adds the inject step to the GraphTraversal.
func (g *GraphTraversal) Inject(args ...interface{}) *GraphTraversal {
// Force int32 serialization for valid number values for server compatibility
- g.Bytecode.AddStep("inject", int32Args(args)...)
+ g.GremlinLang.AddStep("inject", int32Args(args)...)
return g
}
// Intersect adds the intersect step to the GraphTraversal.
func (g *GraphTraversal) Intersect(args ...interface{}) *GraphTraversal {
// Force int32 serialization for valid number values for server compatibility
- g.Bytecode.AddStep("intersect", args...)
+ g.GremlinLang.AddStep("intersect", args...)
return g
}
// Is adds the is step to the GraphTraversal.
func (g *GraphTraversal) Is(args ...interface{}) *GraphTraversal {
- g.Bytecode.AddStep("is", args...)
+ g.GremlinLang.AddStep("is", args...)
return g
}
// Key adds the key step to the GraphTraversal.
func (g *GraphTraversal) Key(args ...interface{}) *GraphTraversal {
- g.Bytecode.AddStep("key", args...)
+ g.GremlinLang.AddStep("key", args...)
return g
}
// Label adds the label step to the GraphTraversal.
func (g *GraphTraversal) Label(args ...interface{}) *GraphTraversal {
- g.Bytecode.AddStep("label", args...)
+ g.GremlinLang.AddStep("label", args...)
return g
}
// Length adds the length step to the GraphTraversal.
func (g *GraphTraversal) Length(args ...interface{}) *GraphTraversal {
- g.Bytecode.AddStep("length", args...)
+ g.GremlinLang.AddStep("length", args...)
return g
}
// Limit adds the limit step to the GraphTraversal.
func (g *GraphTraversal) Limit(args ...interface{}) *GraphTraversal {
- g.Bytecode.AddStep("limit", args...)
+ g.GremlinLang.AddStep("limit", args...)
return g
}
// Local adds the local step to the GraphTraversal.
func (g *GraphTraversal) Local(args ...interface{}) *GraphTraversal {
- g.Bytecode.AddStep("local", args...)
+ g.GremlinLang.AddStep("local", args...)
return g
}
// Loops adds the loops step to the GraphTraversal.
func (g *GraphTraversal) Loops(args ...interface{}) *GraphTraversal {
- g.Bytecode.AddStep("loops", args...)
+ g.GremlinLang.AddStep("loops", args...)
return g
}
// LTrim adds the lTrim step to the GraphTraversal.
func (g *GraphTraversal) LTrim(args ...interface{}) *GraphTraversal {
- g.Bytecode.AddStep("lTrim", args...)
+ g.GremlinLang.AddStep("lTrim", args...)
return g
}
// Map adds the map step to the GraphTraversal.
func (g *GraphTraversal) Map(args ...interface{}) *GraphTraversal {
- g.Bytecode.AddStep("map", args...)
+ g.GremlinLang.AddStep("map", args...)
return g
}
// Match adds the match step to the GraphTraversal.
func (g *GraphTraversal) Match(args ...interface{}) *GraphTraversal {
- g.Bytecode.AddStep("match", args...)
+ g.GremlinLang.AddStep("match", args...)
return g
}
// Math adds the math step to the GraphTraversal.
func (g *GraphTraversal) Math(args ...interface{}) *GraphTraversal {
- g.Bytecode.AddStep("math", args...)
+ g.GremlinLang.AddStep("math", args...)
return g
}
// Max adds the max step to the GraphTraversal.
func (g *GraphTraversal) Max(args ...interface{}) *GraphTraversal {
- g.Bytecode.AddStep("max", args...)
+ g.GremlinLang.AddStep("max", args...)
return g
}
// Mean adds the mean step to the GraphTraversal.
func (g *GraphTraversal) Mean(args ...interface{}) *GraphTraversal {
- g.Bytecode.AddStep("mean", args...)
+ g.GremlinLang.AddStep("mean", args...)
return g
}
// Merge adds the merge step to the GraphTraversal.
func (g *GraphTraversal) Merge(args ...interface{}) *GraphTraversal {
- g.Bytecode.AddStep("merge", args...)
+ g.GremlinLang.AddStep("merge", args...)
return g
}
@@ -540,261 +543,261 @@ func (g *GraphTraversal) MergeE(args ...interface{}) *GraphTraversal {
}
}
}
- g.Bytecode.AddStep("mergeE", args...)
+ g.GremlinLang.AddStep("mergeE", args...)
return g
}
// MergeV adds the mergeE step to the GraphTraversal.
func (g *GraphTraversal) MergeV(args ...interface{}) *GraphTraversal {
- g.Bytecode.AddStep("mergeV", args...)
+ g.GremlinLang.AddStep("mergeV", args...)
return g
}
// Min adds the min step to the GraphTraversal.
func (g *GraphTraversal) Min(args ...interface{}) *GraphTraversal {
- g.Bytecode.AddStep("min", args...)
+ g.GremlinLang.AddStep("min", args...)
return g
}
// None adds the none step to the GraphTraversal.
func (g *GraphTraversal) None(args ...interface{}) *GraphTraversal {
- g.Bytecode.AddStep("none", args...)
+ g.GremlinLang.AddStep("none", args...)
return g
}
// Not adds the not step to the GraphTraversal.
func (g *GraphTraversal) Not(args ...interface{}) *GraphTraversal {
- g.Bytecode.AddStep("not", args...)
+ g.GremlinLang.AddStep("not", args...)
return g
}
// Option adds the option step to the GraphTraversal.
func (g *GraphTraversal) Option(args ...interface{}) *GraphTraversal {
- g.Bytecode.AddStep("option", args...)
+ g.GremlinLang.AddStep("option", args...)
return g
}
// Optional adds the optional step to the GraphTraversal.
func (g *GraphTraversal) Optional(args ...interface{}) *GraphTraversal {
- g.Bytecode.AddStep("optional", args...)
+ g.GremlinLang.AddStep("optional", args...)
return g
}
// Or adds the or step to the GraphTraversal.
func (g *GraphTraversal) Or(args ...interface{}) *GraphTraversal {
- g.Bytecode.AddStep("or", args...)
+ g.GremlinLang.AddStep("or", args...)
return g
}
// Order adds the order step to the GraphTraversal.
func (g *GraphTraversal) Order(args ...interface{}) *GraphTraversal {
- g.Bytecode.AddStep("order", args...)
+ g.GremlinLang.AddStep("order", args...)
return g
}
// OtherV adds the otherV step to the GraphTraversal.
func (g *GraphTraversal) OtherV(args ...interface{}) *GraphTraversal {
- g.Bytecode.AddStep("otherV", args...)
+ g.GremlinLang.AddStep("otherV", args...)
return g
}
// Out adds the out step to the GraphTraversal.
func (g *GraphTraversal) Out(args ...interface{}) *GraphTraversal {
- g.Bytecode.AddStep("out", args...)
+ g.GremlinLang.AddStep("out", args...)
return g
}
// OutE adds the outE step to the GraphTraversal.
func (g *GraphTraversal) OutE(args ...interface{}) *GraphTraversal {
- g.Bytecode.AddStep("outE", args...)
+ g.GremlinLang.AddStep("outE", args...)
return g
}
// OutV adds the outV step to the GraphTraversal.
func (g *GraphTraversal) OutV(args ...interface{}) *GraphTraversal {
- g.Bytecode.AddStep("outV", args...)
+ g.GremlinLang.AddStep("outV", args...)
return g
}
// PageRank adds the pageRank step to the GraphTraversal.
func (g *GraphTraversal) PageRank(args ...interface{}) *GraphTraversal {
- g.Bytecode.AddStep("pageRank", args...)
+ g.GremlinLang.AddStep("pageRank", args...)
return g
}
// Path adds the path step to the GraphTraversal.
func (g *GraphTraversal) Path(args ...interface{}) *GraphTraversal {
- g.Bytecode.AddStep("path", args...)
+ g.GremlinLang.AddStep("path", args...)
return g
}
// PeerPressure adds the peerPressure step to the GraphTraversal.
func (g *GraphTraversal) PeerPressure(args ...interface{}) *GraphTraversal {
- g.Bytecode.AddStep("peerPressure", args...)
+ g.GremlinLang.AddStep("peerPressure", args...)
return g
}
// Product adds the product step to the GraphTraversal.
func (g *GraphTraversal) Product(args ...interface{}) *GraphTraversal {
- g.Bytecode.AddStep("product", args...)
+ g.GremlinLang.AddStep("product", args...)
return g
}
// Profile adds the profile step to the GraphTraversal.
func (g *GraphTraversal) Profile(args ...interface{}) *GraphTraversal {
- g.Bytecode.AddStep("profile", args...)
+ g.GremlinLang.AddStep("profile", args...)
return g
}
// Program adds the program step to the GraphTraversal.
func (g *GraphTraversal) Program(args ...interface{}) *GraphTraversal {
- g.Bytecode.AddStep("program", args...)
+ g.GremlinLang.AddStep("program", args...)
return g
}
// Project adds the project step to the GraphTraversal.
func (g *GraphTraversal) Project(args ...interface{}) *GraphTraversal {
- g.Bytecode.AddStep("project", args...)
+ g.GremlinLang.AddStep("project", args...)
return g
}
// Properties adds the properties step to the GraphTraversal.
func (g *GraphTraversal) Properties(args ...interface{}) *GraphTraversal {
- g.Bytecode.AddStep("properties", args...)
+ g.GremlinLang.AddStep("properties", args...)
return g
}
// Property adds the property step to the GraphTraversal.
func (g *GraphTraversal) Property(args ...interface{}) *GraphTraversal {
- g.Bytecode.AddStep("property", args...)
+ g.GremlinLang.AddStep("property", args...)
return g
}
// PropertyMap adds the propertyMap step to the GraphTraversal.
func (g *GraphTraversal) PropertyMap(args ...interface{}) *GraphTraversal {
- g.Bytecode.AddStep("propertyMap", args...)
+ g.GremlinLang.AddStep("propertyMap", args...)
return g
}
// Range adds the range step to the GraphTraversal.
func (g *GraphTraversal) Range(args ...interface{}) *GraphTraversal {
- g.Bytecode.AddStep("range", args...)
+ g.GremlinLang.AddStep("range", args...)
return g
}
// Read adds the read step to the GraphTraversal.
func (g *GraphTraversal) Read(args ...interface{}) *GraphTraversal {
- g.Bytecode.AddStep("read", args...)
+ g.GremlinLang.AddStep("read", args...)
return g
}
// Repeat adds the repeat step to the GraphTraversal.
func (g *GraphTraversal) Repeat(args ...interface{}) *GraphTraversal {
- g.Bytecode.AddStep("repeat", args...)
+ g.GremlinLang.AddStep("repeat", args...)
return g
}
// Replace adds the replace step to the GraphTraversal.
func (g *GraphTraversal) Replace(args ...interface{}) *GraphTraversal {
- g.Bytecode.AddStep("replace", args...)
+ g.GremlinLang.AddStep("replace", args...)
return g
}
// Reverse adds the reverse step to the GraphTraversal.
func (g *GraphTraversal) Reverse(args ...interface{}) *GraphTraversal {
- g.Bytecode.AddStep("reverse", args...)
+ g.GremlinLang.AddStep("reverse", args...)
return g
}
// RTrim adds the repeat step to the GraphTraversal.
func (g *GraphTraversal) RTrim(args ...interface{}) *GraphTraversal {
- g.Bytecode.AddStep("rTrim", args...)
+ g.GremlinLang.AddStep("rTrim", args...)
return g
}
// Sack adds the sack step to the GraphTraversal.
func (g *GraphTraversal) Sack(args ...interface{}) *GraphTraversal {
- g.Bytecode.AddStep("sack", args...)
+ g.GremlinLang.AddStep("sack", args...)
return g
}
// Sample adds the sample step to the GraphTraversal.
func (g *GraphTraversal) Sample(args ...interface{}) *GraphTraversal {
// Force int32 serialization for valid number values for server compatibility
- g.Bytecode.AddStep("sample", int32Args(args)...)
+ g.GremlinLang.AddStep("sample", int32Args(args)...)
return g
}
// Select adds the select step to the GraphTraversal.
func (g *GraphTraversal) Select(args ...interface{}) *GraphTraversal {
- g.Bytecode.AddStep("select", args...)
+ g.GremlinLang.AddStep("select", args...)
return g
}
// ShortestPath adds the shortestPath step to the GraphTraversal.
func (g *GraphTraversal) ShortestPath(args ...interface{}) *GraphTraversal {
- g.Bytecode.AddStep("shortestPath", args...)
+ g.GremlinLang.AddStep("shortestPath", args...)
return g
}
// SideEffect adds the sideEffect step to the GraphTraversal.
func (g *GraphTraversal) SideEffect(args ...interface{}) *GraphTraversal {
- g.Bytecode.AddStep("sideEffect", args...)
+ g.GremlinLang.AddStep("sideEffect", args...)
return g
}
// SimplePath adds the simplePath step to the GraphTraversal.
func (g *GraphTraversal) SimplePath(args ...interface{}) *GraphTraversal {
- g.Bytecode.AddStep("simplePath", args...)
+ g.GremlinLang.AddStep("simplePath", args...)
return g
}
// Skip adds the skip step to the GraphTraversal.
func (g *GraphTraversal) Skip(args ...interface{}) *GraphTraversal {
- g.Bytecode.AddStep("skip", args...)
+ g.GremlinLang.AddStep("skip", args...)
return g
}
// Split adds the split step to the GraphTraversal.
func (g *GraphTraversal) Split(args ...interface{}) *GraphTraversal {
- g.Bytecode.AddStep("split", args...)
+ g.GremlinLang.AddStep("split", args...)
return g
}
// Subgraph adds the subgraph step to the GraphTraversal.
func (g *GraphTraversal) Subgraph(args ...interface{}) *GraphTraversal {
- g.Bytecode.AddStep("subgraph", args...)
+ g.GremlinLang.AddStep("subgraph", args...)
return g
}
// Substring adds the substring step to the GraphTraversal.
func (g *GraphTraversal) Substring(args ...interface{}) *GraphTraversal {
- g.Bytecode.AddStep("substring", int32Args(args)...)
+ g.GremlinLang.AddStep("substring", int32Args(args)...)
return g
}
// Sum adds the sum step to the GraphTraversal.
func (g *GraphTraversal) Sum(args ...interface{}) *GraphTraversal {
- g.Bytecode.AddStep("sum", args...)
+ g.GremlinLang.AddStep("sum", args...)
return g
}
// Tail adds the tail step to the GraphTraversal.
func (g *GraphTraversal) Tail(args ...interface{}) *GraphTraversal {
- g.Bytecode.AddStep("tail", args...)
+ g.GremlinLang.AddStep("tail", args...)
return g
}
// TimeLimit adds the timeLimit step to the GraphTraversal.
func (g *GraphTraversal) TimeLimit(args ...interface{}) *GraphTraversal {
- g.Bytecode.AddStep("timeLimit", args...)
+ g.GremlinLang.AddStep("timeLimit", args...)
return g
}
// Times adds the times step to the GraphTraversal.
func (g *GraphTraversal) Times(args ...interface{}) *GraphTraversal {
// Force int32 serialization for valid number values for server compatibility
- g.Bytecode.AddStep("times", int32Args(args)...)
+ g.GremlinLang.AddStep("times", int32Args(args)...)
return g
}
@@ -802,101 +805,101 @@ func (g *GraphTraversal) Times(args ...interface{}) *GraphTraversal {
func (g *GraphTraversal) To(args ...interface{}) *GraphTraversal {
for i := 0; i < len(args); i++ {
if v, ok := args[i].(*Vertex); ok {
- args[i] = v.Id
+ args[i] = T__.V(v.Id)
}
}
- g.Bytecode.AddStep("to", args...)
+ g.GremlinLang.AddStep("to", args...)
return g
}
// ToE adds the toE step to the GraphTraversal.
func (g *GraphTraversal) ToE(args ...interface{}) *GraphTraversal {
- g.Bytecode.AddStep("toE", args...)
+ g.GremlinLang.AddStep("toE", args...)
return g
}
// ToLower adds the toLower step to the GraphTraversal.
func (g *GraphTraversal) ToLower(args ...interface{}) *GraphTraversal {
- g.Bytecode.AddStep("toLower", args...)
+ g.GremlinLang.AddStep("toLower", args...)
return g
}
// ToUpper adds the toUpper step to the GraphTraversal.
func (g *GraphTraversal) ToUpper(args ...interface{}) *GraphTraversal {
- g.Bytecode.AddStep("toUpper", args...)
+ g.GremlinLang.AddStep("toUpper", args...)
return g
}
// ToV adds the toV step to the GraphTraversal.
func (g *GraphTraversal) ToV(args ...interface{}) *GraphTraversal {
- g.Bytecode.AddStep("toV", args...)
+ g.GremlinLang.AddStep("toV", args...)
return g
}
// Tree adds the tree step to the GraphTraversal.
func (g *GraphTraversal) Tree(args ...interface{}) *GraphTraversal {
- g.Bytecode.AddStep("tree", args...)
+ g.GremlinLang.AddStep("tree", args...)
return g
}
// Trim adds the tree step to the GraphTraversal.
func (g *GraphTraversal) Trim(args ...interface{}) *GraphTraversal {
- g.Bytecode.AddStep("trim", args...)
+ g.GremlinLang.AddStep("trim", args...)
return g
}
// Unfold adds the unfold step to the GraphTraversal.
func (g *GraphTraversal) Unfold(args ...interface{}) *GraphTraversal {
- g.Bytecode.AddStep("unfold", args...)
+ g.GremlinLang.AddStep("unfold", args...)
return g
}
// Union adds the union step to the GraphTraversal.
func (g *GraphTraversal) Union(args ...interface{}) *GraphTraversal {
- g.Bytecode.AddStep("union", args...)
+ g.GremlinLang.AddStep("union", args...)
return g
}
// Until adds the until step to the GraphTraversal.
func (g *GraphTraversal) Until(args ...interface{}) *GraphTraversal {
- g.Bytecode.AddStep("until", args...)
+ g.GremlinLang.AddStep("until", args...)
return g
}
// Value adds the value step to the GraphTraversal.
func (g *GraphTraversal) Value(args ...interface{}) *GraphTraversal {
- g.Bytecode.AddStep("value", args...)
+ g.GremlinLang.AddStep("value", args...)
return g
}
// ValueMap adds the valueMap step to the GraphTraversal.
func (g *GraphTraversal) ValueMap(args ...interface{}) *GraphTraversal {
- g.Bytecode.AddStep("valueMap", args...)
+ g.GremlinLang.AddStep("valueMap", args...)
return g
}
// Values adds the values step to the GraphTraversal.
func (g *GraphTraversal) Values(args ...interface{}) *GraphTraversal {
- g.Bytecode.AddStep("values", args...)
+ g.GremlinLang.AddStep("values", args...)
return g
}
// Where adds the where step to the GraphTraversal.
func (g *GraphTraversal) Where(args ...interface{}) *GraphTraversal {
- g.Bytecode.AddStep("where", args...)
+ g.GremlinLang.AddStep("where", args...)
return g
}
// With adds the with step to the GraphTraversal.
func (g *GraphTraversal) With(args ...interface{}) *GraphTraversal {
// Force int32 serialization for valid number values for server compatibility
- g.Bytecode.AddStep("with", int32Args(args)...)
+ g.GremlinLang.AddStep("with", int32Args(args)...)
return g
}
// Write adds the write step to the GraphTraversal.
func (g *GraphTraversal) Write(args ...interface{}) *GraphTraversal {
- g.Bytecode.AddStep("write", args...)
+ g.GremlinLang.AddStep("write", args...)
return g
}
@@ -944,8 +947,8 @@ func (t *Transaction) Begin() (*GraphTraversalSource, error) {
}
gts := &GraphTraversalSource{
- graph: t.g.graph,
- bytecode: t.g.bytecode}
+ graph: t.g.graph,
+ gremlinLang: t.g.gremlinLang}
return gts, nil
}
diff --git a/gremlin-go/driver/graphTraversalSource.go b/gremlin-go/driver/graphTraversalSource.go
index d90825003a..5f22eb553e 100644
--- a/gremlin-go/driver/graphTraversalSource.go
+++ b/gremlin-go/driver/graphTraversalSource.go
@@ -30,7 +30,7 @@ func convertStrategyVarargs(strategies []TraversalStrategy) []interface{} {
// GraphTraversalSource can be used to start GraphTraversal.
type GraphTraversalSource struct {
graph *Graph
- bytecode *Bytecode
+ gremlinLang *GremlinLang
remoteConnection *DriverRemoteConnection
graphTraversal *GraphTraversal
}
@@ -39,34 +39,32 @@ type GraphTraversalSource struct {
// Graph and DriverRemoteConnection can be set to nil as valid default values.
func NewGraphTraversalSource(graph *Graph, remoteConnection *DriverRemoteConnection,
traversalStrategies ...TraversalStrategy) *GraphTraversalSource {
- convertedArgs := convertStrategyVarargs(traversalStrategies)
- bc := NewBytecode(nil)
- bc.AddSource("withStrategies", convertedArgs...)
- return &GraphTraversalSource{graph: graph, bytecode: bc, remoteConnection: remoteConnection}
+ // TODO: revisit when updating strategies
+ gl := NewGremlinLang(nil)
+ return &GraphTraversalSource{graph: graph, gremlinLang: gl, remoteConnection: remoteConnection}
}
// NewDefaultGraphTraversalSource creates a new graph GraphTraversalSource without a graph, strategy, or existing traversal.
func NewDefaultGraphTraversalSource() *GraphTraversalSource {
- return &GraphTraversalSource{graph: nil, bytecode: NewBytecode(nil), remoteConnection: nil}
+ return &GraphTraversalSource{graph: nil, gremlinLang: NewGremlinLang(nil), remoteConnection: nil}
}
-// GetBytecode gets the traversal Bytecode associated with this graph traversal source.
-func (gts *GraphTraversalSource) GetBytecode() *Bytecode {
- return gts.bytecode
+func (gts *GraphTraversalSource) GetGremlinLang() *GremlinLang {
+ return gts.gremlinLang
}
// GetGraphTraversal gets the graph traversal associated with this graph traversal source.
func (gts *GraphTraversalSource) GetGraphTraversal() *GraphTraversal {
- return NewGraphTraversal(gts.graph, NewBytecode(gts.bytecode), gts.remoteConnection)
+ return NewGraphTraversal(gts.graph, NewGremlinLang(gts.gremlinLang), gts.remoteConnection)
}
func (gts *GraphTraversalSource) clone() *GraphTraversalSource {
- return cloneGraphTraversalSource(gts.graph, NewBytecode(gts.bytecode), gts.remoteConnection)
+ return cloneGraphTraversalSource(gts.graph, NewGremlinLang(gts.gremlinLang), gts.remoteConnection)
}
-func cloneGraphTraversalSource(graph *Graph, bc *Bytecode, remoteConnection *DriverRemoteConnection) *GraphTraversalSource {
+func cloneGraphTraversalSource(graph *Graph, gl *GremlinLang, remoteConnection *DriverRemoteConnection) *GraphTraversalSource {
return &GraphTraversalSource{graph: graph,
- bytecode: bc,
+ gremlinLang: gl,
remoteConnection: remoteConnection,
}
}
@@ -74,17 +72,14 @@ func cloneGraphTraversalSource(graph *Graph, bc *Bytecode, remoteConnection *Dri
// WithBulk allows for control of bulking operations.
func (gts *GraphTraversalSource) WithBulk(args ...interface{}) *GraphTraversalSource {
source := gts.clone()
- err := source.bytecode.AddSource("withBulk", args...)
- if err != nil {
- return nil
- }
+ source.gremlinLang.AddSource("withBulk", args...)
return source
}
// WithPath adds a path to be used throughout the life of a spawned Traversal.
func (gts *GraphTraversalSource) WithPath(args ...interface{}) *GraphTraversalSource {
source := gts.clone()
- source.bytecode.AddSource("withPath", args...)
+ source.gremlinLang.AddSource("withPath", args...)
return source
}
@@ -92,14 +87,14 @@ func (gts *GraphTraversalSource) WithPath(args ...interface{}) *GraphTraversalSo
func (gts *GraphTraversalSource) WithSack(args ...interface{}) *GraphTraversalSource {
source := gts.clone()
// Force int32 serialization for valid number values for server compatibility
- source.bytecode.AddSource("withSack", int32Args(args)...)
+ source.gremlinLang.AddSource("withSack", int32Args(args)...)
return source
}
// WithSideEffect adds a side effect to be used throughout the life of a spawned Traversal.
func (gts *GraphTraversalSource) WithSideEffect(args ...interface{}) *GraphTraversalSource {
source := gts.clone()
- source.bytecode.AddSource("withSideEffect", args...)
+ source.gremlinLang.AddSource("withSideEffect", args...)
return source
}
@@ -107,7 +102,7 @@ func (gts *GraphTraversalSource) WithSideEffect(args ...interface{}) *GraphTrave
func (gts *GraphTraversalSource) WithStrategies(args ...TraversalStrategy) *GraphTraversalSource {
convertedArgs := convertStrategyVarargs(args)
source := gts.clone()
- source.bytecode.AddSource("withStrategies", convertedArgs...)
+ source.gremlinLang.AddSource("withStrategies", convertedArgs...)
return source
}
@@ -115,7 +110,7 @@ func (gts *GraphTraversalSource) WithStrategies(args ...TraversalStrategy) *Grap
func (gts *GraphTraversalSource) WithoutStrategies(args ...TraversalStrategy) *GraphTraversalSource {
convertedArgs := convertStrategyVarargs(args)
source := gts.clone()
- source.bytecode.AddSource("withoutStrategies", convertedArgs...)
+ source.gremlinLang.AddSource("withoutStrategies", convertedArgs...)
return source
}
@@ -123,13 +118,10 @@ func (gts *GraphTraversalSource) WithoutStrategies(args ...TraversalStrategy) *G
func (gts *GraphTraversalSource) With(key interface{}, value interface{}) *GraphTraversalSource {
source := gts.clone()
+ //TODO verify remote when connection is set-up
var optionsStrategy TraversalStrategy = nil
- for _, v := range gts.bytecode.sourceInstructions {
- if v.operator == "withStrategies" &&
- v.arguments[0].(*traversalStrategy).name == decorationNamespace+"OptionsStrategy" {
- optionsStrategy = v.arguments[0]
- break
- }
+ if len(gts.gremlinLang.optionsStrategies) != 0 {
+ optionsStrategy = gts.gremlinLang.optionsStrategies[0]
}
if optionsStrategy == nil {
@@ -154,7 +146,7 @@ func (gts *GraphTraversalSource) WithRemote(remoteConnection *DriverRemoteConnec
// E reads edges from the graph to start the traversal.
func (gts *GraphTraversalSource) E(args ...interface{}) *GraphTraversal {
traversal := gts.GetGraphTraversal()
- traversal.Bytecode.AddStep("E", args...)
+ traversal.GremlinLang.AddStep("E", args...)
return traversal
}
@@ -166,28 +158,28 @@ func (gts *GraphTraversalSource) V(args ...interface{}) *GraphTraversal {
}
}
traversal := gts.GetGraphTraversal()
- traversal.Bytecode.AddStep("V", args...)
+ traversal.GremlinLang.AddStep("V", args...)
return traversal
}
// AddE adds an Edge to start the traversal.
func (gts *GraphTraversalSource) AddE(args ...interface{}) *GraphTraversal {
traversal := gts.GetGraphTraversal()
- traversal.Bytecode.AddStep("addE", args...)
+ traversal.GremlinLang.AddStep("addE", args...)
return traversal
}
// AddV adds a Vertex to start the traversal.
func (gts *GraphTraversalSource) AddV(args ...interface{}) *GraphTraversal {
traversal := gts.GetGraphTraversal()
- traversal.Bytecode.AddStep("addV", args...)
+ traversal.GremlinLang.AddStep("addV", args...)
return traversal
}
// Call starts the traversal by executing a procedure
func (gts *GraphTraversalSource) Call(args ...interface{}) *GraphTraversal {
traversal := gts.GetGraphTraversal()
- traversal.Bytecode.AddStep("call", args...)
+ traversal.GremlinLang.AddStep("call", args...)
return traversal
}
@@ -195,14 +187,14 @@ func (gts *GraphTraversalSource) Call(args ...interface{}) *GraphTraversal {
func (gts *GraphTraversalSource) Inject(args ...interface{}) *GraphTraversal {
traversal := gts.GetGraphTraversal()
// Force int32 serialization for valid number values for server compatibility
- traversal.Bytecode.AddStep("inject", int32Args(args)...)
+ traversal.GremlinLang.AddStep("inject", int32Args(args)...)
return traversal
}
// Io adds the io steps to start the traversal.
func (gts *GraphTraversalSource) Io(args ...interface{}) *GraphTraversal {
traversal := gts.GetGraphTraversal()
- traversal.Bytecode.AddStep("io", args...)
+ traversal.GremlinLang.AddStep("io", args...)
return traversal
}
@@ -219,21 +211,21 @@ func (gts *GraphTraversalSource) MergeE(args ...interface{}) *GraphTraversal {
}
}
traversal := gts.GetGraphTraversal()
- traversal.Bytecode.AddStep("mergeE", args...)
+ traversal.GremlinLang.AddStep("mergeE", args...)
return traversal
}
// MergeV uses an upsert-like operation to add a Vertex to start the traversal.
func (gts *GraphTraversalSource) MergeV(args ...interface{}) *GraphTraversal {
traversal := gts.GetGraphTraversal()
- traversal.Bytecode.AddStep("mergeV", args...)
+ traversal.GremlinLang.AddStep("mergeV", args...)
return traversal
}
// Union allows for a multi-branched start to a traversal.
func (gts *GraphTraversalSource) Union(args ...interface{}) *GraphTraversal {
traversal := gts.GetGraphTraversal()
- traversal.Bytecode.AddStep("union", args...)
+ traversal.GremlinLang.AddStep("union", args...)
return traversal
}
diff --git a/gremlin-go/driver/graphTraversalSource_test.go b/gremlin-go/driver/graphTraversalSource_test.go
index f33c432d33..7a842e5e4b 100644
--- a/gremlin-go/driver/graphTraversalSource_test.go
+++ b/gremlin-go/driver/graphTraversalSource_test.go
@@ -28,41 +28,29 @@ func TestGraphTraversalSource(t *testing.T) {
t.Run("GraphTraversalSource.With tests", func(t *testing.T) {
t.Run("Test for single property", func(t *testing.T) {
- g := &GraphTraversalSource{graph: &Graph{}, bytecode: NewBytecode(nil), remoteConnection: nil}
+ g := &GraphTraversalSource{graph: &Graph{}, gremlinLang: NewGremlinLang(nil), remoteConnection: nil}
traversal := g.With("foo", "bar")
assert.NotNil(t, traversal)
- assert.Equal(t, 1, len(traversal.bytecode.sourceInstructions))
- instruction := traversal.bytecode.sourceInstructions[0]
- assert.Equal(t, "withStrategies", instruction.operator)
- assert.Equal(t, "org.apache.tinkerpop.gremlin.process.traversal.strategy.decoration.OptionsStrategy",
- instruction.arguments[0].(*traversalStrategy).name)
- config := instruction.arguments[0].(*traversalStrategy).configuration
+ assert.Equal(t, 1, len(traversal.gremlinLang.optionsStrategies))
+ config := traversal.gremlinLang.optionsStrategies[0].configuration
assert.Equal(t, map[string]interface{}{"foo": "bar"}, config)
})
t.Run("Test for multiple property", func(t *testing.T) {
- g := &GraphTraversalSource{graph: &Graph{}, bytecode: NewBytecode(nil), remoteConnection: nil}
+ g := &GraphTraversalSource{graph: &Graph{}, gremlinLang: NewGremlinLang(nil), remoteConnection: nil}
traversal := g.With("foo", "bar").With("foo2", "bar2")
assert.NotNil(t, traversal)
- assert.Equal(t, 1, len(traversal.bytecode.sourceInstructions))
- instruction := traversal.bytecode.sourceInstructions[0]
- assert.Equal(t, "withStrategies", instruction.operator)
- assert.Equal(t, "org.apache.tinkerpop.gremlin.process.traversal.strategy.decoration.OptionsStrategy",
- instruction.arguments[0].(*traversalStrategy).name)
- config := instruction.arguments[0].(*traversalStrategy).configuration
+ assert.Equal(t, 1, len(traversal.gremlinLang.optionsStrategies))
+ config := traversal.gremlinLang.optionsStrategies[0].configuration
assert.Equal(t, map[string]interface{}{"foo": "bar", "foo2": "bar2"}, config)
})
t.Run("Test for property replacement", func(t *testing.T) {
- g := &GraphTraversalSource{graph: &Graph{}, bytecode: NewBytecode(nil), remoteConnection: nil}
+ g := &GraphTraversalSource{graph: &Graph{}, gremlinLang: NewGremlinLang(nil), remoteConnection: nil}
traversal := g.With("foo", "bar").With("foo", "not bar")
assert.NotNil(t, traversal)
- assert.Equal(t, 1, len(traversal.bytecode.sourceInstructions))
- instruction := traversal.bytecode.sourceInstructions[0]
- assert.Equal(t, "withStrategies", instruction.operator)
- assert.Equal(t, "org.apache.tinkerpop.gremlin.process.traversal.strategy.decoration.OptionsStrategy",
- instruction.arguments[0].(*traversalStrategy).name)
- config := instruction.arguments[0].(*traversalStrategy).configuration
+ assert.Equal(t, 1, len(traversal.gremlinLang.optionsStrategies))
+ config := traversal.gremlinLang.optionsStrategies[0].configuration
assert.Equal(t, map[string]interface{}{"foo": "not bar"}, config)
})
})
diff --git a/gremlin-go/driver/gremlinClient.go b/gremlin-go/driver/gremlinClient.go
deleted file mode 100644
index 5a1369c612..0000000000
--- a/gremlin-go/driver/gremlinClient.go
+++ /dev/null
@@ -1,148 +0,0 @@
-/*
-Licensed to the Apache Software Foundation (ASF) under one
-or more contributor license agreements. See the NOTICE file
-distributed with this work for additional information
-regarding copyright ownership. The ASF licenses this file
-to you under the Apache License, Version 2.0 (the
-"License"); you may not use this file except in compliance
-with the License. You may obtain a copy of the License at
-
-http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing,
-software distributed under the License is distributed on an
-"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-KIND, either express or implied. See the License for the
-specific language governing permissions and limitations
-under the License.
-*/
-
-package gremlingo
-
-import (
- "net/http"
-)
-
-// responsible for serializing and sending requests and then receiving and deserializing responses
-type gremlinClient struct {
- serializer serializer
- logHandler *logHandler
- url string
- connSettings *connectionSettings
- httpClient *http.Client
-}
-
-func newGremlinClient(handler *logHandler, url string, connSettings *connectionSettings) *gremlinClient {
- transport := &http.Transport{
- TLSClientConfig: connSettings.tlsConfig,
- MaxConnsPerHost: 0, // TODO
- IdleConnTimeout: 0, // TODO
- DisableCompression: !connSettings.enableCompression,
- }
-
- httpClient := http.Client{
- Transport: transport,
- Timeout: connSettings.connectionTimeout,
- }
-
- httpProt := &gremlinClient{
- serializer: newGraphBinarySerializer(handler),
- logHandler: handler,
- url: url,
- connSettings: connSettings,
- httpClient: &httpClient,
- }
- return httpProt
-}
-
-// sends a query request and returns a ResultSet that can be used to obtain query results
-func (client *gremlinClient) send(request *request) (ResultSet, error) {
- rs := newChannelResultSet()
- bytes, err := client.serializer.serializeMessage(request)
- if err != nil {
- rs.setError(err)
- rs.Close()
- return rs, err
- }
-
- // one transport per request
- transport := newHttpTransporter(client.url, client.connSettings, client.httpClient, client.logHandler)
-
- // async send request
- transport.wg.Add(1)
- go func() {
- defer transport.wg.Done()
- err := transport.write(bytes)
- if err != nil {
- rs.setError(err)
- rs.Close()
- }
- }()
-
- // async receive response
- transport.wg.Add(1)
- go func() {
- defer transport.wg.Done()
- msg, err := transport.read()
- if err != nil {
- rs.setError(err)
- rs.Close()
- } else {
- err = client.receive(rs, msg)
- }
- transport.close()
- }()
-
- return rs, err
-}
-
-// receives a binary response message, deserializes, and adds results to the ResultSet
-func (client *gremlinClient) receive(rs ResultSet, msg []byte) error {
- resp, err := client.serializer.deserializeMessage(msg)
- if err != nil {
- client.logHandler.logf(Error, logErrorGeneric, "deserializeMessage()", err.Error())
- rs.Close()
- return err
- }
-
- err = client.handleResponse(rs, resp)
- if err != nil {
- client.logHandler.logf(Error, logErrorGeneric, "handleResponse()", err.Error())
- rs.Close()
- return err
- }
- return nil
-}
-
-// processes a deserialized response and attempts to add results to the ResultSet
-func (client *gremlinClient) handleResponse(rs ResultSet, response response) error {
- statusCode, data := response.responseStatus.code, response.responseResult.data
- if rs == nil {
- return newError(err0501ResponseHandlerResultSetNotCreatedError)
- }
-
- if statusCode == http.StatusNoContent {
- rs.addResult(&Result{make([]interface{}, 0)})
- rs.Close()
- client.logHandler.logf(Debug, readComplete)
- } else if statusCode == http.StatusOK {
- rs.addResult(&Result{data})
- rs.Close()
- client.logHandler.logf(Debug, readComplete)
- } else if statusCode == http.StatusUnauthorized || statusCode == http.StatusForbidden {
- rs.Close()
- err := newError(err0503ResponseHandlerAuthError, response.responseStatus, response.responseResult)
- rs.setError(err)
- return err
- } else {
- rs.Close()
- err := newError(err0502ResponseHandlerError, response.responseStatus, statusCode)
- rs.setError(err)
- return err
- }
- return nil
-}
-
-func (client *gremlinClient) close() {
- client.httpClient.CloseIdleConnections()
-}
diff --git a/gremlin-go/driver/gremlinlang.go b/gremlin-go/driver/gremlinlang.go
new file mode 100644
index 0000000000..cd2b724487
--- /dev/null
+++ b/gremlin-go/driver/gremlinlang.go
@@ -0,0 +1,569 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements. See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership. The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License. You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied. See the License for the
+specific language governing permissions and limitations
+under the License.
+*/
+
+package gremlingo
+
+import (
+ "fmt"
+ "go/token"
+ "math"
+ "math/big"
+ "reflect"
+ "strconv"
+ "strings"
+ "sync/atomic"
+ "time"
+
+ "github.com/google/uuid"
+)
+
+type GremlinLang struct {
+ emptyArray []interface{}
+ gremlin []string
+ parameters map[string]interface{}
+ optionsStrategies []*traversalStrategy
+ paramCount *atomic.Uint64
+}
+
+// NewGremlinLang creates a new GremlinLang to be used in traversals.
+func NewGremlinLang(gl *GremlinLang) *GremlinLang {
+ gremlin := make([]string, 0)
+ parameters := make(map[string]interface{})
+ optionsStrategies := make([]*traversalStrategy, 0)
+ paramCount := atomic.Uint64{}
+ if gl != nil {
+ gremlin = make([]string, len(gl.gremlin))
+ copy(gremlin, gl.gremlin)
+
+ parameters = make(map[string]interface{})
+ for k, v := range gl.parameters {
+ parameters[k] = v
+ }
+
+ optionsStrategies = make([]*traversalStrategy, len(gl.optionsStrategies))
+ copy(optionsStrategies, gl.optionsStrategies)
+
+ paramCount.Store(gl.paramCount.Load())
+ }
+
+ return &GremlinLang{
+ gremlin: gremlin,
+ parameters: parameters,
+ optionsStrategies: optionsStrategies,
+ paramCount: ¶mCount,
+ }
+}
+
+func (gl *GremlinLang) addToGremlin(name string, args ...interface{}) error {
+ flattenedArgs := gl.flattenArguments(args...)
+ if name == "CardinalityValueTraversal" {
+ str0, err := gl.argAsString(flattenedArgs[0])
+ if err != nil {
+ return err
+ }
+ str1, err := gl.argAsString(flattenedArgs[1])
+ if err != nil {
+ return err
+ }
+ gl.gremlin = append(gl.gremlin, str0)
+ gl.gremlin = append(gl.gremlin, "(")
+ gl.gremlin = append(gl.gremlin, str1)
+ gl.gremlin = append(gl.gremlin, ")")
+ return nil
+ }
+
+ gl.gremlin = append(gl.gremlin, ".")
+ gl.gremlin = append(gl.gremlin, name)
+ gl.gremlin = append(gl.gremlin, "(")
+
+ for i := 0; i < len(flattenedArgs); i++ {
+ if i > 0 {
+ gl.gremlin = append(gl.gremlin, ",")
+ }
+ convertArg, err := gl.convertArgument(flattenedArgs[i]) //.Index(i).Interface())
+ if err != nil {
+ return err
+ }
+ argStr, err := gl.argAsString(convertArg)
+ if err != nil {
+ return err
+ }
+ gl.gremlin = append(gl.gremlin, argStr)
+ }
+ gl.gremlin = append(gl.gremlin, ")")
+ return nil
+}
+
+func (gl *GremlinLang) argAsString(arg interface{}) (string, error) {
+ if arg == nil {
+ return "null", nil
+ }
+ // we are concerned with both single and double quotes and %q in fmt only escapes double quotes
+ escapeQuotes := strings.NewReplacer(`'`, `\'`, `"`, `\"`)
+
+ switch v := arg.(type) {
+ case string:
+ return fmt.Sprintf("\"%s\"", escapeQuotes.Replace(v)), nil
+ case bool:
+ return strconv.FormatBool(v), nil
+ case int8, uint8:
+ return fmt.Sprintf("%dB", v), nil
+ case int16:
+ return fmt.Sprintf("%dS", v), nil
+ case int32, uint16:
+ return fmt.Sprintf("%d", v), nil
+ case int:
+ if v <= math.MaxInt32 && v >= math.MinInt32 {
+ return fmt.Sprintf("%d", v), nil
+ } else {
+ return fmt.Sprintf("%dL", v), nil
+ }
+ case int64, uint32:
+ return fmt.Sprintf("%dL", v), nil
+ case uint:
+ if v <= math.MaxUint32 {
+ return fmt.Sprintf("%dL", v), nil
+ } else {
+ return fmt.Sprintf("%dN", v), nil
+ }
+ case uint64, *big.Int:
+ return fmt.Sprintf("%dN", v), nil
+ case float32:
+ if math.IsNaN(float64(v)) {
+ return "NaN", nil
+ }
+ if math.IsInf(float64(v), 1) {
+ return "Infinity", nil
+ }
+ if math.IsInf(float64(v), -1) {
+ return "-Infinity", nil
+ }
+ return fmt.Sprintf("%vF", v), nil
+ case float64:
+ if math.IsNaN(v) {
+ return "NaN", nil
+ }
+ if math.IsInf(v, 1) {
+ return "Infinity", nil
+ }
+ if math.IsInf(v, -1) {
+ return "-Infinity", nil
+ }
+ return fmt.Sprintf("%vD", v), nil
+ case *SimpleSet:
+ return gl.translateSet(v.ToSlice())
+ case BigDecimal:
+ return fmt.Sprintf("%vM", v.Value()), nil
+ case *BigDecimal:
+ return fmt.Sprintf("%vM", v.Value()), nil
+ case time.Time:
+ return fmt.Sprintf("datetime(\"%v\")", v.Format(time.RFC3339)), nil
+ 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
+ case dt:
+ name := reflect.ValueOf(v).Type().Name()
+ return fmt.Sprintf("%s.%s", strings.ToUpper(name), v), nil
+ case *Vertex:
+ return gl.argAsString(v.Id)
+ case textP:
+ return gl.translateTextPredicate(&v)
+ case *textP:
+ return gl.translateTextPredicate(v)
+ case p:
+ return gl.translatePredicate(&v)
+ case *p:
+ return gl.translatePredicate(v)
+ case Traversal:
+ gremlinLang := v.GremlinLang
+ for key, val := range gremlinLang.parameters {
+ gl.parameters[key] = val
+ }
+ return gremlinLang.GetGremlin("__"), nil
+ case *Traversal:
+ gremlinLang := v.GremlinLang
+ for key, val := range gremlinLang.parameters {
+ gl.parameters[key] = val
+ }
+ return gremlinLang.GetGremlin("__"), nil
+ case GraphTraversal:
+ gremlinLang := v.GremlinLang
+ for key, val := range gremlinLang.parameters {
+ gl.parameters[key] = val
+ }
+ return gremlinLang.GetGremlin("__"), nil
+ case *GraphTraversal:
+ gremlinLang := v.GremlinLang
+ for key, val := range gremlinLang.parameters {
+ gl.parameters[key] = val
+ }
+ return gremlinLang.GetGremlin("__"), nil
+ case GremlinLang:
+ for key, val := range v.parameters {
+ gl.parameters[key] = val
+ }
+ return v.GetGremlin("__"), nil
+ case *GremlinLang:
+ for key, val := range v.parameters {
+ gl.parameters[key] = val
+ }
+ return v.GetGremlin("__"), nil
+ case GValue:
+ key := v.Name()
+ if !token.IsIdentifier(key) {
+ panic(fmt.Sprintf("invalid parameter name '%v'.", key))
+ }
+ value := v.Value()
+ if val, ok := gl.parameters[key]; ok {
+ if reflect.TypeOf(val).Kind() == reflect.Slice || reflect.TypeOf(value).Kind() == reflect.Slice ||
+ reflect.TypeOf(val).Kind() == reflect.Map || reflect.TypeOf(value).Kind() == reflect.Map {
+ if !reflect.DeepEqual(val, value) {
+ panic(fmt.Sprintf("parameter with name '%v' already exists.", key))
+ }
+ } else if val != value {
+ panic(fmt.Sprintf("parameter with name '%v' already exists.", key))
+ }
+ } else {
+ gl.parameters[key] = v.Value()
+ }
+ return key, nil
+ case uuid.UUID:
+ return fmt.Sprintf("UUID(\"%v\")", v.String()), nil
+ default:
+ switch reflect.TypeOf(arg).Kind() {
+ case reflect.Map:
+ return gl.translateMap(arg)
+ case reflect.Slice:
+ return gl.translateSlice(arg)
+ default:
+ return gl.asParameter(arg), nil
+ }
+ }
+}
+
+func (gl *GremlinLang) translateMap(arg interface{}) (string, error) {
+ sb := strings.Builder{}
+ sb.WriteString("[")
+ size := reflect.ValueOf(arg).Len()
+ if size == 0 {
+ sb.WriteString(":")
+ } else {
+ iter := reflect.ValueOf(arg).MapRange()
+ for iter.Next() {
+ k := iter.Key().Interface()
+ kString, err := gl.argAsString(k)
+ if err != nil {
+ return "", err
+ }
+ v := iter.Value().Interface()
+ vString, err := gl.argAsString(v)
+ if err != nil {
+ return "", err
+ }
+ sb.WriteString(kString)
+ sb.WriteByte(':')
+ sb.WriteString(vString)
+ size--
+ if size > 0 {
+ sb.WriteString(",")
+ }
+ }
+ }
+
+ sb.WriteString("]")
+ return sb.String(), nil
+}
+
+func (gl *GremlinLang) translateSlice(arg interface{}) (string, error) {
+ return gl.translateCollection(arg, "[", "]")
+}
+
+func (gl *GremlinLang) translateSet(arg interface{}) (string, error) {
+ return gl.translateCollection(arg, "{", "}")
+}
+
+func (gl *GremlinLang) translateCollection(arg interface{}, open, close string) (string, error) {
+ sb := strings.Builder{}
+ sb.WriteString(open)
+ list := reflect.ValueOf(arg)
+
+ for i := 0; i < list.Len(); i++ {
+ if i > 0 {
+ sb.WriteString(",")
+ }
+ vString, err := gl.argAsString(list.Index(i).Interface())
+ if err != nil {
+ return "", err
+ }
+ sb.WriteString(vString)
+ }
+ sb.WriteString(close)
+ return sb.String(), nil
+}
+
+func (gl *GremlinLang) translateTextPredicate(v *textP) (string, error) {
+ if v.operator == "" && len(v.values) == 0 {
+ return "", nil
+ }
+
+ if v.operator == "or" || v.operator == "and" {
+ return gl.translateConnPOp(v.operator, v.values, gl.getPredicateString)
+ }
+ return gl.translatePValue(v.operator, v.values)
+}
+
+func (gl *GremlinLang) translatePredicate(v *p) (string, error) {
+ if v.operator == "" && len(v.values) == 0 {
+ return "", nil
+ }
+
+ if v.operator == "or" || v.operator == "and" {
+ return gl.translateConnPOp(v.operator, v.values, gl.getPredicateString)
+ }
+ return gl.translatePValue(v.operator, v.values)
+}
+
+func (gl *GremlinLang) translateConnPOp(operator string, values []interface{}, translator func(interface{}) (string, error)) (string, error) {
+ arg1, err := translator(values[0])
+ if err != nil {
+ return "", err
+ }
+ arg2, err := translator(values[1])
+ if err != nil {
+ return "", err
+ }
+ return arg1 + "." + operator + "(" + arg2 + ")", nil
+}
+
+func (gl *GremlinLang) getPredicateString(v interface{}) (string, error) {
+ if val, ok := v.(textP); ok {
+ return gl.translateTextPredicate(&val)
+ }
+ if val, ok := v.(*textP); ok {
+ return gl.translateTextPredicate(val)
+ }
+ if val, ok := v.(p); ok {
+ return gl.translatePredicate(&val)
+ }
+ return gl.translatePredicate(v.(*p))
+}
+
+func (gl *GremlinLang) translatePValue(operator string, values []interface{}) (string, error) {
+ sb := strings.Builder{}
+ sb.WriteString(operator + "(")
+
+ if len(values) > 1 && operator != "between" && operator != "inside" {
+ sb.WriteString("[")
+ }
+
+ for i, arg := range values {
+ argString, err := gl.argAsString(arg)
+ if err != nil {
+ return "", err
+ }
+ sb.WriteString(argString)
+ if i < len(values)-1 && argString != "" {
+ sb.WriteString(",")
+ }
+ }
+
+ if len(values) > 1 && operator != "between" && operator != "inside" {
+ sb.WriteString("]")
+ }
+ sb.WriteString(")")
+
+ return sb.String(), nil
+}
+
+func (gl *GremlinLang) asParameter(arg interface{}) string {
+ paramName := fmt.Sprintf("_%d", gl.paramCount.Load())
+ gl.paramCount.Add(1)
+ gl.parameters[paramName] = arg
+ return paramName
+}
+
+func (gl *GremlinLang) GetGremlin(arg ...string) string {
+ var g string
+ gremlin := strings.Join(gl.gremlin, "")
+ if len(arg) == 0 {
+ g = "g"
+ } else {
+ g = arg[0]
+ }
+ // special handling for CardinalityValueTraversal
+ if len(gl.gremlin) != 0 && string(gremlin[0]) != "." {
+ return gremlin
+ }
+ return g + gremlin
+}
+
+func (gl *GremlinLang) GetParameters() map[string]interface{} {
+ return gl.parameters
+}
+
+func (gl *GremlinLang) AddG(g string) {
+ gl.parameters["g"] = g
+}
+
+func (gl *GremlinLang) Reset() {
+ gl.paramCount.Store(0)
+}
+
+func (gl *GremlinLang) AddSource(name string, arguments ...interface{}) {
+ if (name == "withStrategies" || name == "withoutStrategies") && len(arguments) != 0 {
+ args := gl.buildStrategyArgs(arguments...)
+ // possible to have empty strategies list to send
+ if len(args) != 0 {
+ if name == "withoutStrategies" {
+ gl.gremlin = append(gl.gremlin, ".withoutStrategies(")
+ } else {
+ gl.gremlin = append(gl.gremlin, ".withStrategies(")
+ }
+ gl.gremlin = append(gl.gremlin, args)
+ gl.gremlin = append(gl.gremlin, ")")
+ }
+ return
+ }
+ gl.addToGremlin(name, arguments)
+}
+
+func (gl *GremlinLang) buildStrategyArgs(args ...interface{}) string {
+ sb := strings.Builder{}
+ c := 0
+ for _, arg := range args {
+ if c > 0 {
+ sb.WriteString(",")
+ }
+ strategy, ok := arg.(*traversalStrategy)
+ if !ok {
+ // error?
+ continue
+ }
+ // special handling for OptionsStrategy
+ if strategy.name == "OptionsStrategy" {
+ gl.optionsStrategies = append(gl.optionsStrategies, strategy)
+ continue
+ }
+ if len(strategy.configuration) == 0 {
+ sb.WriteString(strategy.name)
+ } else {
+ sb.WriteString("new ")
+ sb.WriteString(strategy.name)
+ sb.WriteString("(")
+ ct := 0
+ for key, val := range strategy.configuration {
+ if ct > 0 {
+ sb.WriteString(",")
+ }
+ sb.WriteString(key)
+ sb.WriteString(":")
+ traversal, ok := val.(*Traversal)
+ if ok {
+ stringTraversal, _ := gl.argAsString(traversal.GremlinLang)
+ sb.WriteString(stringTraversal)
+ } else {
+ stringArg, _ := gl.argAsString(val)
+ sb.WriteString(stringArg)
+ }
+ ct++
+ }
+ sb.WriteString(")")
+ }
+ c++
+ }
+ return sb.String()
+}
+
+func (gl *GremlinLang) AddStep(stepName string, arguments ...interface{}) error {
+ err := gl.addToGremlin(stepName, arguments)
+ return err
+}
+
+func (gl *GremlinLang) GetOptionsStrategies() []*traversalStrategy {
+ return gl.optionsStrategies
+}
+
+func (gl *GremlinLang) IsEmpty() bool {
+ return len(gl.gremlin) == 0
+}
+
+func (gl *GremlinLang) flattenArguments(arguments ...interface{}) []interface{} {
+ if arguments == nil || len(arguments) == 0 {
+ return gl.emptyArray
+ }
+ flatArgs := make([]interface{}, 0)
+ for _, argument := range arguments {
+ arg, ok := argument.([]interface{})
+ if ok {
+ for _, nestedArg := range arg {
+ _, isUUID := nestedArg.(uuid.UUID)
+ if isUUID {
+ flatArgs = append(flatArgs, nestedArg)
+ } else {
+ converted, _ := gl.convertArgument(nestedArg)
+ flatArgs = append(flatArgs, converted)
+ }
+ }
+ } else {
+ converted, _ := gl.convertArgument(argument)
+ flatArgs = append(flatArgs, converted)
+ }
+ }
+ return flatArgs
+}
+
+func (gl *GremlinLang) convertArgument(arg interface{}) (interface{}, error) {
+ if arg == nil {
+ return nil, nil
+ }
+ switch v := arg.(type) {
+ case *Traversal:
+ if v.graph != nil {
+ return nil, fmt.Errorf("the child traversal of %s was not spawned anonymously - "+
+ "use the __ class rather than a TraversalSource to construct the child traversal", arg)
+ }
+ return v.GremlinLang, nil
+ default:
+ switch reflect.TypeOf(arg).Kind() {
+ case reflect.Map:
+ argMap := reflect.ValueOf(arg)
+ newMap := make(map[interface{}]interface{}, argMap.Len())
+ for _, key := range argMap.MapKeys() {
+ convertKey, _ := gl.convertArgument(key.Interface())
+ convertValue, _ := gl.convertArgument(argMap.MapIndex(key).Interface())
+ newMap[convertKey] = convertValue
+ }
+ return newMap, nil
+ case reflect.Array, reflect.Slice:
+ _, isUUID := arg.(uuid.UUID)
+ if isUUID {
+ return arg, nil
+ }
+ argList := reflect.ValueOf(arg)
+ newList := make([]interface{}, argList.Len())
+ for i := 0; i < argList.Len(); i++ {
+ convertValue, _ := gl.convertArgument(argList.Index(i).Interface())
+ newList[i] = convertValue
+ }
+ return newList, nil
+ default:
+ return arg, nil
+ }
+ }
+}
diff --git a/gremlin-go/driver/translator_test.go b/gremlin-go/driver/gremlinlang_test.go
similarity index 66%
rename from gremlin-go/driver/translator_test.go
rename to gremlin-go/driver/gremlinlang_test.go
index 8daa5ce312..2ff2abef23 100644
--- a/gremlin-go/driver/translator_test.go
+++ b/gremlin-go/driver/gremlinlang_test.go
@@ -20,12 +20,15 @@ under the License.
package gremlingo
import (
+ "math"
"regexp"
"testing"
"time"
+
+ "github.com/google/uuid"
)
-func Test_translator_Translate(t *testing.T) {
+func Test_GremlinLang(t *testing.T) {
type test struct {
name string
assert func(g *GraphTraversalSource) *GraphTraversal
@@ -42,11 +45,11 @@ func Test_translator_Translate(t *testing.T) {
},
{
assert: func(g *GraphTraversalSource) *GraphTraversal { return g.V("1", "2", "3", "4") },
- equals: "g.V('1','2','3','4')",
+ equals: "g.V(\"1\",\"2\",\"3\",\"4\")",
},
{
assert: func(g *GraphTraversalSource) *GraphTraversal { return g.V("3").ValueMap(true) },
- equals: "g.V('3').valueMap(true)",
+ equals: "g.V(\"3\").valueMap(true)",
},
{
assert: func(g *GraphTraversalSource) *GraphTraversal { return g.V().Constant(5) },
@@ -54,101 +57,101 @@ func Test_translator_Translate(t *testing.T) {
},
{
assert: func(g *GraphTraversalSource) *GraphTraversal { return g.V().Constant(1.5) },
- equals: "g.V().constant(1.5)",
+ equals: "g.V().constant(1.5D)",
},
{
assert: func(g *GraphTraversalSource) *GraphTraversal { return g.V().Constant("Hello") },
- equals: "g.V().constant('Hello')",
+ equals: "g.V().constant(\"Hello\")",
},
{
assert: func(g *GraphTraversalSource) *GraphTraversal { return g.V().HasLabel("airport").Limit(5) },
- equals: "g.V().hasLabel('airport').limit(5)",
+ equals: "g.V().hasLabel(\"airport\").limit(5)",
},
{
assert: func(g *GraphTraversalSource) *GraphTraversal { return g.V().HasLabel(P.Within("a", "b", "c")) },
- equals: "g.V().hasLabel(within(['a','b','c']))",
+ equals: "g.V().hasLabel(within([\"a\",\"b\",\"c\"]))",
},
{
assert: func(g *GraphTraversalSource) *GraphTraversal {
return g.V().HasLabel("airport", "continent").Out().Limit(5)
},
- equals: "g.V().hasLabel('airport','continent').out().limit(5)",
+ equals: "g.V().hasLabel(\"airport\",\"continent\").out().limit(5)",
},
{
assert: func(g *GraphTraversalSource) *GraphTraversal {
return g.V().HasLabel("airport").Out().Values("code").Limit(5)
},
- equals: "g.V().hasLabel('airport').out().values('code').limit(5)",
+ equals: "g.V().hasLabel(\"airport\").out().values(\"code\").limit(5)",
},
{
assert: func(g *GraphTraversalSource) *GraphTraversal {
return g.V("3").As("a").Out("route").Limit(10).Where(P.Eq("a")).By("region")
},
- equals: "g.V('3').as('a').out('route').limit(10).where(eq('a')).by('region')",
+ equals: "g.V(\"3\").as(\"a\").out(\"route\").limit(10).where(eq(\"a\")).by(\"region\")",
},
{
assert: func(g *GraphTraversalSource) *GraphTraversal {
return g.V("3").Repeat(T__.Out("route").SimplePath()).Times(2).Path().By("code")
},
- equals: "g.V('3').repeat(out('route').simplePath()).times(2).path().by('code')",
+ equals: "g.V(\"3\").repeat(__.out(\"route\").simplePath()).times(2).path().by(\"code\")",
},
{
assert: func(g *GraphTraversalSource) *GraphTraversal {
return g.V().HasLabel("airport").Out().Has("region", "US-TX").Values("code").Limit(5)
},
- equals: "g.V().hasLabel('airport').out().has('region','US-TX').values('code').limit(5)",
+ equals: "g.V().hasLabel(\"airport\").out().has(\"region\",\"US-TX\").values(\"code\").limit(5)",
},
{
assert: func(g *GraphTraversalSource) *GraphTraversal {
return g.V().HasLabel("airport").Union(T__.Values("city"), T__.Values("region")).Limit(5)
},
- equals: "g.V().hasLabel('airport').union(values('city'),values('region')).limit(5)",
+ equals: "g.V().hasLabel(\"airport\").union(__.values(\"city\"),__.values(\"region\")).limit(5)",
},
{
assert: func(g *GraphTraversalSource) *GraphTraversal { return g.V("3").As("a").Out("route", "routes") },
- equals: "g.V('3').as('a').out('route','routes')",
+ equals: "g.V(\"3\").as(\"a\").out(\"route\",\"routes\")",
},
{
assert: func(g *GraphTraversalSource) *GraphTraversal { return g.V().Where(T__.Values("runways").Is(5)) },
- equals: "g.V().where(values('runways').is(5))",
+ equals: "g.V().where(__.values(\"runways\").is(5))",
},
{
assert: func(g *GraphTraversalSource) *GraphTraversal {
return g.V("3").Repeat(T__.Out().SimplePath()).Until(T__.Has("code", "AGR")).Path().By("code").Limit(5)
},
- equals: "g.V('3').repeat(out().simplePath()).until(has('code','AGR')).path().by('code').limit(5)",
+ equals: "g.V(\"3\").repeat(__.out().simplePath()).until(__.has(\"code\",\"AGR\")).path().by(\"code\").limit(5)",
},
{
assert: func(g *GraphTraversalSource) *GraphTraversal { return g.V().HasLabel("airport").Order().By(T__.Id()) },
- equals: "g.V().hasLabel('airport').order().by(id())",
+ equals: "g.V().hasLabel(\"airport\").order().by(__.id())",
},
{
assert: func(g *GraphTraversalSource) *GraphTraversal { return g.V().HasLabel("airport").Order().By(T.Id) },
- equals: "g.V().hasLabel('airport').order().by(id)",
+ equals: "g.V().hasLabel(\"airport\").order().by(T.id)",
},
{
assert: func(g *GraphTraversalSource) *GraphTraversal {
return g.V().HasLabel("airport").Order().By(T__.Id(), Order.Desc)
},
- equals: "g.V().hasLabel('airport').order().by(id(),desc)",
+ equals: "g.V().hasLabel(\"airport\").order().by(__.id(),Order.desc)",
},
{
assert: func(g *GraphTraversalSource) *GraphTraversal {
return g.V().HasLabel("airport").Order().By("code", Order.Desc)
},
- equals: "g.V().hasLabel('airport').order().by('code',desc)",
+ equals: "g.V().hasLabel(\"airport\").order().by(\"code\",Order.desc)",
},
{
assert: func(g *GraphTraversalSource) *GraphTraversal {
return g.V("1", "2", "3").Local(T__.Out().Out().Dedup().Fold())
},
- equals: "g.V('1','2','3').local(out().out().dedup().fold())",
+ equals: "g.V(\"1\",\"2\",\"3\").local(__.out().out().dedup().fold())",
},
{
assert: func(g *GraphTraversalSource) *GraphTraversal {
return g.V("3").Out().Path().Count(Scope.Local)
},
- equals: "g.V('3').out().path().count(local)",
+ equals: "g.V(\"3\").out().path().count(Scope.local)",
},
{
assert: func(g *GraphTraversalSource) *GraphTraversal {
@@ -160,79 +163,79 @@ func Test_translator_Translate(t *testing.T) {
assert: func(g *GraphTraversalSource) *GraphTraversal {
return g.V("5").OutE("route").InV().Path().Limit(10)
},
- equals: "g.V('5').outE('route').inV().path().limit(10)",
+ equals: "g.V(\"5\").outE(\"route\").inV().path().limit(10)",
},
{
assert: func(g *GraphTraversalSource) *GraphTraversal {
return g.V("5").PropertyMap().Select(Column.Keys)
},
- equals: "g.V('5').propertyMap().select(keys)",
+ equals: "g.V(\"5\").propertyMap().select(Column.keys)",
},
{
assert: func(g *GraphTraversalSource) *GraphTraversal {
return g.V("5").PropertyMap().Select(Column.Values)
},
- equals: "g.V('5').propertyMap().select(values)",
+ equals: "g.V(\"5\").propertyMap().select(Column.values)",
},
{
assert: func(g *GraphTraversalSource) *GraphTraversal {
return g.V("3").Values("runways").Math("_ + 1")
},
- equals: "g.V('3').values('runways').math('_ + 1')",
+ equals: "g.V(\"3\").values(\"runways\").math(\"_ + 1\")",
},
{
assert: func(g *GraphTraversalSource) *GraphTraversal {
return g.V("3").Emit().Repeat(T__.Out().SimplePath()).Times(3).Limit(5).Path()
},
- equals: "g.V('3').emit().repeat(out().simplePath()).times(3).limit(5).path()",
+ equals: "g.V(\"3\").emit().repeat(__.out().simplePath()).times(3).limit(5).path()",
},
{
assert: func(g *GraphTraversalSource) *GraphTraversal {
return g.V().Match(T__.As("a").Has("code", "LHR").As("b")).Select("b").By("code")
},
- equals: "g.V().match(as('a').has('code','LHR').as('b')).select('b').by('code')",
+ equals: "g.V().match(__.as(\"a\").has(\"code\",\"LHR\").as(\"b\")).select(\"b\").by(\"code\")",
},
{
assert: func(g *GraphTraversalSource) *GraphTraversal {
return g.V().Has("test-using-keyword-as-property", "repeat")
},
- equals: "g.V().has('test-using-keyword-as-property','repeat')",
+ equals: "g.V().has(\"test-using-keyword-as-property\",\"repeat\")",
},
{
assert: func(g *GraphTraversalSource) *GraphTraversal {
return g.V("1").AddE("test").To(T__.V("4"))
},
- equals: "g.V('1').addE('test').to(V('4'))",
+ equals: "g.V(\"1\").addE(\"test\").to(__.V(\"4\"))",
},
{
assert: func(g *GraphTraversalSource) *GraphTraversal {
return g.V().Values("runways").Max()
},
- equals: "g.V().values('runways').max()",
+ equals: "g.V().values(\"runways\").max()",
},
{
assert: func(g *GraphTraversalSource) *GraphTraversal {
return g.V().Values("runways").Min()
},
- equals: "g.V().values('runways').min()",
+ equals: "g.V().values(\"runways\").min()",
},
{
assert: func(g *GraphTraversalSource) *GraphTraversal {
return g.V().Values("runways").Sum()
},
- equals: "g.V().values('runways').sum()",
+ equals: "g.V().values(\"runways\").sum()",
},
{
assert: func(g *GraphTraversalSource) *GraphTraversal {
return g.V().Values("runways").Mean()
},
- equals: "g.V().values('runways').mean()",
+ equals: "g.V().values(\"runways\").mean()",
},
{
assert: func(g *GraphTraversalSource) *GraphTraversal {
return g.WithSack(0).V("3", "5").Sack(Operator.Sum).By("runways").Sack()
},
- equals: "g.withSack(0).V('3','5').sack(sum).by('runways').sack()",
+ equals: "g.withSack(0).V(\"3\",\"5\").sack(Operator.sum).by(\"runways\").sack()",
},
{
assert: func(g *GraphTraversalSource) *GraphTraversal {
@@ -256,115 +259,115 @@ func Test_translator_Translate(t *testing.T) {
assert: func(g *GraphTraversalSource) *GraphTraversal {
return g.V().Has("runways", P.Gt(5)).Count()
},
- equals: "g.V().has('runways',gt(5)).count()",
+ equals: "g.V().has(\"runways\",gt(5)).count()",
},
{
assert: func(g *GraphTraversalSource) *GraphTraversal {
return g.V().Has("runways", P.Lte(5.3)).Count()
},
- equals: "g.V().has('runways',lte(5.3)).count()",
+ equals: "g.V().has(\"runways\",lte(5.3D)).count()",
},
{
assert: func(g *GraphTraversalSource) *GraphTraversal {
return g.V().Has("code", P.Within(123, 124))
},
- equals: "g.V().has('code',within([123,124]))",
+ equals: "g.V().has(\"code\",within([123,124]))",
},
{
assert: func(g *GraphTraversalSource) *GraphTraversal {
return g.V().Has("code", P.Within(123, "abc"))
},
- equals: "g.V().has('code',within([123,'abc']))",
+ equals: "g.V().has(\"code\",within([123,\"abc\"]))",
},
{
assert: func(g *GraphTraversalSource) *GraphTraversal {
return g.V().Has("code", P.Within("abc", 123))
},
- equals: "g.V().has('code',within(['abc',123]))",
+ equals: "g.V().has(\"code\",within([\"abc\",123]))",
},
{
assert: func(g *GraphTraversalSource) *GraphTraversal {
return g.V().Has("code", P.Within("abc", "xyz"))
},
- equals: "g.V().has('code',within(['abc','xyz']))",
+ equals: "g.V().has(\"code\",within([\"abc\",\"xyz\"]))",
},
{
assert: func(g *GraphTraversalSource) *GraphTraversal {
return g.V("1", "2").Has("region", P.Within("US-TX", "US-GA"))
},
- equals: "g.V('1','2').has('region',within(['US-TX','US-GA']))",
+ equals: "g.V(\"1\",\"2\").has(\"region\",within([\"US-TX\",\"US-GA\"]))",
},
{
assert: func(g *GraphTraversalSource) *GraphTraversal {
return g.V().And(T__.Has("runways", P.Gt(5)), T__.Has("region", "US-TX"))
},
- equals: "g.V().and(has('runways',gt(5)),has('region','US-TX'))",
+ equals: "g.V().and(__.has(\"runways\",gt(5)),__.has(\"region\",\"US-TX\"))",
},
{
assert: func(g *GraphTraversalSource) *GraphTraversal {
return g.V().Union(T__.Has("runways", P.Gt(5)), T__.Has("region", "US-TX"))
},
- equals: "g.V().union(has('runways',gt(5)),has('region','US-TX'))",
+ equals: "g.V().union(__.has(\"runways\",gt(5)),__.has(\"region\",\"US-TX\"))",
},
{
assert: func(g *GraphTraversalSource) *GraphTraversal {
return g.V("3").Choose(T__.Values("runways").Is(3), T__.Constant("three"), T__.Constant("not three"))
},
- equals: "g.V('3').choose(values('runways').is(3),constant('three'),constant('not three'))",
+ equals: "g.V(\"3\").choose(__.values(\"runways\").is(3),__.constant(\"three\"),__.constant(\"not three\"))",
},
{
assert: func(g *GraphTraversalSource) *GraphTraversal {
return g.V("3").Choose(T__.Values("runways")).Option(1, T__.Constant("three")).Option(2, T__.Constant("not three"))
},
- equals: "g.V('3').choose(values('runways')).option(1,constant('three')).option(2,constant('not three'))",
+ equals: "g.V(\"3\").choose(__.values(\"runways\")).option(1,__.constant(\"three\")).option(2,__.constant(\"not three\"))",
},
{
assert: func(g *GraphTraversalSource) *GraphTraversal {
return g.V("3").Choose(T__.Values("runways")).Option(1.5, T__.Constant("one and a half")).Option(2, T__.Constant("not three"))
},
- equals: "g.V('3').choose(values('runways')).option(1.5,constant('one and a half')).option(2,constant('not three'))",
+ equals: "g.V(\"3\").choose(__.values(\"runways\")).option(1.5D,__.constant(\"one and a half\")).option(2,__.constant(\"not three\"))",
},
{
assert: func(g *GraphTraversalSource) *GraphTraversal {
return g.V("3").Repeat(T__.Out().SimplePath()).Until(T__.Loops().Is(1)).Count()
},
- equals: "g.V('3').repeat(out().simplePath()).until(loops().is(1)).count()",
+ equals: "g.V(\"3\").repeat(__.out().simplePath()).until(__.loops().is(1)).count()",
},
{
assert: func(g *GraphTraversalSource) *GraphTraversal {
return g.V().HasLabel("airport").Limit(20).Group().By("region").By("code").Order(Scope.Local).By(Column.Keys)
},
- equals: "g.V().hasLabel('airport').limit(20).group().by('region').by('code').order(local).by(keys)",
+ equals: "g.V().hasLabel(\"airport\").limit(20).group().by(\"region\").by(\"code\").order(Scope.local).by(Column.keys)",
},
{
assert: func(g *GraphTraversalSource) *GraphTraversal {
return g.V("1").As("a").V("2").As("a").Select(Pop.All, "a")
},
- equals: "g.V('1').as('a').V('2').as('a').select(all,'a')",
+ equals: "g.V(\"1\").as(\"a\").V(\"2\").as(\"a\").select(Pop.all,\"a\")",
},
{
assert: func(g *GraphTraversalSource) *GraphTraversal {
return g.AddV("test").Property(Cardinality.Set, "p1", 10)
},
- equals: "g.addV('test').property(set,'p1',10)",
+ equals: "g.addV(\"test\").property(Cardinality.set,\"p1\",10)",
},
{
assert: func(g *GraphTraversalSource) *GraphTraversal {
return g.AddV("test").Property(Cardinality.List, "p1", 10)
},
- equals: "g.addV('test').property(list,'p1',10)",
+ equals: "g.addV(\"test\").property(Cardinality.list,\"p1\",10)",
},
{
assert: func(g *GraphTraversalSource) *GraphTraversal {
return g.AddV("test").Property(Cardinality.Single, "p1", 10)
},
- equals: "g.addV('test').property(single,'p1',10)",
+ equals: "g.addV(\"test\").property(Cardinality.single,\"p1\",10)",
},
{
assert: func(g *GraphTraversalSource) *GraphTraversal {
return g.V().Limit(5).Order().By(T__.Label())
},
- equals: "g.V().limit(5).order().by(label())",
+ equals: "g.V().limit(5).order().by(__.label())",
},
{
assert: func(g *GraphTraversalSource) *GraphTraversal {
@@ -376,67 +379,67 @@ func Test_translator_Translate(t *testing.T) {
assert: func(g *GraphTraversalSource) *GraphTraversal {
return g.AddV("test").Property("p1", 123)
},
- equals: "g.addV('test').property('p1',123)",
+ equals: "g.addV(\"test\").property(\"p1\",123)",
},
{
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(new Date(121,1,1,9,30,0)))",
+ equals: "g.V().has(\"date\",gt(datetime(\"2021-01-01T09:30:00Z\")))",
},
{
assert: func(g *GraphTraversalSource) *GraphTraversal {
return g.AddE("route").From(T__.V("1")).To(T__.V("2"))
},
- equals: "g.addE('route').from(V('1')).to(V('2'))",
+ equals: "g.addE(\"route\").from(__.V(\"1\")).to(__.V(\"2\"))",
},
{
assert: func(g *GraphTraversalSource) *GraphTraversal {
return g.WithSideEffect("a", []interface{}{1, 2}).V("3").Select("a")
},
- equals: "g.withSideEffect('a',[1,2]).V('3').select('a')",
+ equals: "g.withSideEffect(\"a\",[1,2]).V(\"3\").select(\"a\")",
},
{
assert: func(g *GraphTraversalSource) *GraphTraversal {
return g.WithSideEffect("a", 1).V("3").Select("a")
},
- equals: "g.withSideEffect('a',1).V('3').select('a')",
+ equals: "g.withSideEffect(\"a\",1).V(\"3\").select(\"a\")",
},
{
assert: func(g *GraphTraversalSource) *GraphTraversal {
return g.WithSideEffect("a", "abc").V("3").Select("a")
},
- equals: "g.withSideEffect('a','abc').V('3').select('a')",
+ equals: "g.withSideEffect(\"a\",\"abc\").V(\"3\").select(\"a\")",
},
{
assert: func(g *GraphTraversalSource) *GraphTraversal {
return g.V().Has("airport", "region", "US-NM").Limit(3).Values("elev").Fold().Index()
},
- equals: "g.V().has('airport','region','US-NM').limit(3).values('elev').fold().index()",
+ equals: "g.V().has(\"airport\",\"region\",\"US-NM\").limit(3).values(\"elev\").fold().index()",
},
{
assert: func(g *GraphTraversalSource) *GraphTraversal {
return g.V("3").Repeat(T__.TimeLimit(1000).Out().SimplePath()).Until(T__.Has("code", "AGR")).Path()
},
- equals: "g.V('3').repeat(timeLimit(1000).out().simplePath()).until(has('code','AGR')).path()",
+ equals: "g.V(\"3\").repeat(__.timeLimit(1000).out().simplePath()).until(__.has(\"code\",\"AGR\")).path()",
},
{
assert: func(g *GraphTraversalSource) *GraphTraversal {
return g.V().HasLabel("airport").Where(T__.Values("elev").Is(P.Gt(14000)))
},
- equals: "g.V().hasLabel('airport').where(values('elev').is(gt(14000)))",
+ equals: "g.V().hasLabel(\"airport\").where(__.values(\"elev\").is(gt(14000)))",
},
{
assert: func(g *GraphTraversalSource) *GraphTraversal {
return g.V().HasLabel("airport").Where(T__.Out().Count().Is(P.Gt(250))).Values("code")
},
- equals: "g.V().hasLabel('airport').where(out().count().is(gt(250))).values('code')",
+ equals: "g.V().hasLabel(\"airport\").where(__.out().count().is(gt(250))).values(\"code\")",
},
{
assert: func(g *GraphTraversalSource) *GraphTraversal {
return g.V().HasLabel("airport").Filter(T__.Out().Count().Is(P.Gt(250))).Values("code")
},
- equals: "g.V().hasLabel('airport').filter(out().count().is(gt(250))).values('code')",
+ equals: "g.V().hasLabel(\"airport\").filter(__.out().count().is(gt(250))).values(\"code\")",
},
{
assert: func(g *GraphTraversalSource) *GraphTraversal {
@@ -448,191 +451,228 @@ func Test_translator_Translate(t *testing.T) {
Local(T__.Union(T__.Path().By("code").By("dist"), T__.Sack()).Fold()).
Limit(10)
},
- equals: "g.withSack(0).V('3').repeat(outE('route').sack(sum).by('dist').inV()).until(has('code','AGR').or().loops().is(4)).has('code','AGR').local(union(path().by('code').by('dist'),sack()).fold()).limit(10)",
+ equals: "g.withSack(0).V(\"3\").repeat(__.outE(\"route\").sack(Operator.sum).by(\"dist\").inV()).until(__.has(\"code\",\"AGR\").or().loops().is(4)).has(\"code\",\"AGR\").local(__.union(__.path().by(\"code\").by(\"dist\"),__.sack()).fold()).limit(10)",
},
{
assert: func(g *GraphTraversalSource) *GraphTraversal {
return g.AddV().As("a").AddV().As("b").AddE("knows").From("a").To("b")
},
- equals: "g.addV().as('a').addV().as('b').addE('knows').from('a').to('b')",
+ equals: "g.addV().as(\"a\").addV().as(\"b\").addE(\"knows\").from(\"a\").to(\"b\")",
},
{
assert: func(g *GraphTraversalSource) *GraphTraversal {
return g.AddV("Person").As("a").AddV("Person").As("b").AddE("knows").From("a").To("b")
},
- equals: "g.addV('Person').as('a').addV('Person').as('b').addE('knows').from('a').to('b')",
+ equals: "g.addV(\"Person\").as(\"a\").addV(\"Person\").as(\"b\").addE(\"knows\").from(\"a\").to(\"b\")",
},
{
assert: func(g *GraphTraversalSource) *GraphTraversal {
return g.V("3").Project("Out", "In").By(T__.Out().Count()).By(T__.In().Count())
},
- equals: "g.V('3').project('Out','In').by(out().count()).by(in().count())",
+ equals: "g.V(\"3\").project(\"Out\",\"In\").by(__.out().count()).by(__.in().count())",
},
{
assert: func(g *GraphTraversalSource) *GraphTraversal {
return g.V("44").Out().Aggregate("a").Out().Where(P.Within("a")).Path()
},
- equals: "g.V('44').out().aggregate('a').out().where(within('a')).path()",
+ equals: "g.V(\"44\").out().aggregate(\"a\").out().where(within(\"a\")).path()",
},
{
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',new Date(121,2,22,0,0,0))",
+ equals: "g.V().has(\"date\",datetime(\"2021-02-22T00:00:00Z\"))",
},
{
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([new Date(121,2,22,0,0,0),new Date(121,1,1,0,0,0)]))",
+ equals: "g.V().has(\"date\",within([datetime(\"2021-02-22T00:00:00Z\"),datetime(\"2021-01-01T00:00:00Z\")]))",
},
{
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(new Date(121,1,1,0,0,0),new Date(121,2,22,0,0,0)))",
+ equals: "g.V().has(\"date\",between(datetime(\"2021-01-01T00:00:00Z\"),datetime(\"2021-02-22T00:00:00Z\")))",
},
{
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(new Date(121,1,1,0,0,0),new Date(121,2,22,0,0,0)))",
+ equals: "g.V().has(\"date\",inside(datetime(\"2021-01-01T00:00:00Z\"),datetime(\"2021-02-22T00:00:00Z\")))",
},
{
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(new Date(121,1,1,9,30,0)))",
+ equals: "g.V().has(\"date\",gt(datetime(\"2021-01-01T09:30:00Z\")))",
},
{
assert: func(g *GraphTraversalSource) *GraphTraversal {
return g.V().Has("runways", P.Between(3, 5))
},
- equals: "g.V().has('runways',between(3,5))",
+ equals: "g.V().has(\"runways\",between(3,5))",
},
{
assert: func(g *GraphTraversalSource) *GraphTraversal {
return g.V().Has("runways", P.Inside(3, 5))
},
- equals: "g.V().has('runways',inside(3,5))",
+ equals: "g.V().has(\"runways\",inside(3,5))",
},
{
assert: func(g *GraphTraversalSource) *GraphTraversal {
return g.V("44").OutE().ElementMap()
},
- equals: "g.V('44').outE().elementMap()",
+ equals: "g.V(\"44\").outE().elementMap()",
},
{
assert: func(g *GraphTraversalSource) *GraphTraversal {
return g.V("44").ValueMap().By(T__.Unfold())
},
- equals: "g.V('44').valueMap().by(unfold())",
+ equals: "g.V(\"44\").valueMap().by(__.unfold())",
},
{
assert: func(g *GraphTraversalSource) *GraphTraversal {
return g.V("44").ValueMap().With(WithOptions.Tokens, WithOptions.Labels)
},
- equals: "g.V('44').valueMap().with(WithOptions.tokens,WithOptions.labels)",
+ equals: "g.V(\"44\").valueMap().with(\"~tinkerpop.valueMap.tokens\",2)",
},
{
assert: func(g *GraphTraversalSource) *GraphTraversal {
return g.V("44").ValueMap().With(WithOptions.All)
},
- equals: "g.V('44').valueMap().with(WithOptions.all)",
+ equals: "g.V(\"44\").valueMap().with(15)",
},
{
assert: func(g *GraphTraversalSource) *GraphTraversal {
return g.V("44").ValueMap().With(WithOptions.Indexer)
},
- equals: "g.V('44').valueMap().with(WithOptions.indexer)",
+ equals: "g.V(\"44\").valueMap().with(\"~tinkerpop.index.indexer\")",
},
{
assert: func(g *GraphTraversalSource) *GraphTraversal {
return g.V("44").ValueMap().With(WithOptions.Tokens)
},
- equals: "g.V('44').valueMap().with(WithOptions.tokens)",
+ equals: "g.V(\"44\").valueMap().with(\"~tinkerpop.valueMap.tokens\")",
},
{
assert: func(g *GraphTraversalSource) *GraphTraversal {
return g.WithStrategies(ReadOnlyStrategy()).AddV("test")
},
- equals: "g.withStrategies(new ReadOnlyStrategy()).addV('test')",
+ equals: "g.withStrategies(ReadOnlyStrategy).addV(\"test\")",
},
{
assert: func(g *GraphTraversalSource) *GraphTraversal {
return g.WithStrategies(SubgraphStrategy(SubgraphStrategyConfig{Vertices: T__.Has("region", "US-TX"), Edges: T__.HasLabel("route")})).V().Count()
},
containsRandomClassParams: true,
- equals: "g.withStrategies(new SubgraphStrategy(vertices:has('region','US-TX'),edges:hasLabel('route'))).V().count()",
+ equals: "g.withStrategies(new SubgraphStrategy(vertices:__.has(\"region\",\"US-TX\"),edges:__.hasLabel(\"route\"))).V().count()",
},
{
assert: func(g *GraphTraversalSource) *GraphTraversal {
return g.WithStrategies(SubgraphStrategy(SubgraphStrategyConfig{VertexProperties: T__.HasNot("runways")})).V().Count()
},
- equals: "g.withStrategies(new SubgraphStrategy(vertexProperties:hasNot('runways'))).V().count()",
+ equals: "g.withStrategies(new SubgraphStrategy(vertexProperties:__.hasNot(\"runways\"))).V().count()",
},
{
assert: func(g *GraphTraversalSource) *GraphTraversal {
return g.WithStrategies(SubgraphStrategy(SubgraphStrategyConfig{Vertices: T__.Has("region", "US-TX"), VertexProperties: T__.HasNot("runways")})).V().Count()
},
containsRandomClassParams: true,
- equals: "g.withStrategies(new SubgraphStrategy(vertices:has('region','US-TX'),vertexProperties:hasNot('runways'))).V().count()",
+ equals: "g.withStrategies(new SubgraphStrategy(vertices:__.has(\"region\",\"US-TX\"),vertexProperties:__.hasNot(\"runways\"))).V().count()",
},
{
assert: func(g *GraphTraversalSource) *GraphTraversal {
return g.WithStrategies(ReadOnlyStrategy(), SubgraphStrategy(SubgraphStrategyConfig{Vertices: T__.Has("region", "US-TX"), Edges: T__.HasLabel("route")})).V().Count()
},
containsRandomClassParams: true,
- equals: "g.withStrategies(new ReadOnlyStrategy(),new SubgraphStrategy(vertices:has('region','US-TX'),edges:hasLabel('route'))).V().count()",
+ equals: "g.withStrategies(ReadOnlyStrategy,new SubgraphStrategy(vertices:__.has(\"region\",\"US-TX\"),edges:__.hasLabel(\"route\"))).V().count()",
},
{
assert: func(g *GraphTraversalSource) *GraphTraversal {
return g.WithStrategies(ReadOnlyStrategy(), SubgraphStrategy(SubgraphStrategyConfig{Vertices: T__.Has("region", "US-TX")})).V().Count()
},
- equals: "g.withStrategies(new ReadOnlyStrategy(),new SubgraphStrategy(vertices:has('region','US-TX'))).V().count()",
+ equals: "g.withStrategies(ReadOnlyStrategy,new SubgraphStrategy(vertices:__.has(\"region\",\"US-TX\"))).V().count()",
},
{
assert: func(g *GraphTraversalSource) *GraphTraversal {
return g.WithStrategies(OptionsStrategy(map[string]interface{}{"evaluationTimeout": 500})).V().Count()
},
- equals: "g.withStrategies(new OptionsStrategy(evaluationTimeout:500)).V().count()",
+ // OptionsStrategy are now extracted into request message and is no longer sent with the script
+ equals: "g.V().count()",
},
{
assert: func(g *GraphTraversalSource) *GraphTraversal {
return g.WithStrategies(PartitionStrategy(PartitionStrategyConfig{PartitionKey: "partition", WritePartition: "a", ReadPartitions: NewSimpleSet("a")})).AddV("test")
},
containsRandomClassParams: true,
- equals: "g.withStrategies(new PartitionStrategy(includeMetaProperties:false,partitionKey:'partition',writePartition:'a',readPartitions:['a'])).addV('test')",
+ equals: "g.withStrategies(new PartitionStrategy(includeMetaProperties:false,partitionKey:\"partition\",writePartition:\"a\",readPartitions:[\"a\"])).addV(\"test\")",
},
{
assert: func(g *GraphTraversalSource) *GraphTraversal {
return g.WithStrategies(VertexProgramStrategy(VertexProgramStrategyConfig{})).V().ShortestPath().With("~tinkerpop.shortestPath.target", T__.Has("name", "peter"))
},
- equals: "g.withStrategies(new VertexProgramStrategy()).V().shortestPath().with('~tinkerpop.shortestPath.target',has('name','peter'))",
+ equals: "g.withStrategies(VertexProgramStrategy).V().shortestPath().with(\"~tinkerpop.shortestPath.target\",__.has(\"name\",\"peter\"))",
},
{
assert: func(g *GraphTraversalSource) *GraphTraversal {
return g.V().Has("p1", TextP.StartingWith("foo"))
},
- equals: "g.V().has('p1',startingWith('foo'))",
+ equals: "g.V().has(\"p1\",startingWith(\"foo\"))",
},
{
assert: func(g *GraphTraversalSource) *GraphTraversal {
return g.V().Has("p1", TextP.EndingWith("foo"))
},
- equals: "g.V().has('p1',endingWith('foo'))",
+ equals: "g.V().has(\"p1\",endingWith(\"foo\"))",
},
{
assert: func(g *GraphTraversalSource) *GraphTraversal {
return g.V().Has("p1", TextP.Containing("foo"))
},
- equals: "g.V().has('p1',containing('foo'))",
+ equals: "g.V().has(\"p1\",containing(\"foo\"))",
},
{
assert: func(g *GraphTraversalSource) *GraphTraversal {
return g.V().Has("p1", nil)
},
- equals: "g.V().has('p1',null)",
+ equals: "g.V().has(\"p1\",null)",
+ },
+ {
+ assert: func(g *GraphTraversalSource) *GraphTraversal {
+ return g.V().HasLabel("person").Values("age").Is(P.Between(26, 30).Or(P.Gt(34)))
+ },
+ equals: "g.V().hasLabel(\"person\").values(\"age\").is(between(26,30).or(gt(34)))",
+ },
+ {
+ assert: func(g *GraphTraversalSource) *GraphTraversal {
+ return g.Inject(1).AsNumber(GType.Double)
+ },
+ equals: "g.inject(1).asNumber(GType.DOUBLE)",
+ },
+ {
+ assert: func(g *GraphTraversalSource) *GraphTraversal {
+ return g.Inject(uuid.MustParse("f47af10b-58cc-4372-a567-0f02b2f3d479"))
+ },
+ equals: "g.inject(UUID(\"f47af10b-58cc-4372-a567-0f02b2f3d479\"))",
+ },
+ {
+ assert: func(g *GraphTraversalSource) *GraphTraversal {
+ return g.V().Filter(T__.Has("name", TextP.StartingWith("m").Or(TextP.StartingWith("p"))))
+ },
+ equals: "g.V().filter(__.has(\"name\",startingWith(\"m\").or(startingWith(\"p\"))))",
+ },
+ {
+ assert: func(g *GraphTraversalSource) *GraphTraversal {
+ return g.V().Has("person", "age", P.Within())
+ },
+ equals: "g.V().has(\"person\",\"age\",within())",
+ },
+ {
+ assert: func(g *GraphTraversalSource) *GraphTraversal {
+ return g.Inject(math.NaN()).Is(P.Eq(math.NaN()))
+ },
+ equals: "g.inject(NaN).is(eq(NaN))",
},
}
@@ -662,26 +702,17 @@ func Test_translator_Translate(t *testing.T) {
}
t.Run(testName, func(t *testing.T) {
- tr := &translator{
- source: "g",
- }
g := NewGraphTraversalSource(nil, nil)
- bytecode := tt.assert(g).Bytecode
- got, err := tr.Translate(bytecode)
- if (err != nil) != tt.wantErr {
- t.Errorf("translator.Translate() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- if !tt.containsRandomClassParams && got != tt.equals {
- t.Errorf("translator.Translate() = %v, equals %v", got, tt.equals)
+ gremlinLang := tt.assert(g).GremlinLang.GetGremlin()
+ if !tt.containsRandomClassParams && gremlinLang != tt.equals {
+ t.Errorf("GremlinLang = %v, equals %v", gremlinLang, tt.equals)
}
-
if tt.containsRandomClassParams {
- equalsParams := getParams(tt.equals)
- gotParams := getParams(got)
+ equalsParams := getArgs(tt.equals)
+ gotParams := getArgs(gremlinLang)
if len(equalsParams) != len(gotParams) {
- t.Errorf("translator.Translate() = %v, equals %v", got, tt.equals)
+ t.Errorf("GremlinLang = %v, equals %v", gremlinLang, tt.equals)
}
for _, equalsParam := range equalsParams {
@@ -693,7 +724,7 @@ func Test_translator_Translate(t *testing.T) {
}
}
if !found {
- t.Errorf("translator.Translate() = %v, equals %v", got, tt.equals)
+ t.Errorf("translator.Translate() = %v, equals %v", gremlinLang, tt.equals)
}
}
}
@@ -701,7 +732,7 @@ func Test_translator_Translate(t *testing.T) {
}
}
-func getParams(result string) []string {
+func getArgs(result string) []string {
pattern := `(\w+:.*?)`
re := regexp.MustCompile(pattern)
matches := re.FindAllStringSubmatch(result, -1)
diff --git a/gremlin-go/driver/httpConnection.go b/gremlin-go/driver/httpConnection.go
new file mode 100644
index 0000000000..4f034e0e65
--- /dev/null
+++ b/gremlin-go/driver/httpConnection.go
@@ -0,0 +1,277 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements. See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership. The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License. You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied. See the License for the
+specific language governing permissions and limitations
+under the License.
+*/
+
+package gremlingo
+
+import (
+ "bytes"
+ "compress/zlib"
+ "crypto/sha256"
+ "encoding/hex"
+ "io"
+ "net"
+ "net/http"
+ "net/url"
+ "time"
+)
+
+// Common HTTP header keys
+const (
+ HeaderContentType = "Content-Type"
+ HeaderAccept = "Accept"
+ HeaderUserAgent = "User-Agent"
+ HeaderAcceptEncoding = "Accept-Encoding"
+ HeaderAuthorization = "Authorization"
+)
+
+// HttpRequest represents an HTTP request that can be modified by interceptors.
+type HttpRequest struct {
+ Method string
+ URL *url.URL
+ Headers http.Header
+ Body []byte
+}
+
+// NewHttpRequest creates a new HttpRequest with the given method and URL.
+func NewHttpRequest(method, rawURL string) (*HttpRequest, error) {
+ u, err := url.Parse(rawURL)
+ if err != nil {
+ return nil, err
+ }
+ return &HttpRequest{
+ Method: method,
+ URL: u,
+ Headers: make(http.Header),
+ }, nil
+}
+
+// ToStdRequest converts HttpRequest to a standard http.Request for signing.
+// Returns nil if the request cannot be created (invalid method or URL).
+func (r *HttpRequest) ToStdRequest() (*http.Request, error) {
+ req, err := http.NewRequest(r.Method, r.URL.String(), bytes.NewReader(r.Body))
+ if err != nil {
+ return nil, err
+ }
+ req.Header = r.Headers
+ return req, nil
+}
+
+// PayloadHash returns the SHA256 hash of the request body for SigV4 signing.
+func (r *HttpRequest) PayloadHash() string {
+ if len(r.Body) == 0 {
+ return "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" // SHA256 of empty string
+ }
+ h := sha256.Sum256(r.Body)
+ return hex.EncodeToString(h[:])
+}
+
+// RequestInterceptor is a function that modifies an HTTP request before it is sent.
+type RequestInterceptor func(*HttpRequest) error
+
+// httpConnection handles HTTP request/response for Gremlin queries
+type httpConnection struct {
+ url string
+ httpClient *http.Client
+ connSettings *connectionSettings
+ logHandler *logHandler
+ serializer *GraphBinarySerializer
+ interceptors []RequestInterceptor
+}
+
+// Connection pool defaults aligned with Java driver
+const (
+ defaultMaxConnsPerHost = 128 // Java: ConnectionPool.MAX_POOL_SIZE
+ defaultMaxIdleConnsPerHost = 8 // Keep some connections warm
+ defaultIdleConnTimeout = 180 * time.Second // Java: CONNECTION_IDLE_TIMEOUT_MILLIS
+ defaultConnectionTimeout = 15 * time.Second // Java: CONNECTION_SETUP_TIMEOUT_MILLIS
+)
+
+func newHttpConnection(handler *logHandler, url string, connSettings *connectionSettings) *httpConnection {
+ connectionTimeout := connSettings.connectionTimeout
+ if connectionTimeout == 0 {
+ connectionTimeout = defaultConnectionTimeout
+ }
+
+ transport := &http.Transport{
+ DialContext: (&net.Dialer{
+ Timeout: connectionTimeout, // Connection setup timeout only
+ KeepAlive: 30 * time.Second,
+ }).DialContext,
+ TLSClientConfig: connSettings.tlsConfig,
+ MaxConnsPerHost: defaultMaxConnsPerHost,
+ MaxIdleConnsPerHost: defaultMaxIdleConnsPerHost,
+ IdleConnTimeout: defaultIdleConnTimeout,
+ DisableCompression: !connSettings.enableCompression,
+ }
+
+ return &httpConnection{
+ url: url,
+ httpClient: &http.Client{Transport: transport}, // No Timeout - allows streaming
+ connSettings: connSettings,
+ logHandler: handler,
+ serializer: newGraphBinarySerializer(handler),
+ }
+}
+
+// AddInterceptor adds a request interceptor to the chain.
+func (c *httpConnection) AddInterceptor(interceptor RequestInterceptor) {
+ c.interceptors = append(c.interceptors, interceptor)
+}
+
+// send sends request and streams results directly to ResultSet
+func (c *httpConnection) send(req *request) (ResultSet, error) {
+ rs := newChannelResultSet()
+
+ data, err := c.serializer.SerializeMessage(req)
+ if err != nil {
+ rs.Close()
+ return rs, err
+ }
+
+ go c.executeAndStream(data, rs)
+
+ return rs, nil
+}
+
+func (c *httpConnection) executeAndStream(data []byte, rs ResultSet) {
+ defer rs.Close()
+
+ // Create HttpRequest for interceptors
+ httpReq, err := NewHttpRequest(http.MethodPost, c.url)
+ if err != nil {
+ c.logHandler.logf(Error, failedToSendRequest, err.Error())
+ rs.setError(err)
+ return
+ }
+ httpReq.Body = data
+
+ // Set default headers before interceptors
+ c.setHttpRequestHeaders(httpReq)
+
+ // Apply interceptors
+ for _, interceptor := range c.interceptors {
+ if err := interceptor(httpReq); err != nil {
+ c.logHandler.logf(Error, failedToSendRequest, err.Error())
+ rs.setError(err)
+ return
+ }
+ }
+
+ // Create actual http.Request from HttpRequest
+ req, err := http.NewRequest(httpReq.Method, httpReq.URL.String(), bytes.NewReader(httpReq.Body))
+ if err != nil {
+ c.logHandler.logf(Error, failedToSendRequest, err.Error())
+ rs.setError(err)
+ return
+ }
+ req.Header = httpReq.Headers
+
+ resp, err := c.httpClient.Do(req)
+ if err != nil {
+ c.logHandler.logf(Error, failedToSendRequest, err.Error())
+ rs.setError(err)
+ return
+ }
+ defer func() {
+ if err := resp.Body.Close(); err != nil {
+ c.logHandler.logf(Debug, failedToCloseResponseBody, err.Error())
+ }
+ }()
+
+ reader, zlibReader, err := c.getReader(resp)
+ if err != nil {
+ c.logHandler.logf(Error, failedToReceiveResponse, err.Error())
+ rs.setError(err)
+ return
+ }
+ if zlibReader != nil {
+ defer func() {
+ if err := zlibReader.Close(); err != nil {
+ c.logHandler.logf(Debug, failedToCloseDecompReader, err.Error())
+ }
+ }()
+ }
+
+ c.streamToResultSet(reader, rs)
+}
+
+// setHttpRequestHeaders sets default headers on HttpRequest (for interceptors)
+func (c *httpConnection) setHttpRequestHeaders(req *HttpRequest) {
+ req.Headers.Set(HeaderContentType, graphBinaryMimeType)
+ req.Headers.Set(HeaderAccept, graphBinaryMimeType)
+
+ if c.connSettings.enableUserAgentOnConnect {
+ req.Headers.Set(HeaderUserAgent, userAgent)
+ }
+ if c.connSettings.enableCompression {
+ req.Headers.Set(HeaderAcceptEncoding, "deflate")
+ }
+}
+
+func (c *httpConnection) getReader(resp *http.Response) (io.Reader, io.Closer, error) {
+ if resp.Header.Get("Content-Encoding") == "deflate" {
+ zr, err := zlib.NewReader(resp.Body)
+ if err != nil {
+ return nil, nil, err
+ }
+ return zr, zr, nil
+ }
+ return resp.Body, nil, nil
+}
+
+func (c *httpConnection) streamToResultSet(reader io.Reader, rs ResultSet) {
+ d := NewStreamingDeserializer(reader)
+ if err := d.ReadHeader(); err != nil {
+ if err != io.EOF {
+ c.logHandler.logf(Error, failedToReceiveResponse, err.Error())
+ rs.setError(err)
+ }
+ return
+ }
+
+ for {
+ obj, err := d.ReadFullyQualified()
+ if err != nil {
+ if err != io.EOF {
+ c.logHandler.logf(Error, failedToReceiveResponse, err.Error())
+ rs.setError(err)
+ }
+ return
+ }
+
+ if marker, ok := obj.(Marker); ok && marker == EndOfStream() {
+ code, msg, _, err := d.ReadStatus()
+ if err != nil {
+ c.logHandler.logf(Error, failedToReceiveResponse, err.Error())
+ rs.setError(err)
+ return
+ }
+ if code != 200 && code != 0 {
+ rs.setError(newError(err0502ResponseHandlerReadLoopError, msg, code))
+ }
+ return
+ }
+
+ rs.Channel() <- &Result{obj}
+ }
+}
+
+func (c *httpConnection) close() {
+ c.httpClient.CloseIdleConnections()
+}
diff --git a/gremlin-go/driver/httpConnection_test.go b/gremlin-go/driver/httpConnection_test.go
new file mode 100644
index 0000000000..76d9146936
--- /dev/null
+++ b/gremlin-go/driver/httpConnection_test.go
@@ -0,0 +1,178 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements. See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership. The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License. You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied. See the License for the
+specific language governing permissions and limitations
+under the License.
+*/
+
+package gremlingo
+
+import (
+ "bytes"
+ "crypto/tls"
+ "io"
+ "net/http"
+ "net/http/httptest"
+ "testing"
+ "time"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+ "golang.org/x/text/language"
+)
+
+func newTestLogHandler() *logHandler {
+ return newLogHandler(&defaultLogger{}, Warning, language.English)
+}
+
+func TestNewHttpConnection(t *testing.T) {
+ t.Run("creates connection with default settings", func(t *testing.T) {
+ conn := newHttpConnection(newTestLogHandler(), "http://localhost:8182/gremlin", &connectionSettings{})
+
+ assert.NotNil(t, conn.httpClient)
+ assert.NotNil(t, conn.httpClient.Transport)
+ })
+
+ t.Run("applies TLS config", func(t *testing.T) {
+ tlsConfig := &tls.Config{InsecureSkipVerify: true}
+ conn := newHttpConnection(newTestLogHandler(), "https://localhost:8182/gremlin", &connectionSettings{
+ tlsConfig: tlsConfig,
+ })
+
+ transport := conn.httpClient.Transport.(*http.Transport)
+ assert.Equal(t, tlsConfig, transport.TLSClientConfig)
+ })
+}
+
+func TestSetHttpRequestHeaders(t *testing.T) {
+ t.Run("sets content type and accept headers", func(t *testing.T) {
+ conn := newHttpConnection(newTestLogHandler(), "http://localhost/gremlin", &connectionSettings{})
+ req, _ := NewHttpRequest(http.MethodPost, "http://localhost/gremlin")
+
+ conn.setHttpRequestHeaders(req)
+
+ assert.Equal(t, graphBinaryMimeType, req.Headers.Get("Content-Type"))
+ assert.Equal(t, graphBinaryMimeType, req.Headers.Get("Accept"))
+ })
+
+ t.Run("sets user agent when enabled", func(t *testing.T) {
+ conn := newHttpConnection(newTestLogHandler(), "http://localhost/gremlin", &connectionSettings{
+ enableUserAgentOnConnect: true,
+ })
+ req, _ := NewHttpRequest(http.MethodPost, "http://localhost/gremlin")
+
+ conn.setHttpRequestHeaders(req)
+
+ assert.NotEmpty(t, req.Headers.Get(HeaderUserAgent))
+ })
+
+ t.Run("sets compression header when enabled", func(t *testing.T) {
+ conn := newHttpConnection(newTestLogHandler(), "http://localhost/gremlin", &connectionSettings{
+ enableCompression: true,
+ })
+ req, _ := NewHttpRequest(http.MethodPost, "http://localhost/gremlin")
+
+ conn.setHttpRequestHeaders(req)
+
+ assert.Equal(t, "deflate", req.Headers.Get("Accept-Encoding"))
+ })
+}
+
+func TestGetReader(t *testing.T) {
+ conn := newHttpConnection(newTestLogHandler(), "http://localhost/gremlin", &connectionSettings{})
+
+ t.Run("returns body for non-compressed response", func(t *testing.T) {
+ resp := &http.Response{
+ Header: http.Header{},
+ Body: http.NoBody,
+ }
+
+ reader, closer, err := conn.getReader(resp)
+
+ assert.NoError(t, err)
+ assert.Nil(t, closer)
+ assert.Equal(t, resp.Body, reader)
+ })
+
+ t.Run("returns zlib reader for deflate response", func(t *testing.T) {
+ // Valid zlib compressed empty data
+ zlibData := []byte{0x78, 0x9c, 0x03, 0x00, 0x00, 0x00, 0x00, 0x01}
+ resp := &http.Response{
+ Header: http.Header{"Content-Encoding": []string{"deflate"}},
+ Body: io.NopCloser(bytes.NewReader(zlibData)),
+ }
+
+ reader, closer, err := conn.getReader(resp)
+
+ assert.NoError(t, err)
+ assert.NotNil(t, closer)
+ assert.NotNil(t, reader)
+ require.NoError(t, closer.Close())
+ })
+
+ t.Run("returns error for invalid zlib data", func(t *testing.T) {
+ resp := &http.Response{
+ Header: http.Header{"Content-Encoding": []string{"deflate"}},
+ Body: io.NopCloser(bytes.NewReader([]byte{0x00, 0x00})),
+ }
+
+ _, _, err := conn.getReader(resp)
+
+ assert.Error(t, err)
+ })
+}
+
+func TestHttpConnectionWithMockServer(t *testing.T) {
+ t.Run("handles connection error", func(t *testing.T) {
+ conn := newHttpConnection(newTestLogHandler(), "http://localhost:99999/gremlin", &connectionSettings{
+ connectionTimeout: 100 * time.Millisecond,
+ })
+
+ rs, err := conn.send(&request{gremlin: "g.V()", fields: map[string]interface{}{}})
+ assert.NoError(t, err) // send returns nil, error goes to ResultSet
+
+ // All() blocks until stream closes, then we can check error
+ _, _ = rs.All()
+ assert.Error(t, rs.GetError())
+ })
+
+ t.Run("receives headers from request", func(t *testing.T) {
+ headersCh := make(chan http.Header, 1)
+ server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ headersCh <- r.Header.Clone()
+ w.WriteHeader(http.StatusOK)
+ }))
+ defer server.Close()
+
+ conn := newHttpConnection(newTestLogHandler(), server.URL, &connectionSettings{
+ enableUserAgentOnConnect: true,
+ enableCompression: true,
+ })
+
+ rs, err := conn.send(&request{gremlin: "g.V()", fields: map[string]interface{}{}})
+ require.NoError(t, err)
+
+ select {
+ case receivedHeaders := <-headersCh:
+ assert.Equal(t, graphBinaryMimeType, receivedHeaders.Get("Content-Type"))
+ assert.Equal(t, "deflate", receivedHeaders.Get("Accept-Encoding"))
+ assert.NotEmpty(t, receivedHeaders.Get(userAgentHeader))
+ case <-time.After(time.Second):
+ t.Fatal("timeout waiting for request")
+ }
+
+ _, _ = rs.All() // drain
+ })
+}
diff --git a/gremlin-go/driver/httpTransporter.go b/gremlin-go/driver/httpTransporter.go
deleted file mode 100644
index ed8ca7f18f..0000000000
--- a/gremlin-go/driver/httpTransporter.go
+++ /dev/null
@@ -1,171 +0,0 @@
-/*
-Licensed to the Apache Software Foundation (ASF) under one
-or more contributor license agreements. See the NOTICE file
-distributed with this work for additional information
-regarding copyright ownership. The ASF licenses this file
-to you under the Apache License, Version 2.0 (the
-"License"); you may not use this file except in compliance
-with the License. You may obtain a copy of the License at
-
-http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing,
-software distributed under the License is distributed on an
-"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-KIND, either express or implied. See the License for the
-specific language governing permissions and limitations
-under the License.
-*/
-
-package gremlingo
-
-import (
- "bytes"
- "compress/zlib"
- "encoding/json"
- "errors"
- "io"
- "net/http"
- "sync"
-)
-
-// TODO decide channel size when chunked response handling is implemented - for now just set to 1
-const responseChannelSizeDefault = 1
-const contentTypeHeader = "content-type"
-
-// httpTransporter responsible for sending and receiving bytes to/from the server
-type httpTransporter struct {
- url string
- isClosed bool
- connSettings *connectionSettings
- responseChannel chan []byte // receives response bytes from the server
- httpClient *http.Client
- wg *sync.WaitGroup
- logHandler *logHandler
-}
-
-func newHttpTransporter(url string, connSettings *connectionSettings, httpClient *http.Client, logHandler *logHandler) *httpTransporter {
- wg := &sync.WaitGroup{}
-
- return &httpTransporter{
- url: url,
- connSettings: connSettings,
- responseChannel: make(chan []byte, responseChannelSizeDefault),
- httpClient: httpClient,
- wg: wg,
- logHandler: logHandler,
- }
-}
-
-// write sends bytes to the server as a POST request and sends received response bytes to the responseChannel
-func (transporter *httpTransporter) write(data []byte) error {
- resp, err := transporter.sendRequest(data)
- if err != nil {
- return err
- }
-
- respBytes, err := transporter.readResponse(resp)
- if err != nil {
- return err
- }
-
- // possible to receive graph-binary or json error response bodies
- if resp.StatusCode != 200 && resp.Header.Get(contentTypeHeader) != graphBinaryMimeType {
- return transporter.createResponseError(respBytes, resp)
- }
-
- transporter.responseChannel <- respBytes
- return nil
-}
-
-// read reads bytes from the responseChannel
-func (transporter *httpTransporter) read() ([]byte, error) {
- msg, ok := <-transporter.responseChannel
- if !ok {
- return []byte{}, errors.New("failed to read from response channel")
- }
- return msg, nil
-}
-
-// close closes the transporter and its corresponding responseChannel
-func (transporter *httpTransporter) close() {
- if !transporter.isClosed {
- if transporter.responseChannel != nil {
- close(transporter.responseChannel)
- }
- transporter.isClosed = true
- }
-}
-
-func (transporter *httpTransporter) createResponseError(respBytes []byte, resp *http.Response) error {
- contentType := resp.Header.Get(contentTypeHeader)
- if contentType == "application/json" {
- var jsonMap map[string]interface{}
- err := json.Unmarshal(respBytes, &jsonMap)
- if err != nil {
- return err
- }
- message, exists := jsonMap["message"]
- if exists {
- return newError(err0502ResponseHandlerError, message, resp.StatusCode)
- }
- return newError(err0502ResponseHandlerError, "Response was not successful", resp.StatusCode)
- }
- // unexpected error content type
- return newError(err0502ResponseHandlerError, "Response was not successful and of unexpected content-type: "+contentType, resp.StatusCode)
-}
-
-// reads bytes from the given response
-func (transporter *httpTransporter) readResponse(resp *http.Response) ([]byte, error) {
- var reader io.ReadCloser
- var err error
-
- if resp.Header.Get("content-encoding") == "deflate" {
- reader, err = zlib.NewReader(resp.Body)
- if err != nil {
- transporter.logHandler.logf(Error, failedToReceiveResponse, err.Error())
- return nil, err
- }
- } else {
- reader = resp.Body
- }
-
- // TODO handle chunked encoding and send chunks to responseChannel
- all, err := io.ReadAll(reader)
- if err != nil {
- transporter.logHandler.logf(Error, failedToReceiveResponse, err.Error())
- return nil, err
- }
- err = reader.Close()
- if err != nil {
- return nil, err
- }
- transporter.logHandler.log(Debug, receivedResponse)
- return all, nil
-}
-
-// sends a POST request for the given byte content
-func (transporter *httpTransporter) sendRequest(data []byte) (*http.Response, error) {
- transporter.logHandler.logf(Debug, creatingRequest)
- req, err := http.NewRequest("POST", transporter.url, bytes.NewBuffer(data))
- if err != nil {
- transporter.logHandler.logf(Error, failedToSendRequest, err.Error())
- return nil, err
- }
- req.Header.Set(contentTypeHeader, graphBinaryMimeType)
- req.Header.Set("accept", graphBinaryMimeType)
- if transporter.connSettings.enableUserAgentOnConnect {
- req.Header.Set(userAgentHeader, userAgent)
- }
- if transporter.connSettings.enableCompression {
- req.Header.Set("accept-encoding", "deflate")
- }
-
- transporter.logHandler.logf(Debug, writeRequest)
- resp, err := transporter.httpClient.Do(req)
- if err != nil {
- transporter.logHandler.logf(Error, failedToSendRequest, err.Error())
- return nil, err
- }
- return resp, nil
-}
diff --git a/gremlin-go/driver/logger.go b/gremlin-go/driver/logger.go
index 6297afef38..388ce44a2c 100644
--- a/gremlin-go/driver/logger.go
+++ b/gremlin-go/driver/logger.go
@@ -103,14 +103,17 @@ const (
nullInput errorKey = "NULL_INPUT"
unexpectedNull errorKey = "UNEXPECTED_NULL_VALUE"
writeRequest errorKey = "WRITE_REQUEST"
+ errorCallback errorKey = "ERROR_CALLBACK"
creatingRequest errorKey = "CREATING_REQUEST"
readComplete errorKey = "READ_COMPLETE"
submitStartedString errorKey = "SUBMIT_STARTED_STRING"
- submitStartedBytecode errorKey = "SUBMIT_STARTED_BYTECODE"
+ failedToCloseInErrorCallback errorKey = "FAILED_TO_CLOSE_IN_ERROR_CALLBACK"
+ failedToWriteMessage errorKey = "FAILED_TO_WRITE_MESSAGE"
failedToReceiveResponse errorKey = "FAILED_TO_RECEIVE_RESPONSE"
failedToSendRequest errorKey = "FAILED_TO_SEND_REQUEST"
- receivedResponse errorKey = "RECEIVED_RESPONSE"
logErrorGeneric errorKey = "LOG_ERROR_GENERIC"
closeDriverRemoteConnection errorKey = "CLOSE_DRIVER_REMOTE_CONNECTION"
closeClient errorKey = "CLOSE_CLIENT"
+ failedToCloseResponseBody errorKey = "FAILED_TO_CLOSE_RESPONSE_BODY"
+ failedToCloseDecompReader errorKey = "FAILED_TO_CLOSE_DECOMPRESSION_READER"
)
diff --git a/gremlin-go/driver/transporter.go b/gremlin-go/driver/marker.go
similarity index 65%
rename from gremlin-go/driver/transporter.go
rename to gremlin-go/driver/marker.go
index 11d65f5e73..bb6e855bf5 100644
--- a/gremlin-go/driver/transporter.go
+++ b/gremlin-go/driver/marker.go
@@ -19,22 +19,28 @@ under the License.
package gremlingo
-import "time"
-
-type transporter interface {
- Connect() error
- Write(data []byte) error
- Read() ([]byte, error)
- Close() error
- IsClosed() bool
- getAuthInfo() AuthInfoProvider
+import "fmt"
+
+// Marker is used in response
+type Marker interface {
+ GetValue() byte
+}
+
+type marker struct {
+ value byte
+}
+
+func EndOfStream() Marker {
+ return marker{byte(0)}
+}
+
+func (m marker) GetValue() byte {
+ return m.value
}
-type websocketConn interface {
- WriteMessage(int, []byte) error
- ReadMessage() (int, []byte, error)
- SetPongHandler(h func(appData string) error)
- Close() error
- SetReadDeadline(t time.Time) error
- SetWriteDeadline(t time.Time) error
+func Of(value byte) (Marker, error) {
+ if value != 0 {
+ return nil, fmt.Errorf("marker value erorr")
+ }
+ return EndOfStream(), nil
}
diff --git a/gremlin-go/driver/performance/performanceSuite.go b/gremlin-go/driver/performance/performanceSuite.go
index ba2b493b91..bcad844f67 100644
--- a/gremlin-go/driver/performance/performanceSuite.go
+++ b/gremlin-go/driver/performance/performanceSuite.go
@@ -378,10 +378,7 @@ func createConnection(host string, port, poolSize, buffersSize int) (*GraphTrave
drc, err = gremlingo.NewDriverRemoteConnection(endpoint, func(settings *DriverRemoteConnectionSettings) {
settings.LogVerbosity = GremlinWarning
settings.TraversalSource = gratefulGraphAlias
- settings.NewConnectionThreshold = threshold
settings.MaximumConcurrentConnections = poolSize
- settings.WriteBufferSize = buffersSize
- settings.ReadBufferSize = buffersSize
})
if err != nil {
diff --git a/gremlin-go/driver/request.go b/gremlin-go/driver/request.go
index b5eebe65d3..022ddc484d 100644
--- a/gremlin-go/driver/request.go
+++ b/gremlin-go/driver/request.go
@@ -77,43 +77,6 @@ func MakeStringRequest(stringGremlin string, traversalSource string, requestOpti
}
}
-// 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")
-//
-// Returns:
-// - request: A request structure ready for serialization
-//
-// Example for alternative transports:
-//
-// bytecode := g.V().HasLabel("person").Bytecode
-// 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) (req request) {
- newFields := map[string]interface{}{
- "gremlin": *bytecodeGremlin,
- "aliases": map[string]interface{}{
- "g": traversalSource,
- },
- }
-
- for k, v := range extractReqArgs(bytecodeGremlin) {
- newFields[k] = v
- }
-
- return request{
- fields: newFields,
- }
-}
-
// allowedReqArgs contains the arguments that will be extracted from the
// bytecode and sent with the request.
var allowedReqArgs = map[string]bool{
@@ -123,72 +86,3 @@ var allowedReqArgs = map[string]bool{
"userAgent": true,
"materializeProperties": true,
}
-
-// extractReqArgs extracts request arguments from the provided bytecode.
-func extractReqArgs(bytecode *Bytecode) map[string]interface{} {
- args := make(map[string]interface{})
-
- for _, insn := range bytecode.sourceInstructions {
- switch insn.operator {
- case "withStrategies":
- for k, v := range extractWithStrategiesReqArgs(insn) {
- args[k] = v
- }
- case "with":
- if k, v := extractWithReqArg(insn); k != "" {
- args[k] = v
- }
- }
- }
-
- return args
-}
-
-// extractWithStrategiesReqArgs extracts request arguments from the passed
-// "withStrategies" source instruction. Only OptionsStrategy is considered.
-func extractWithStrategiesReqArgs(insn instruction) map[string]interface{} {
- args := make(map[string]interface{})
-
- for _, strategyInterface := range insn.arguments {
- strategy, ok := strategyInterface.(*traversalStrategy)
- if !ok {
- // (*GraphTraversalSource).WithStrategies accepts
- // TraversalStrategy parameters only. Thus, this
- // should be unreachable.
- continue
- }
-
- if strategy.name != decorationNamespace+"OptionsStrategy" {
- continue
- }
-
- for k, v := range strategy.configuration {
- if allowedReqArgs[k] {
- args[k] = v
- }
- }
- }
-
- return args
-}
-
-// extractWithReqArg extracts a request argument from the passed "with" source
-// instruction.
-func extractWithReqArg(insn instruction) (key string, value interface{}) {
- if len(insn.arguments) != 2 {
- // (*GraphTraversalSource).With accepts two parameters. Thus,
- // this should be unreachable.
- return "", nil
- }
-
- key, ok := insn.arguments[0].(string)
- if !ok {
- return "", nil
- }
-
- if !allowedReqArgs[key] {
- return "", nil
- }
-
- return key, insn.arguments[1]
-}
diff --git a/gremlin-go/driver/resources/error-messages/en.json b/gremlin-go/driver/resources/error-messages/en.json
index cfd070af47..0120b5b77f 100644
--- a/gremlin-go/driver/resources/error-messages/en.json
+++ b/gremlin-go/driver/resources/error-messages/en.json
@@ -23,6 +23,7 @@
"E0501_PROTOCOL_RESPONSEHANDLER_NO_RESULTSET_ON_DATA_RECEIVE":"E0501: resultSet was not created before data was received",
"E0502_PROTOCOL_RESPONSEHANDLER_ERROR": "E0502: error handling response, error message '%+v'. statusCode: %d",
+ "E0502_PROTOCOL_RESPONSEHANDLER_READ_LOOP_ERROR": "E0502: error in read loop, error message '%v'. statusCode: %d",
"E0503_PROTOCOL_RESPONSEHANDLER_AUTH_ERROR":"E0503: failed to authenticate %v : %v",
"E0601_RESULT_NOT_VERTEX_ERROR":"E0601: result is not a Vertex",
diff --git a/gremlin-go/driver/resources/logger-messages/en.json b/gremlin-go/driver/resources/logger-messages/en.json
index d024444ed6..f7025e837a 100644
--- a/gremlin-go/driver/resources/logger-messages/en.json
+++ b/gremlin-go/driver/resources/logger-messages/en.json
@@ -15,7 +15,12 @@
"LOG_ERROR_GENERIC": "Error occurred during operation %s: '%s'",
"CLOSE_DRIVER_REMOTE_CONNECTION": "Closing DriverRemoteConnection with url '%s'",
"CLOSE_CLIENT": "Closing Client with url '%s'",
+ "ERROR_CLOSING_CONNECTION": "Ignoring error closing connection: %s",
+ "CREATE_CONNECTION_ERROR": "Error creating new connection for connection pool: %s",
+ "POOL_NEW_CONNECTION_ERROR": "Falling back to least-used connection. Creating new connection due to least-used connection exceeding concurrent usage threshold failed: %s",
+ "POOL_INITIAL_EXCEEDS_MAXIMUM": "InitialConcurrentConnections setting %d exceeded MaximumConcurrentConnections setting %d - limiting InitialConcurrentConnections to %d.",
"FAILED_TO_RECEIVE_RESPONSE": "Failed to receive response: %s",
"FAILED_TO_SEND_REQUEST": "Failed to send request: %s",
- "RECEIVED_RESPONSE": "Received response data"
+ "FAILED_TO_CLOSE_RESPONSE_BODY": "Error closing response body: %s",
+ "FAILED_TO_CLOSE_DECOMPRESSION_READER": "Error closing decompression reader: %s"
}
diff --git a/gremlin-go/driver/response.go b/gremlin-go/driver/response.go
index c2a08d550a..7750eb5b1b 100644
--- a/gremlin-go/driver/response.go
+++ b/gremlin-go/driver/response.go
@@ -19,8 +19,6 @@ under the License.
package gremlingo
-import "github.com/google/uuid"
-
// ResponseStatus contains the status info of the response.
type ResponseStatus struct {
code uint32
@@ -35,7 +33,6 @@ type ResponseResult struct {
// Response represents a Response from the server.
type Response struct {
- ResponseID uuid.UUID
ResponseStatus ResponseStatus
ResponseResult ResponseResult
}
diff --git a/gremlin-go/driver/resultSet.go b/gremlin-go/driver/resultSet.go
index 59e22e66d1..cf1230cd53 100644
--- a/gremlin-go/driver/resultSet.go
+++ b/gremlin-go/driver/resultSet.go
@@ -152,8 +152,8 @@ func (channelResultSet *channelResultSet) All() ([]*Result, error) {
func (channelResultSet *channelResultSet) addResult(r *Result) {
channelResultSet.channelMutex.Lock()
- if r.GetType().Kind() == reflect.Array || r.GetType().Kind() == reflect.Slice {
- for _, v := range r.Data.([]interface{}) {
+ if data, ok := r.Data.([]interface{}); ok {
+ for _, v := range data {
if reflect.TypeOf(v) == reflect.TypeOf(&Traverser{}) {
for i := int64(0); i < (v.(*Traverser)).bulk; i++ {
channelResultSet.channel <- &Result{(v.(*Traverser)).value}
diff --git a/gremlin-go/driver/serializer.go b/gremlin-go/driver/serializer.go
index efd32618f1..98a5f3142c 100644
--- a/gremlin-go/driver/serializer.go
+++ b/gremlin-go/driver/serializer.go
@@ -35,7 +35,9 @@ type Serializer interface {
// GraphBinarySerializer serializes/deserializes message to/from GraphBinary.
type GraphBinarySerializer struct {
- ser *graphBinaryTypeSerializer
+ ser *graphBinaryTypeSerializer
+ bulked bool // State maintained between chunks
+ buffer []byte // Buffer for incomplete objects across chunks
}
// CustomTypeReader user provided function to deserialize custom types
@@ -56,11 +58,16 @@ func init() {
initDeserializers()
}
-func newGraphBinarySerializer(handler *logHandler) Serializer {
+func newGraphBinarySerializer(handler *logHandler) *GraphBinarySerializer {
serializer := graphBinaryTypeSerializer{handler}
- return GraphBinarySerializer{&serializer}
+ return &GraphBinarySerializer{
+ ser: &serializer,
+ bulked: false,
+ buffer: make([]byte, 0),
+ }
}
+// TODO change for graph binary 4.0 version is finalized
const versionByte byte = 0x81
// SerializeMessage serializes a request message into GraphBinary format.
@@ -85,8 +92,8 @@ const versionByte byte = 0x81
// 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) {
+// SerializeMessage serializes a request message into GraphBinary.
+func (gs *GraphBinarySerializer) SerializeMessage(request *request) ([]byte, error) {
finalMessage, err := gs.buildMessage(request.gremlin, request.fields)
if err != nil {
return nil, err
@@ -131,7 +138,7 @@ func (gs *GraphBinarySerializer) buildMessage(gremlin string, args map[string]in
// serializer := newGraphBinarySerializer(nil)
// resp, err := serializer.(graphBinarySerializer).DeserializeMessage(responseBytes)
// results := resp.responseResult.data
-func (gs GraphBinarySerializer) DeserializeMessage(message []byte) (Response, error) {
+func (gs *GraphBinarySerializer) DeserializeMessage(message []byte) (Response, error) {
var msg Response
if message == nil || len(message) == 0 {
@@ -142,22 +149,21 @@ func (gs GraphBinarySerializer) DeserializeMessage(message []byte) (Response, er
//Skip version and nullable byte.
i := 2
- // TODO temp serialization before fully streaming set-up
for len(message) > 0 {
n, err := readFullyQualifiedNullable(&message, &i, true)
if err != nil {
return msg, err
}
+ if n == EndOfStream() {
+ break
+ }
results = append(results, n)
}
- if len(results) == 1 {
- // unwrap single results
- msg.ResponseResult.Data = results[0]
- } else {
- msg.ResponseResult.Data = results
- }
+
+ msg.ResponseResult.Data = results
code := readUint32Safe(&message, &i)
msg.ResponseStatus.code = code
+ msg.ResponseStatus.message = "OK"
statusMsg, err := readUnqualified(&message, &i, stringType, true)
if err != nil {
return msg, err
@@ -175,9 +181,96 @@ func (gs GraphBinarySerializer) DeserializeMessage(message []byte) (Response, er
return msg, nil
}
+// readChunk processes HTTP chunks with simple buffering
+func (gs *GraphBinarySerializer) readChunk(chunk []byte, isFirstChunk bool) (*Response, error) {
+ var msg Response
+
+ if len(chunk) == 0 {
+ msg.ResponseStatus.code = 204
+ return &msg, nil
+ }
+
+ // Append to buffer
+ gs.buffer = append(gs.buffer, chunk...)
+
+ i := 0
+
+ if isFirstChunk {
+ if len(gs.buffer) < 2 {
+ msg.ResponseResult.Data = make([]interface{}, 0)
+ return &msg, nil
+ }
+ i++ // skip version
+ gs.bulked = (gs.buffer[i] & 1) == 1
+ i++
+ }
+
+ results := make([]interface{}, 0)
+ startPos := i
+ processedPos := i // Track position after last successfully processed object
+ endOfStream := false
+
+ // Process complete objects, recover from panic on incomplete data
+ func() {
+ defer func() {
+ if r := recover(); r != nil {
+ if r == ErrIncompleteData {
+ i = startPos // reset to start of incomplete object
+ } else {
+ panic(r) // re-panic for other errors
+ }
+ }
+ }()
+ for i < len(gs.buffer) {
+ // Peek before parsing - need at least 2 bytes for type + nullable
+ if len(gs.buffer)-i < 2 {
+ break
+ }
+ startPos = i
+ obj, err := readFullyQualifiedNullable(&gs.buffer, &i, true)
+ if err != nil {
+ return
+ }
+
+ if marker, ok := obj.(Marker); ok && marker == EndOfStream() {
+ gs.bulked = false
+ // Read status after end of stream
+ msg.ResponseStatus.code = readUint32Safe(&gs.buffer, &i)
+ if statusMsg, _ := readUnqualified(&gs.buffer, &i, stringType, true); statusMsg != nil {
+ msg.ResponseStatus.message = statusMsg.(string)
+ }
+ if exception, _ := readUnqualified(&gs.buffer, &i, stringType, true); exception != nil {
+ msg.ResponseStatus.exception = exception.(string)
+ }
+ // Only set endOfStream after successfully reading status
+ endOfStream = true
+ gs.buffer = gs.buffer[:0]
+ return
+ }
+
+ results = append(results, obj)
+ processedPos = i // Update after successful processing
+ }
+ }()
+
+ msg.ResponseResult.Data = results
+
+ // Keep unprocessed data in buffer (unless we hit end of stream)
+ if !endOfStream {
+ if processedPos < len(gs.buffer) {
+ remaining := make([]byte, len(gs.buffer)-processedPos)
+ copy(remaining, gs.buffer[processedPos:])
+ gs.buffer = remaining
+ } else {
+ gs.buffer = gs.buffer[:0]
+ }
+ }
+
+ return &msg, nil
+}
+
func initSerializers() {
serializers = map[dataType]writer{
- bytecodeType: bytecodeWriter,
stringType: stringWriter,
bigDecimalType: bigDecimalWriter,
bigIntegerType: bigIntWriter,
@@ -204,37 +297,22 @@ func initSerializers() {
err := binary.Write(buffer, binary.BigEndian, value)
return buffer.Bytes(), err
},
- vertexType: vertexWriter,
- edgeType: edgeWriter,
- propertyType: propertyWriter,
- vertexPropertyType: vertexPropertyWriter,
- lambdaType: lambdaWriter,
- traversalStrategyType: traversalStrategyWriter,
- pathType: pathWriter,
- setType: setWriter,
- dateType: timeWriter,
- durationType: durationWriter,
- offsetDateTimeType: offsetDateTimeWriter,
- cardinalityType: enumWriter,
- columnType: enumWriter,
- directionType: enumWriter,
- dtType: enumWriter,
- gTypeType: enumWriter,
- operatorType: enumWriter,
- orderType: enumWriter,
- pickType: enumWriter,
- popType: enumWriter,
- tType: enumWriter,
- barrierType: enumWriter,
- scopeType: enumWriter,
- mergeType: enumWriter,
- pType: pWriter,
- textPType: textPWriter,
- bindingType: bindingWriter,
- mapType: mapWriter,
- listType: listWriter,
- byteBuffer: byteBufferWriter,
- classType: classWriter,
+ vertexType: vertexWriter,
+ edgeType: edgeWriter,
+ propertyType: propertyWriter,
+ vertexPropertyType: vertexPropertyWriter,
+ pathType: pathWriter,
+ datetimeType: dateTimeWriter,
+ durationType: durationWriter,
+ directionType: enumWriter,
+ gTypeType: enumWriter,
+ tType: enumWriter,
+ mergeType: enumWriter,
+ mapType: mapWriter,
+ listType: listWriter,
+ setType: setWriter,
+ byteBuffer: byteBufferWriter,
+ markerType: markerWriter,
}
}
@@ -253,39 +331,30 @@ func initDeserializers() {
stringType: readString,
// Composite
- listType: readList,
+ //listType: readList,
+ //setType: readSet,
mapType: readMap,
- setType: readSet,
uuidType: readUuid,
byteBuffer: readByteBuffer,
- classType: readClass,
// Date Time
- dateType: timeReader,
- timestampType: timeReader,
- offsetDateTimeType: offsetDateTimeReader,
- durationType: durationReader,
+ datetimeType: dateTimeReader,
+ durationType: durationReader,
// Graph
- traverserType: traverserReader,
vertexType: vertexReader,
edgeType: edgeReader,
propertyType: propertyReader,
vertexPropertyType: vertexPropertyReader,
pathType: pathReader,
- bulkSetType: bulkSetReader,
tType: enumReader,
directionType: enumReader,
- dtType: enumReader,
gTypeType: enumReader,
- bindingType: bindingReader,
-
- // Metrics
- metricsType: metricsReader,
- traversalMetricsType: traversalMetricsReader,
// Customer
customType: customTypeReader,
+
+ markerType: markerReader,
}
customDeserializers = map[string]CustomTypeReader{}
}
diff --git a/gremlin-go/driver/serializer_test.go b/gremlin-go/driver/serializer_test.go
index 756f0af15f..2f2b062cdc 100644
--- a/gremlin-go/driver/serializer_test.go
+++ b/gremlin-go/driver/serializer_test.go
@@ -20,7 +20,6 @@ under the License.
package gremlingo
import (
- "errors"
"fmt"
"testing"
@@ -28,14 +27,14 @@ import (
"golang.org/x/text/language"
)
-const mapDataOrder1 = "[32 97 112 112 108 105 99 97 116 105 111 110 47 118 110 100 46 103 114 97 112 104 98 105 110 97 114 121 45 118 49 46 48 129 65 210 226 138 32 164 74 176 179 121 216 16 222 222 55 134 0 0 0 4 101 118 97 108 0 0 0 0 0 0 0 2 3 0 0 0 0 7 103 114 101 109 108 105 110 3 0 0 0 0 13 103 46 86 40 41 46 99 111 117 110 116 40 41 3 0 0 0 0 7 97 108 105 97 115 101 115 10 0 0 0 0 1 3 0 0 0 0 1 103 3 0 0 0 0 1 103]"
-const mapDataOrder2 = "[32 97 112 112 108 105 99 97 116 105 111 110 47 118 110 100 46 103 114 97 112 104 98 105 110 97 114 121 45 118 49 46 48 129 65 210 226 138 32 164 74 176 179 121 216 16 222 222 55 134 0 0 0 4 101 118 97 108 0 0 0 0 0 0 0 2 3 0 0 0 0 7 97 108 105 97 115 101 115 10 0 0 0 0 1 3 0 0 0 0 1 103 3 0 0 0 0 1 103 3 0 0 0 0 7 103 114 101 109 108 105 110 3 0 0 0 0 13 103 46 86 40 41 46 99 111 117 110 116 40 41]"
+const mapDataOrder1 = "[129 0 0 0 2 3 0 0 0 0 8 108 97 110 103 117 97 103 101 3 0 0 0 0 12 103 114 101 109 108 105 110 45 108 97 110 103 3 0 0 0 0 1 103 3 0 0 0 0 1 103 0 0 0 13 103 46 86 40 41 46 99 111 117 110 116 40 41]"
+const mapDataOrder2 = "[129 0 0 0 2 3 0 0 0 0 1 103 3 0 0 0 0 1 103 3 0 0 0 0 8 108 97 110 103 117 97 103 101 3 0 0 0 0 12 103 114 101 109 108 105 110 45 108 97 110 103 0 0 0 13 103 46 86 40 41 46 99 111 117 110 116 40 41]"
func TestSerializer(t *testing.T) {
t.Run("test serialized request message", func(t *testing.T) {
testRequest := request{
gremlin: "g.V().count()",
- fields: map[string]interface{}{"aliases": map[string]interface{}{"g": "g"}},
+ fields: map[string]interface{}{"g": "g", "language": "gremlin-lang"},
}
serializer := newGraphBinarySerializer(newLogHandler(&defaultLogger{}, Error, language.English))
serialized, _ := serializer.SerializeMessage(&testRequest)
@@ -46,76 +45,28 @@ func TestSerializer(t *testing.T) {
})
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}
+ // response of status code 200 with message OK and data of list of single item of integer zero
+ responseByteArray := []byte{129, 0, 1, 0, 0, 0, 0, 0, 253, 0, 0, 0, 0, 0, 200, 1, 1}
serializer := newGraphBinarySerializer(newLogHandler(&defaultLogger{}, Error, language.English))
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)
- })
-
- t.Run("test serialized response message w/ custom type", func(t *testing.T) {
- RegisterCustomTypeReader("janusgraph.RelationIdentifier", exampleJanusgraphRelationIdentifierReader)
- defer func() {
- UnregisterCustomTypeReader("janusgraph.RelationIdentifier")
- }()
- 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, 1 [...]
- serializer := newGraphBinarySerializer(newLogHandler(&defaultLogger{}, Error, language.English))
- 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, uint32(200), response.ResponseStatus.code)
+ assert.Equal(t, "OK", response.ResponseStatus.message)
+ assert.Equal(t, []interface{}{int32(0)}, response.ResponseResult.Data)
})
}
func TestSerializerFailures(t *testing.T) {
- t.Run("test convertArgs failure", func(t *testing.T) {
+ t.Run("test serialize request fields failure", func(t *testing.T) {
+ invalid := "invalid"
testRequest := request{
- // Invalid Input in fields, so should fail
- fields: map[string]interface{}{"invalidInput": "invalidInput", "aliases": map[string]interface{}{"g": "g"}},
+ // Invalid pointer type in fields, so should fail
+ fields: map[string]interface{}{"invalidInput": &invalid, "g": "g"},
}
serializer := newGraphBinarySerializer(newLogHandler(&defaultLogger{}, Error, language.English))
resp, err := serializer.SerializeMessage(&testRequest)
assert.Nil(t, resp)
assert.NotNil(t, err)
- assert.True(t, isSameErrorCode(newError(err0704ConvertArgsNoSerializerError), err))
- })
-
- t.Run("test unkownCustomType failure", func(t *testing.T) {
- 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, 1 [...]
- serializer := newGraphBinarySerializer(newLogHandler(&defaultLogger{}, Error, language.English))
- resp, err := serializer.DeserializeMessage(responseByteArray)
- // a partial message will still be returned
- assert.NotNil(t, resp)
- assert.NotNil(t, err)
- assert.True(t, isSameErrorCode(newError(err0409GetSerializerToReadUnknownCustomTypeError), err))
+ assert.True(t, isSameErrorCode(newError(err0407GetSerializerToWriteUnknownTypeError), err))
})
}
-
-// exampleJanusgraphRelationIdentifierReader this implementation is not complete and is used only for the purposes of testing custom readers
-func exampleJanusgraphRelationIdentifierReader(data *[]byte, i *int) (interface{}, error) {
- const relationIdentifierType = 0x1001
- const longMarker = 0
-
- // expect type code
- customDataTyp := readUint32Safe(data, i)
- if customDataTyp != relationIdentifierType {
- return nil, fmt.Errorf("unknown type code. got 0x%x, expected 0x%x", customDataTyp, relationIdentifierType)
- }
-
- // value flag, expect this to be non-nullable
- if readByteSafe(data, i) != valueFlagNone {
- return nil, errors.New("expected non-null value")
- }
-
- // outVertexId
- if readByteSafe(data, i) == longMarker {
- return readLongSafe(data, i), nil
- } else {
- return readString(data, i)
- }
-}
diff --git a/gremlin-go/driver/strategies.go b/gremlin-go/driver/strategies.go
index e2887eb703..c0d8b3b578 100644
--- a/gremlin-go/driver/strategies.go
+++ b/gremlin-go/driver/strategies.go
@@ -19,20 +19,6 @@ under the License.
package gremlingo
-const (
- baseNamespace = "org.apache.tinkerpop.gremlin.process.traversal.strategy."
- decorationNamespace = baseNamespace + "decoration."
- finalizationNamespace = baseNamespace + "finalization."
- optimizationNamespace = baseNamespace + "optimization."
- verificationNamespace = baseNamespace + "verification."
- computerDecorationNamespace = "org.apache.tinkerpop.gremlin.process.computer.traversal.strategy.decoration."
- computerVerificationNamespace = "org.apache.tinkerpop.gremlin.process.computer.traversal.strategy.verification."
- // org.apache.tinkerpop.gremlin.process.computer.traversal.strategy.verification
- // org.apache.tinkerpop.gremlin.process.traversal.strategy.verification
- computerFinalizationNamespace = "org.apache.tinkerpop.gremlin.process.computer.traversal.strategy.finalization."
- computerOptimizationNamespace = "org.apache.tinkerpop.gremlin.process.computer.traversal.strategy.optimization."
-)
-
type TraversalStrategy interface {
}
@@ -42,12 +28,17 @@ type traversalStrategy struct {
apply func(g GraphTraversal)
}
+// NewTraversalStrategy creates a new strategy with custom name and config
+func NewTraversalStrategy(name string, configuration map[string]interface{}) TraversalStrategy {
+ return &traversalStrategy{name: name, configuration: configuration}
+}
+
// Decoration strategies
// ConnectiveStrategy rewrites the binary conjunction form of a.And().b into a AndStep of
// And(a,b) (likewise for OrStep).
func ConnectiveStrategy() TraversalStrategy {
- return &traversalStrategy{name: decorationNamespace + "ConnectiveStrategy"}
+ return &traversalStrategy{name: "ConnectiveStrategy"}
}
// ElementIdStrategy provides a degree of control over element identifier assignment as some Graphs don't provide
@@ -55,7 +46,7 @@ func ConnectiveStrategy() TraversalStrategy {
// under the hood, thus simulating that capability.
// By default, when an identifier is not supplied by the user, newly generated identifiers are UUID objects.
func ElementIdStrategy() TraversalStrategy {
- return &traversalStrategy{name: decorationNamespace + "ElementIdStrategy"}
+ return &traversalStrategy{name: "ElementIdStrategy"}
}
func HaltedTraverserStrategy(config ...HaltedTraverserStrategyConfig) TraversalStrategy {
@@ -65,7 +56,7 @@ func HaltedTraverserStrategy(config ...HaltedTraverserStrategyConfig) TraversalS
configMap["haltedTraverserFactory"] = config[0].HaltedTraverserFactory
}
}
- return &traversalStrategy{name: decorationNamespace + "HaltedTraverserStrategy", configuration: configMap}
+ return &traversalStrategy{name: "HaltedTraverserStrategy", configuration: configMap}
}
// HaltedTraverserStrategyConfig provides configuration options for HaltedTraverserStrategy.
@@ -83,8 +74,7 @@ func OptionsStrategy(options ...map[string]interface{}) TraversalStrategy {
if len(options) > 0 {
optsMap = options[0]
}
-
- return &traversalStrategy{name: decorationNamespace + "OptionsStrategy", configuration: optsMap}
+ return &traversalStrategy{name: "OptionsStrategy", configuration: optsMap}
}
// PartitionStrategy partitions the Vertices, Edges and Vertex properties of a Graph into String named
@@ -101,7 +91,7 @@ func PartitionStrategy(config PartitionStrategyConfig) TraversalStrategy {
if len(config.ReadPartitions.ToSlice()) != 0 {
configMap["readPartitions"] = config.ReadPartitions
}
- return &traversalStrategy{name: decorationNamespace + "PartitionStrategy", configuration: configMap}
+ return &traversalStrategy{name: "PartitionStrategy", configuration: configMap}
}
// PartitionStrategyConfig provides configuration options for PartitionStrategy.
@@ -125,8 +115,7 @@ func SeedStrategy(config ...SeedStrategyConfig) TraversalStrategy {
if len(config) > 0 {
configMap["seed"] = config[0].Seed
}
-
- return &traversalStrategy{name: decorationNamespace + "SeedStrategy", configuration: configMap}
+ return &traversalStrategy{name: "SeedStrategy", configuration: configMap}
}
// SeedStrategyConfig provides configuration options for SeedStrategy. Zeroed (unset) values are used.
@@ -151,7 +140,7 @@ func SubgraphStrategy(config SubgraphStrategyConfig) TraversalStrategy {
if config.CheckAdjacentVertices != nil {
configMap["checkAdjacentVertices"] = config.CheckAdjacentVertices.(bool)
}
- return &traversalStrategy{name: decorationNamespace + "SubgraphStrategy", configuration: configMap}
+ return &traversalStrategy{name: "SubgraphStrategy", configuration: configMap}
}
// SubgraphStrategyConfig provides configuration options for SubgraphStrategy. Zeroed (unset) values are ignored.
@@ -187,8 +176,7 @@ func VertexProgramStrategy(config ...VertexProgramStrategyConfig) TraversalStrat
configMap[k] = v
}
}
-
- return &traversalStrategy{name: computerDecorationNamespace + "VertexProgramStrategy", configuration: configMap}
+ return &traversalStrategy{name: "VertexProgramStrategy", configuration: configMap}
}
// VertexProgramStrategyConfig provides configuration options for VertexProgramStrategy.
@@ -212,8 +200,7 @@ func MatchAlgorithmStrategy(config ...MatchAlgorithmStrategyConfig) TraversalStr
configMap["matchAlgorithm"] = config[0].MatchAlgorithm
}
}
-
- return &traversalStrategy{name: finalizationNamespace + "MatchAlgorithmStrategy", configuration: configMap}
+ return &traversalStrategy{name: "MatchAlgorithmStrategy", configuration: configMap}
}
// MatchAlgorithmStrategyConfig provides configuration options for MatchAlgorithmStrategy.
@@ -222,22 +209,38 @@ type MatchAlgorithmStrategyConfig struct {
MatchAlgorithm string
}
-func ReferenceElementStrategy() TraversalStrategy {
- return &traversalStrategy{name: finalizationNamespace + "ReferenceElementStrategy"}
+func ReferenceElementStrategy(options ...map[string]interface{}) TraversalStrategy {
+ config := make(map[string]interface{})
+ if len(options) > 0 {
+ config = options[0]
+ }
+ return &traversalStrategy{name: "ReferenceElementStrategy", configuration: config}
}
-func ComputerFinalizationStrategy() TraversalStrategy {
- return &traversalStrategy{name: computerFinalizationNamespace + "ComputerFinalizationStrategy"}
+func ComputerFinalizationStrategy(options ...map[string]interface{}) TraversalStrategy {
+ config := make(map[string]interface{})
+ if len(options) > 0 {
+ config = options[0]
+ }
+ return &traversalStrategy{name: "ComputerFinalizationStrategy", configuration: config}
}
-func ProfileStrategy() TraversalStrategy {
- return &traversalStrategy{name: finalizationNamespace + "ProfileStrategy"}
+func ProfileStrategy(options ...map[string]interface{}) TraversalStrategy {
+ config := make(map[string]interface{})
+ if len(options) > 0 {
+ config = options[0]
+ }
+ return &traversalStrategy{name: "ProfileStrategy", configuration: config}
}
// Verification strategies
-func ComputerVerificationStrategy() TraversalStrategy {
- return &traversalStrategy{name: verificationNamespace + "ComputerVerificationStrategy"}
+func ComputerVerificationStrategy(options ...map[string]interface{}) TraversalStrategy {
+ config := make(map[string]interface{})
+ if len(options) > 0 {
+ config = options[0]
+ }
+ return &traversalStrategy{name: "ComputerVerificationStrategy", configuration: config}
}
// EdgeLabelVerificationStrategy does not allow Edge traversal steps to have no label specified.
@@ -250,7 +253,7 @@ func EdgeLabelVerificationStrategy(config ...EdgeLabelVerificationStrategyConfig
configMap["throwException"] = config[0].ThrowException
}
- return &traversalStrategy{name: verificationNamespace + "EdgeLabelVerificationStrategy", configuration: configMap}
+ return &traversalStrategy{name: "EdgeLabelVerificationStrategy", configuration: configMap}
}
// EdgeLabelVerificationStrategyConfig provides configuration options for EdgeLabelVerificationStrategy.
@@ -265,12 +268,12 @@ type EdgeLabelVerificationStrategyConfig struct {
// about the traversal. This strategy is not activated by default. However, graph system providers may choose
// to make this a default strategy in order to ensure their respective strategies are better able to operate.
func LambdaRestrictionStrategy() TraversalStrategy {
- return &traversalStrategy{name: verificationNamespace + "LambdaRestrictionStrategy"}
+ return &traversalStrategy{name: "LambdaRestrictionStrategy"}
}
// ReadOnlyStrategy detects steps marked with Mutating and returns an error if one is found.
func ReadOnlyStrategy() TraversalStrategy {
- return &traversalStrategy{name: verificationNamespace + "ReadOnlyStrategy"}
+ return &traversalStrategy{name: "ReadOnlyStrategy"}
}
// ReservedKeysVerificationStrategy detects property keys that should not be used by the traversal.
@@ -284,7 +287,7 @@ func ReservedKeysVerificationStrategy(config ...ReservedKeysVerificationStrategy
configMap["keys"] = config[0].Keys
}
}
- return &traversalStrategy{name: verificationNamespace + "ReservedKeysVerificationStrategy", configuration: configMap}
+ return &traversalStrategy{name: "ReservedKeysVerificationStrategy", configuration: configMap}
}
// ReservedKeysVerificationStrategyConfig provides configuration options for ReservedKeysVerificationStrategy.
@@ -296,11 +299,11 @@ type ReservedKeysVerificationStrategyConfig struct {
}
func StandardVerificationStrategy() TraversalStrategy {
- return &traversalStrategy{name: verificationNamespace + "StandardVerificationStrategy"}
+ return &traversalStrategy{name: "StandardVerificationStrategy"}
}
func VertexProgramRestrictionStrategy() TraversalStrategy {
- return &traversalStrategy{name: computerVerificationNamespace + "VertexProgramRestrictionStrategy"}
+ return &traversalStrategy{name: "VertexProgramRestrictionStrategy"}
}
// Optimization strategies
@@ -313,20 +316,20 @@ func VertexProgramRestrictionStrategy() TraversalStrategy {
// the Vertex on the other side of an Edge) can be satisfied by trips to incident Graph Elements (e.g. just the Edge
// itself).
func AdjacentToIncidentStrategy() TraversalStrategy {
- return &traversalStrategy{name: optimizationNamespace + "AdjacentToIncidentStrategy"}
+ return &traversalStrategy{name: "AdjacentToIncidentStrategy"}
}
// ByModulatorOptimizationStrategy looks for standard traversals in By-modulators and replaces them with more
// optimized traversals (e.g. TokenTraversal) if possible.
func ByModulatorOptimizationStrategy() TraversalStrategy {
- return &traversalStrategy{name: optimizationNamespace + "ByModulatorOptimizationStrategy"}
+ return &traversalStrategy{name: "ByModulatorOptimizationStrategy"}
}
// CountStrategy optimizes any occurrence of CountGlobalStep followed by an IsStep The idea is to limit
// the number of incoming elements in a way that it's enough for the IsStep to decide whether it evaluates
// true or false. If the traversal already contains a user supplied limit, the strategy won't modify it.
func CountStrategy() TraversalStrategy {
- return &traversalStrategy{name: optimizationNamespace + "CountStrategy"}
+ return &traversalStrategy{name: "CountStrategy"}
}
// EarlyLimitStrategy looks for RangeGlobalSteps that can be moved further left in the traversal and thus be applied
@@ -334,7 +337,7 @@ func CountStrategy() TraversalStrategy {
// If the logical consequence of one or multiple RangeGlobalSteps is an empty result, the strategy will remove
// as many steps as possible and add a NoneStep instead.
func EarlyLimitStrategy() TraversalStrategy {
- return &traversalStrategy{name: optimizationNamespace + "EarlyLimitStrategy"}
+ return &traversalStrategy{name: "EarlyLimitStrategy"}
}
// FilterRankingStrategy reorders filter- and order-steps according to their rank. Step ranks are defined within
@@ -342,18 +345,18 @@ func EarlyLimitStrategy() TraversalStrategy {
// push step labels as far "right" as possible in order to keep Traversers as small and bulkable as possible prior to
// the absolute need for Path-labeling.
func FilterRankingStrategy() TraversalStrategy {
- return &traversalStrategy{name: optimizationNamespace + "FilterRankingStrategy"}
+ return &traversalStrategy{name: "FilterRankingStrategy"}
}
func GraphFilterStrategy() TraversalStrategy {
- return &traversalStrategy{name: computerOptimizationNamespace + "GraphFilterStrategy"}
+ return &traversalStrategy{name: "GraphFilterStrategy"}
}
// IdentityRemovalStrategy looks for IdentityStep instances and removes them.
// If the identity step is labeled, its labels are added to the previous step.
// If the identity step is labeled and it's the first step in the traversal, it stays.
func IdentityRemovalStrategy() TraversalStrategy {
- return &traversalStrategy{name: optimizationNamespace + "IdentityRemovalStrategy"}
+ return &traversalStrategy{name: "IdentityRemovalStrategy"}
}
// IncidentToAdjacentStrategy looks for .OutE().InV(), .InE().OutV() and .BothE().OtherV()
@@ -364,7 +367,7 @@ func IdentityRemovalStrategy() TraversalStrategy {
// the traversal contains a Path step
// the traversal contains a Lambda step
func IncidentToAdjacentStrategy() TraversalStrategy {
- return &traversalStrategy{name: optimizationNamespace + "IncidentToAdjacentStrategy"}
+ return &traversalStrategy{name: "IncidentToAdjacentStrategy"}
}
// InlineFilterStrategy analyzes filter-steps with child traversals that themselves are pure filters. If
@@ -373,7 +376,7 @@ func IncidentToAdjacentStrategy() TraversalStrategy {
// a graph provider may need to reason about when writing their own strategies. As a result, this strategy helps
// increase the likelihood that a provider's filtering optimization will succeed at re-writing the traversal.
func InlineFilterStrategy() TraversalStrategy {
- return &traversalStrategy{name: optimizationNamespace + "InlineFilterStrategy"}
+ return &traversalStrategy{name: "InlineFilterStrategy"}
}
// LazyBarrierStrategy is an OLTP-only strategy that automatically inserts a NoOpBarrierStep after every
@@ -381,25 +384,29 @@ func InlineFilterStrategy() TraversalStrategy {
// traversal's last step or a barrier. NoOpBarrierSteps allow Traversers to be bulked, thus this strategy
// is meant to reduce memory requirements and improve the overall query performance.
func LazyBarrierStrategy() TraversalStrategy {
- return &traversalStrategy{name: optimizationNamespace + "LazyBarrierStrategy"}
+ return &traversalStrategy{name: "LazyBarrierStrategy"}
}
// MatchPredicateStrategy will fold any post-Where() step that maintains a traversal constraint into
// Match(). MatchStep is intelligent with traversal constraint applications and thus, can more
// efficiently use the constraint of WhereTraversalStep or WherePredicateStep.
func MatchPredicateStrategy() TraversalStrategy {
- return &traversalStrategy{name: optimizationNamespace + "MatchPredicateStrategy"}
+ return &traversalStrategy{name: "MatchPredicateStrategy"}
}
-func MessagePassingReductionStrategy() TraversalStrategy {
- return &traversalStrategy{name: computerOptimizationNamespace + "MessagePassingReductionStrategy"}
+func MessagePassingReductionStrategy(options ...map[string]interface{}) TraversalStrategy {
+ config := make(map[string]interface{})
+ if len(options) > 0 {
+ config = options[0]
+ }
+ return &traversalStrategy{name: "MessagePassingReductionStrategy", configuration: config}
}
// OrderLimitStrategy is an OLAP strategy that folds a RangeGlobalStep into a preceding
// OrderGlobalStep. This helps to eliminate traversers early in the traversal and can
// significantly reduce the amount of memory required by the OLAP execution engine.
func OrderLimitStrategy() TraversalStrategy {
- return &traversalStrategy{name: optimizationNamespace + "OrderLimitStrategy"}
+ return &traversalStrategy{name: "OrderLimitStrategy"}
}
// PathProcessorStrategy is an OLAP strategy that does its best to turn non-local children in Where()
@@ -407,13 +414,13 @@ func OrderLimitStrategy() TraversalStrategy {
// PathProcessorStrategy helps to ensure that more traversals meet the local child constraint imposed
// on OLAP traversals.
func PathProcessorStrategy() TraversalStrategy {
- return &traversalStrategy{name: optimizationNamespace + "PathProcessorStrategy"}
+ return &traversalStrategy{name: "PathProcessorStrategy"}
}
// PathRetractionStrategy will remove Paths from the Traversers and increase the likelihood of bulking
// as Path data is not required after Select('b').
func PathRetractionStrategy() TraversalStrategy {
- return &traversalStrategy{name: optimizationNamespace + "PathRetractionStrategy"}
+ return &traversalStrategy{name: "PathRetractionStrategy"}
}
// ProductiveByStrategy takes an argument of By() and wraps it CoalesceStep so that the result is either
@@ -426,7 +433,7 @@ func ProductiveByStrategy(config ...ProductiveByStrategyConfig) TraversalStrateg
configMap["productiveKeys"] = config[0].ProductiveKeys
}
- return &traversalStrategy{name: optimizationNamespace + "ProductiveByStrategy", configuration: configMap}
+ return &traversalStrategy{name: "ProductiveByStrategy", configuration: configMap}
}
// ProductiveByStrategyConfig provides configuration options for ProductiveByStrategy.
@@ -443,7 +450,7 @@ type ProductiveByStrategyConfig struct {
// LoopsStep
// LambdaHolder
func RepeatUnrollStrategy() TraversalStrategy {
- return &traversalStrategy{name: optimizationNamespace + "RepeatUnrollStrategy"}
+ return &traversalStrategy{name: "RepeatUnrollStrategy"}
}
// RemoteStrategy reconstructs a Traversal by appending a RemoteStep to its end. That step will submit the Traversal to
@@ -455,7 +462,7 @@ func RemoteStrategy(connection DriverRemoteConnection) TraversalStrategy {
return
}
- rs, err := connection.submitBytecode(g.Bytecode)
+ rs, err := connection.submitGremlinLang(g.GremlinLang)
if err != nil {
return
}
diff --git a/gremlin-go/driver/strategies_test.go b/gremlin-go/driver/strategies_test.go
index c519be5d66..2a855be2d5 100644
--- a/gremlin-go/driver/strategies_test.go
+++ b/gremlin-go/driver/strategies_test.go
@@ -21,16 +21,16 @@ package gremlingo
import (
"crypto/tls"
+ "strings"
"testing"
"github.com/stretchr/testify/assert"
)
-func getModernGraph(t *testing.T, url string, auth AuthInfoProvider, tls *tls.Config) *GraphTraversalSource {
+func getModernGraph(t *testing.T, url string, tls *tls.Config) *GraphTraversalSource {
remote, err := NewDriverRemoteConnection(url,
func(settings *DriverRemoteConnectionSettings) {
settings.TlsConfig = tls
- settings.AuthInfo = auth
settings.TraversalSource = testServerModernGraphAlias
})
assert.Nil(t, err)
@@ -44,7 +44,7 @@ func TestStrategy(t *testing.T) {
testNoAuthUrl := getEnvOrDefaultString("GREMLIN_SERVER_URL", noAuthUrl)
t.Run("Test read with ConnectiveStrategy", func(t *testing.T) {
- g := getModernGraph(t, testNoAuthUrl, &AuthInfo{}, &tls.Config{})
+ g := getModernGraph(t, testNoAuthUrl, &tls.Config{})
defer g.remoteConnection.Close()
count, err := g.WithStrategies(ConnectiveStrategy()).V().Count().ToList()
@@ -57,7 +57,7 @@ func TestStrategy(t *testing.T) {
})
t.Run("Test read with OptionsStrategy", func(t *testing.T) {
- g := getModernGraph(t, testNoAuthUrl, &AuthInfo{}, &tls.Config{})
+ g := getModernGraph(t, testNoAuthUrl, &tls.Config{})
defer g.remoteConnection.Close()
count, err := g.WithStrategies(OptionsStrategy(map[string]interface{}{"a": "b"})).V().Count().ToList()
@@ -70,7 +70,7 @@ func TestStrategy(t *testing.T) {
})
t.Run("Test read with PartitionStrategy", func(t *testing.T) {
- g := getModernGraph(t, testNoAuthUrl, &AuthInfo{}, &tls.Config{})
+ g := getModernGraph(t, testNoAuthUrl, &tls.Config{})
defer g.remoteConnection.Close()
config := PartitionStrategyConfig{
@@ -89,7 +89,7 @@ func TestStrategy(t *testing.T) {
})
t.Run("Test read with SeedStrategy", func(t *testing.T) {
- g := getModernGraph(t, testNoAuthUrl, &AuthInfo{}, &tls.Config{})
+ g := getModernGraph(t, testNoAuthUrl, &tls.Config{})
defer g.remoteConnection.Close()
config := SeedStrategyConfig{Seed: 1}
@@ -103,7 +103,7 @@ func TestStrategy(t *testing.T) {
})
t.Run("Test read with SubgraphStrategy", func(t *testing.T) {
- g := getModernGraph(t, testNoAuthUrl, &AuthInfo{}, &tls.Config{})
+ g := getModernGraph(t, testNoAuthUrl, &tls.Config{})
defer g.remoteConnection.Close()
config := SubgraphStrategyConfig{
Vertices: T__.HasLabel(testLabel),
@@ -120,23 +120,16 @@ func TestStrategy(t *testing.T) {
assert.Equal(t, int32(0), val)
})
- t.Run("Test Bytecode generation for MatchAlgorithmStrategy", func(t *testing.T) {
- g := getModernGraph(t, testNoAuthUrl, &AuthInfo{}, &tls.Config{})
- defer g.remoteConnection.Close()
+ t.Run("Test GremlinLang generation for MatchAlgorithmStrategy", func(t *testing.T) {
+ g := NewGraphTraversalSource(nil, nil)
config := MatchAlgorithmStrategyConfig{MatchAlgorithm: "greedy"}
- bytecode := g.WithStrategies(MatchAlgorithmStrategy(config)).bytecode
- assert.Equal(t, 1, len(bytecode.sourceInstructions))
- assert.Equal(t, 1, len(bytecode.sourceInstructions[0].arguments))
- assert.Equal(t, "withStrategies", bytecode.sourceInstructions[0].operator)
- assert.Equal(t, "org.apache.tinkerpop.gremlin.process.traversal.strategy.finalization.MatchAlgorithmStrategy",
- bytecode.sourceInstructions[0].arguments[0].(*traversalStrategy).name)
- assert.Equal(t, map[string]interface{}{"matchAlgorithm": "greedy"},
- bytecode.sourceInstructions[0].arguments[0].(*traversalStrategy).configuration)
+ gl := g.WithStrategies(MatchAlgorithmStrategy(config)).gremlinLang
+ assert.True(t, strings.Contains(gl.GetGremlin(), "withStrategies(new MatchAlgorithmStrategy(matchAlgorithm:\"greedy\"))"))
})
t.Run("Test read with AdjacentToIncidentStrategy", func(t *testing.T) {
- g := getModernGraph(t, testNoAuthUrl, &AuthInfo{}, &tls.Config{})
+ g := getModernGraph(t, testNoAuthUrl, &tls.Config{})
defer g.remoteConnection.Close()
count, err := g.WithStrategies(AdjacentToIncidentStrategy()).V().Count().ToList()
@@ -149,7 +142,7 @@ func TestStrategy(t *testing.T) {
})
t.Run("Test read with ByModulatorOptimizationStrategy", func(t *testing.T) {
- g := getModernGraph(t, testNoAuthUrl, &AuthInfo{}, &tls.Config{})
+ g := getModernGraph(t, testNoAuthUrl, &tls.Config{})
defer g.remoteConnection.Close()
count, err := g.WithStrategies(ByModulatorOptimizationStrategy()).V().Count().ToList()
@@ -162,7 +155,7 @@ func TestStrategy(t *testing.T) {
})
t.Run("Test read with CountStrategy", func(t *testing.T) {
- g := getModernGraph(t, testNoAuthUrl, &AuthInfo{}, &tls.Config{})
+ g := getModernGraph(t, testNoAuthUrl, &tls.Config{})
defer g.remoteConnection.Close()
count, err := g.WithStrategies(CountStrategy()).V().Count().ToList()
@@ -175,7 +168,7 @@ func TestStrategy(t *testing.T) {
})
t.Run("Test read with EarlyLimitStrategy", func(t *testing.T) {
- g := getModernGraph(t, testNoAuthUrl, &AuthInfo{}, &tls.Config{})
+ g := getModernGraph(t, testNoAuthUrl, &tls.Config{})
defer g.remoteConnection.Close()
count, err := g.WithStrategies(EarlyLimitStrategy()).V().Count().ToList()
@@ -188,7 +181,7 @@ func TestStrategy(t *testing.T) {
})
t.Run("Test read with FilterRankingStrategy", func(t *testing.T) {
- g := getModernGraph(t, testNoAuthUrl, &AuthInfo{}, &tls.Config{})
+ g := getModernGraph(t, testNoAuthUrl, &tls.Config{})
defer g.remoteConnection.Close()
count, err := g.WithStrategies(FilterRankingStrategy()).V().Count().ToList()
@@ -201,7 +194,7 @@ func TestStrategy(t *testing.T) {
})
t.Run("Test read with IdentityRemovalStrategy", func(t *testing.T) {
- g := getModernGraph(t, testNoAuthUrl, &AuthInfo{}, &tls.Config{})
+ g := getModernGraph(t, testNoAuthUrl, &tls.Config{})
defer g.remoteConnection.Close()
count, err := g.WithStrategies(IdentityRemovalStrategy()).V().Count().ToList()
@@ -214,7 +207,7 @@ func TestStrategy(t *testing.T) {
})
t.Run("Test read with IncidentToAdjacentStrategy", func(t *testing.T) {
- g := getModernGraph(t, testNoAuthUrl, &AuthInfo{}, &tls.Config{})
+ g := getModernGraph(t, testNoAuthUrl, &tls.Config{})
defer g.remoteConnection.Close()
count, err := g.WithStrategies(IncidentToAdjacentStrategy()).V().Count().ToList()
@@ -227,7 +220,7 @@ func TestStrategy(t *testing.T) {
})
t.Run("Test read with InlineFilterStrategy", func(t *testing.T) {
- g := getModernGraph(t, testNoAuthUrl, &AuthInfo{}, &tls.Config{})
+ g := getModernGraph(t, testNoAuthUrl, &tls.Config{})
defer g.remoteConnection.Close()
count, err := g.WithStrategies(InlineFilterStrategy()).V().Count().ToList()
@@ -240,7 +233,7 @@ func TestStrategy(t *testing.T) {
})
t.Run("Test read with LazyBarrierStrategy", func(t *testing.T) {
- g := getModernGraph(t, testNoAuthUrl, &AuthInfo{}, &tls.Config{})
+ g := getModernGraph(t, testNoAuthUrl, &tls.Config{})
defer g.remoteConnection.Close()
count, err := g.WithStrategies(LazyBarrierStrategy()).V().Count().ToList()
@@ -253,7 +246,7 @@ func TestStrategy(t *testing.T) {
})
t.Run("Test read with MatchPredicateStrategy", func(t *testing.T) {
- g := getModernGraph(t, testNoAuthUrl, &AuthInfo{}, &tls.Config{})
+ g := getModernGraph(t, testNoAuthUrl, &tls.Config{})
defer g.remoteConnection.Close()
count, err := g.WithStrategies(MatchPredicateStrategy()).V().Count().ToList()
@@ -266,7 +259,7 @@ func TestStrategy(t *testing.T) {
})
t.Run("Test read with OrderLimitStrategy", func(t *testing.T) {
- g := getModernGraph(t, testNoAuthUrl, &AuthInfo{}, &tls.Config{})
+ g := getModernGraph(t, testNoAuthUrl, &tls.Config{})
defer g.remoteConnection.Close()
count, err := g.WithStrategies(OrderLimitStrategy()).V().Count().ToList()
@@ -279,7 +272,7 @@ func TestStrategy(t *testing.T) {
})
t.Run("Test read with PathProcessorStrategy", func(t *testing.T) {
- g := getModernGraph(t, testNoAuthUrl, &AuthInfo{}, &tls.Config{})
+ g := getModernGraph(t, testNoAuthUrl, &tls.Config{})
defer g.remoteConnection.Close()
count, err := g.WithStrategies(PathProcessorStrategy()).V().Count().ToList()
@@ -292,7 +285,7 @@ func TestStrategy(t *testing.T) {
})
t.Run("Test read with PathRetractionStrategy", func(t *testing.T) {
- g := getModernGraph(t, testNoAuthUrl, &AuthInfo{}, &tls.Config{})
+ g := getModernGraph(t, testNoAuthUrl, &tls.Config{})
defer g.remoteConnection.Close()
count, err := g.WithStrategies(PathRetractionStrategy()).V().Count().ToList()
@@ -305,7 +298,7 @@ func TestStrategy(t *testing.T) {
})
t.Run("Test read with ProductiveByStrategy", func(t *testing.T) {
- g := getModernGraph(t, testNoAuthUrl, &AuthInfo{}, &tls.Config{})
+ g := getModernGraph(t, testNoAuthUrl, &tls.Config{})
defer g.remoteConnection.Close()
config := ProductiveByStrategyConfig{ProductiveKeys: []string{"a", "b"}}
@@ -319,7 +312,7 @@ func TestStrategy(t *testing.T) {
})
t.Run("Test read with RepeatUnrollStrategy", func(t *testing.T) {
- g := getModernGraph(t, testNoAuthUrl, &AuthInfo{}, &tls.Config{})
+ g := getModernGraph(t, testNoAuthUrl, &tls.Config{})
defer g.remoteConnection.Close()
count, err := g.WithStrategies(RepeatUnrollStrategy()).V().Count().ToList()
@@ -332,7 +325,7 @@ func TestStrategy(t *testing.T) {
})
t.Run("Test read with EdgeLabelVerificationStrategy", func(t *testing.T) {
- g := getModernGraph(t, testNoAuthUrl, &AuthInfo{}, &tls.Config{})
+ g := getModernGraph(t, testNoAuthUrl, &tls.Config{})
defer g.remoteConnection.Close()
config := EdgeLabelVerificationStrategyConfig{
@@ -349,7 +342,7 @@ func TestStrategy(t *testing.T) {
})
t.Run("Test read with LambdaRestrictionStrategy", func(t *testing.T) {
- g := getModernGraph(t, testNoAuthUrl, &AuthInfo{}, &tls.Config{})
+ g := getModernGraph(t, testNoAuthUrl, &tls.Config{})
defer g.remoteConnection.Close()
count, err := g.WithStrategies(LambdaRestrictionStrategy()).V().Count().ToList()
@@ -362,7 +355,7 @@ func TestStrategy(t *testing.T) {
})
t.Run("Test read with TestReadOnlyStrategy", func(t *testing.T) {
- g := getModernGraph(t, testNoAuthUrl, &AuthInfo{}, &tls.Config{})
+ g := getModernGraph(t, testNoAuthUrl, &tls.Config{})
defer g.remoteConnection.Close()
count, err := g.WithStrategies(ReadOnlyStrategy()).V().Count().ToList()
@@ -375,7 +368,7 @@ func TestStrategy(t *testing.T) {
})
t.Run("Test write with TestReadOnlyStrategy", func(t *testing.T) {
- g := getModernGraph(t, testNoAuthUrl, &AuthInfo{}, &tls.Config{})
+ g := getModernGraph(t, testNoAuthUrl, &tls.Config{})
defer g.remoteConnection.Close()
promise := g.WithStrategies(ReadOnlyStrategy()).AddV("person").Property("name", "foo").Iterate()
@@ -383,7 +376,7 @@ func TestStrategy(t *testing.T) {
})
t.Run("Test read with ReservedKeysVerificationStrategy", func(t *testing.T) {
- g := getModernGraph(t, testNoAuthUrl, &AuthInfo{}, &tls.Config{})
+ g := getModernGraph(t, testNoAuthUrl, &tls.Config{})
defer g.remoteConnection.Close()
config := ReservedKeysVerificationStrategyConfig{
@@ -402,7 +395,7 @@ func TestStrategy(t *testing.T) {
})
t.Run("Test read with RepeatUnrollStrategy", func(t *testing.T) {
- g := getModernGraph(t, testNoAuthUrl, &AuthInfo{}, &tls.Config{})
+ g := getModernGraph(t, testNoAuthUrl, &tls.Config{})
defer g.remoteConnection.Close()
count, err := g.WithStrategies(RepeatUnrollStrategy()).V().Count().ToList()
@@ -415,7 +408,7 @@ func TestStrategy(t *testing.T) {
})
t.Run("Test without strategies MessagePassingReductionStrategy", func(t *testing.T) {
- g := getModernGraph(t, testNoAuthUrl, &AuthInfo{}, &tls.Config{})
+ g := getModernGraph(t, testNoAuthUrl, &tls.Config{})
defer g.remoteConnection.Close()
count, err := g.WithoutStrategies(MessagePassingReductionStrategy()).V().Count().ToList()
@@ -427,4 +420,28 @@ func TestStrategy(t *testing.T) {
assert.Equal(t, int32(6), val)
})
+ t.Run("Test GremlinLang generation for simple custom strategies", func(t *testing.T) {
+ g := NewGraphTraversalSource(nil, nil)
+
+ customStrategy := NewTraversalStrategy("CustomSingletonStrategy", nil)
+ gl := g.WithStrategies(customStrategy).gremlinLang
+ assert.True(t, strings.Contains(gl.GetGremlin(), "withStrategies(CustomSingletonStrategy)"))
+ })
+
+ t.Run("Test GremlinLang generation for config custom strategies", func(t *testing.T) {
+ g := NewGraphTraversalSource(nil, nil)
+
+ customStrategy := NewTraversalStrategy("CustomConfigurableStrategy",
+ map[string]interface{}{"stringKey": "string value", "intKey": 5, "booleanKey": true})
+ gl := g.WithStrategies(customStrategy).gremlinLang
+ // Note that config map doesn't guarantee order, so assert individually
+ assert.True(t, strings.Contains(gl.GetGremlin(),
+ "withStrategies(new CustomConfigurableStrategy("))
+ assert.True(t, strings.Contains(gl.GetGremlin(),
+ "stringKey:\"string value\""))
+ assert.True(t, strings.Contains(gl.GetGremlin(),
+ "intKey:5"))
+ assert.True(t, strings.Contains(gl.GetGremlin(),
+ "booleanKey:true"))
+ })
}
diff --git a/gremlin-go/driver/streamingDeserializer.go b/gremlin-go/driver/streamingDeserializer.go
new file mode 100644
index 0000000000..0c69460a1c
--- /dev/null
+++ b/gremlin-go/driver/streamingDeserializer.go
@@ -0,0 +1,617 @@
+/*
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+*/
+
+package gremlingo
+
+import (
+ "bufio"
+ "encoding/binary"
+ "fmt"
+ "io"
+ "math"
+ "math/big"
+ "reflect"
+ "time"
+
+ "github.com/google/uuid"
+)
+
+// StreamingDeserializer reads GraphBinary data directly from an io.Reader,
+// enabling streaming deserialization of server responses.
+//
+// Streaming Behavior:
+// The deserializer is designed to work with HTTP chunked transfer encoding where
+// the server streams results as they become available. Key behaviors:
+//
+// 1. Blocking on partial data: When reading an object, if the underlying reader
+// doesn't have enough bytes available, the deserializer blocks until the data
+// arrives. This is handled by io.ReadFull which waits for the exact number of
+// bytes needed based on GraphBinary's self-describing format (type codes and
+// length prefixes).
+//
+// 2. Chunk boundary independence: Go's HTTP client may receive data in chunks that
+// don't align with server-sent GraphBinary object boundaries. The deserializer
+// handles this transparently - it reads exactly the bytes needed for each object,
+// blocking if necessary, regardless of how the data was chunked by the network.
+//
+// 3. Immediate object delivery: Each complete object is returned as soon as it's
+// fully read, allowing the caller to process results incrementally rather than
+// waiting for the entire response.
+//
+// The bufio.Reader wrapper provides efficient buffering without affecting the
+// streaming semantics - it simply reduces the number of underlying read syscalls.
+type StreamingDeserializer struct {
+ r *bufio.Reader
+ buf [8]byte
+ err error // sticky error
+}
+
+// GraphBinary flag for bulked list/set
+const flagBulked = 0x02
+
+// NewStreamingDeserializer creates a new StreamingDeserializer that reads from the given io.Reader.
+// The reader is wrapped in a buffered reader for efficient reading.
+func NewStreamingDeserializer(r io.Reader) *StreamingDeserializer {
+ return &StreamingDeserializer{r: bufio.NewReaderSize(r, 8192)}
+}
+
+func (d *StreamingDeserializer) readByte() (byte, error) {
+ if d.err != nil {
+ return 0, d.err
+ }
+ b, err := d.r.ReadByte()
+ if err != nil {
+ d.err = err
+ return 0, err
+ }
+ return b, nil
+}
+
+func (d *StreamingDeserializer) readBytes(n int) ([]byte, error) {
+ if d.err != nil {
+ return nil, d.err
+ }
+ buf := make([]byte, n)
+ _, err := io.ReadFull(d.r, buf)
+ if err != nil {
+ d.err = err
+ return nil, err
+ }
+ return buf, nil
+}
+
+func (d *StreamingDeserializer) readInt32() (int32, error) {
+ if d.err != nil {
+ return 0, d.err
+ }
+ _, err := io.ReadFull(d.r, d.buf[:4])
+ if err != nil {
+ d.err = err
+ return 0, err
+ }
+ return int32(binary.BigEndian.Uint32(d.buf[:4])), nil
+}
+
+func (d *StreamingDeserializer) readUint32() (uint32, error) {
+ if d.err != nil {
+ return 0, d.err
+ }
+ _, err := io.ReadFull(d.r, d.buf[:4])
+ if err != nil {
+ d.err = err
+ return 0, err
+ }
+ return binary.BigEndian.Uint32(d.buf[:4]), nil
+}
+
+func (d *StreamingDeserializer) readInt64() (int64, error) {
+ if d.err != nil {
+ return 0, d.err
+ }
+ _, err := io.ReadFull(d.r, d.buf[:8])
+ if err != nil {
+ d.err = err
+ return 0, err
+ }
+ return int64(binary.BigEndian.Uint64(d.buf[:8])), nil
+}
+
+// ReadHeader reads and validates the GraphBinary response header.
+// This must be called before reading any objects from the stream.
+func (d *StreamingDeserializer) ReadHeader() error {
+ if _, err := d.readByte(); err != nil {
+ return err
+ }
+ _, err := d.readByte()
+ return err
+}
+
+// ReadFullyQualified reads the next fully-qualified GraphBinary value from the stream.
+// Returns the deserialized object, or an error if reading fails.
+// When the end of the result stream is reached, this returns a Marker equal to EndOfStream().
+func (d *StreamingDeserializer) ReadFullyQualified() (interface{}, error) {
+ dtByte, err := d.readByte()
+ if err != nil {
+ return nil, err
+ }
+ dt := dataType(dtByte)
+ if dt == nullType {
+ if _, err := d.readByte(); err != nil {
+ return nil, err
+ }
+ return nil, nil
+ }
+ flag, err := d.readByte()
+ if err != nil {
+ return nil, err
+ }
+ if flag == valueFlagNull {
+ return nil, nil
+ }
+ return d.readValue(dt, flag)
+}
+
+func (d *StreamingDeserializer) readValue(dt dataType, flag byte) (interface{}, error) {
+ switch dt {
+ case intType:
+ return d.readInt32()
+ case longType:
+ return d.readInt64()
+ case stringType:
+ return d.readString()
+ case doubleType:
+ if d.err != nil {
+ return nil, d.err
+ }
+ if _, err := io.ReadFull(d.r, d.buf[:8]); err != nil {
+ d.err = err
+ return nil, err
+ }
+ return math.Float64frombits(binary.BigEndian.Uint64(d.buf[:8])), nil
+ case floatType:
+ if d.err != nil {
+ return nil, d.err
+ }
+ if _, err := io.ReadFull(d.r, d.buf[:4]); err != nil {
+ d.err = err
+ return nil, err
+ }
+ return math.Float32frombits(binary.BigEndian.Uint32(d.buf[:4])), nil
+ case booleanType:
+ b, err := d.readByte()
+ return b != 0, err
+ case byteType:
+ return d.readByte()
+ case shortType:
+ if d.err != nil {
+ return nil, d.err
+ }
+ if _, err := io.ReadFull(d.r, d.buf[:2]); err != nil {
+ d.err = err
+ return nil, err
+ }
+ return int16(binary.BigEndian.Uint16(d.buf[:2])), nil
+ case uuidType:
+ buf, err := d.readBytes(16)
+ if err != nil {
+ return nil, err
+ }
+ id, err := uuid.FromBytes(buf)
+ if err != nil {
+ return nil, err
+ }
+ return id, nil
+ case listType:
+ return d.readList(flag == flagBulked)
+ case setType:
+ list, err := d.readList(flag == flagBulked)
+ if err != nil {
+ return nil, err
+ }
+ return NewSimpleSet(list.([]interface{})...), nil
+ case mapType:
+ return d.readMap()
+ case vertexType:
+ return d.readVertex(true)
+ case edgeType:
+ return d.readEdge()
+ case pathType:
+ return d.readPath()
+ case propertyType:
+ return d.readProperty()
+ case vertexPropertyType:
+ return d.readVertexProperty()
+ case bigIntegerType:
+ return d.readBigInt()
+ case bigDecimalType:
+ return d.readBigDecimal()
+ case datetimeType:
+ return d.readDateTime()
+ case durationType:
+ return d.readDuration()
+ case markerType:
+ b, err := d.readByte()
+ if err != nil {
+ return nil, err
+ }
+ return Of(b)
+ case byteBuffer:
+ return d.readByteBuffer()
+ case tType, directionType, mergeType, gTypeType:
+ return d.readEnum()
+ default:
+ return nil, newError(err0408GetSerializerToReadUnknownTypeError, dt)
+ }
+}
+
+func (d *StreamingDeserializer) readString() (string, error) {
+ length, err := d.readInt32()
+ if err != nil {
+ return "", err
+ }
+ if length == 0 {
+ return "", nil
+ }
+ buf, err := d.readBytes(int(length))
+ if err != nil {
+ return "", err
+ }
+ return string(buf), nil
+}
+
+func (d *StreamingDeserializer) readList(bulked bool) (interface{}, error) {
+ length, err := d.readInt32()
+ if err != nil {
+ return nil, err
+ }
+ list := make([]interface{}, 0, length)
+ for i := int32(0); i < length; i++ {
+ val, err := d.ReadFullyQualified()
+ if err != nil {
+ return nil, err
+ }
+ if bulked {
+ bulk, err := d.readInt64()
+ if err != nil {
+ return nil, err
+ }
+ for j := int64(0); j < bulk; j++ {
+ list = append(list, val)
+ }
+ } else {
+ list = append(list, val)
+ }
+ }
+ return list, nil
+}
+
+func (d *StreamingDeserializer) readMap() (interface{}, error) {
+ length, err := d.readUint32()
+ if err != nil {
+ return nil, err
+ }
+ m := make(map[interface{}]interface{}, length)
+ for i := uint32(0); i < length; i++ {
+ key, err := d.ReadFullyQualified()
+ if err != nil {
+ return nil, err
+ }
+ val, err := d.ReadFullyQualified()
+ if err != nil {
+ return nil, err
+ }
+ if key == nil {
+ m[nil] = val
+ } else if reflect.TypeOf(key).Comparable() {
+ m[key] = val
+ } else if reflect.TypeOf(key).Kind() == reflect.Map {
+ m[&key] = val
+ } else {
+ m[fmt.Sprint(key)] = val
+ }
+ }
+ return m, nil
+}
+
+func (d *StreamingDeserializer) readVertex(withProps bool) (*Vertex, error) {
+ id, err := d.ReadFullyQualified()
+ if err != nil {
+ return nil, err
+ }
+ labels, err := d.readList(false)
+ if err != nil {
+ return nil, err
+ }
+ labelSlice, ok := labels.([]interface{})
+ if !ok || len(labelSlice) == 0 {
+ return nil, newError(err0404ReadNullTypeError)
+ }
+ label, ok := labelSlice[0].(string)
+ if !ok {
+ return nil, newError(err0404ReadNullTypeError)
+ }
+ v := &Vertex{Element: Element{Id: id, Label: label}}
+ if withProps {
+ props, err := d.ReadFullyQualified()
+ if err != nil {
+ return nil, err
+ }
+ v.Properties = make([]interface{}, 0)
+ if props != nil {
+ v.Properties = props
+ }
+ }
+ return v, nil
+}
+
+func (d *StreamingDeserializer) readEdge() (*Edge, error) {
+ id, err := d.ReadFullyQualified()
+ if err != nil {
+ return nil, err
+ }
+ labels, err := d.readList(false)
+ if err != nil {
+ return nil, err
+ }
+ labelSlice, ok := labels.([]interface{})
+ if !ok || len(labelSlice) == 0 {
+ return nil, newError(err0404ReadNullTypeError)
+ }
+ label, ok := labelSlice[0].(string)
+ if !ok {
+ return nil, newError(err0404ReadNullTypeError)
+ }
+ inV, err := d.readVertex(false)
+ if err != nil {
+ return nil, err
+ }
+ outV, err := d.readVertex(false)
+ if err != nil {
+ return nil, err
+ }
+ if _, err := d.readBytes(2); err != nil {
+ return nil, err
+ }
+ props, err := d.ReadFullyQualified()
+ if err != nil {
+ return nil, err
+ }
+ e := &Edge{
+ Element: Element{Id: id, Label: label},
+ InV: *inV,
+ OutV: *outV,
+ }
+ e.Properties = make([]interface{}, 0)
+ if props != nil {
+ e.Properties = props
+ }
+ return e, nil
+}
+
+func (d *StreamingDeserializer) readPath() (*Path, error) {
+ labels, err := d.ReadFullyQualified()
+ if err != nil {
+ return nil, err
+ }
+ objects, err := d.ReadFullyQualified()
+ if err != nil {
+ return nil, err
+ }
+ objectSlice, ok := objects.([]interface{})
+ if !ok {
+ return nil, newError(err0404ReadNullTypeError)
+ }
+ path := &Path{Objects: objectSlice}
+ if labels != nil {
+ labelSlice, ok := labels.([]interface{})
+ if !ok {
+ return nil, newError(err0404ReadNullTypeError)
+ }
+ for _, l := range labelSlice {
+ set, ok := l.(*SimpleSet)
+ if !ok {
+ return nil, newError(err0404ReadNullTypeError)
+ }
+ path.Labels = append(path.Labels, set)
+ }
+ }
+ return path, nil
+}
+
+func (d *StreamingDeserializer) readProperty() (*Property, error) {
+ key, err := d.readString()
+ if err != nil {
+ return nil, err
+ }
+ value, err := d.ReadFullyQualified()
+ if err != nil {
+ return nil, err
+ }
+ if _, err := d.readBytes(2); err != nil {
+ return nil, err
+ }
+ return &Property{Key: key, Value: value}, nil
+}
+
+func (d *StreamingDeserializer) readVertexProperty() (*VertexProperty, error) {
+ id, err := d.ReadFullyQualified()
+ if err != nil {
+ return nil, err
+ }
+ labels, err := d.readList(false)
+ if err != nil {
+ return nil, err
+ }
+ labelSlice, ok := labels.([]interface{})
+ if !ok || len(labelSlice) == 0 {
+ return nil, newError(err0404ReadNullTypeError)
+ }
+ label, ok := labelSlice[0].(string)
+ if !ok {
+ return nil, newError(err0404ReadNullTypeError)
+ }
+ value, err := d.ReadFullyQualified()
+ if err != nil {
+ return nil, err
+ }
+ if _, err := d.readBytes(2); err != nil {
+ return nil, err
+ }
+ props, err := d.ReadFullyQualified()
+ if err != nil {
+ return nil, err
+ }
+ vp := &VertexProperty{
+ Element: Element{Id: id, Label: label},
+ Value: value,
+ }
+ vp.Properties = make([]interface{}, 0)
+ if props != nil {
+ vp.Properties = props
+ }
+ return vp, nil
+}
+
+func (d *StreamingDeserializer) readBigInt() (*big.Int, error) {
+ length, err := d.readInt32()
+ if err != nil {
+ return nil, err
+ }
+ if length == 0 {
+ return big.NewInt(0), nil
+ }
+ b, err := d.readBytes(int(length))
+ if err != nil {
+ return nil, err
+ }
+ bi := big.NewInt(0).SetBytes(b)
+ if b[0]&0x80 != 0 {
+ one := big.NewInt(1)
+ bitLen := uint((len(b)*8)/8+1) * 8
+ bi.Sub(bi, new(big.Int).Lsh(one, bitLen))
+ }
+ return bi, nil
+}
+
+func (d *StreamingDeserializer) readBigDecimal() (*BigDecimal, error) {
+ scale, err := d.readInt32()
+ if err != nil {
+ return nil, err
+ }
+ unscaled, err := d.readBigInt()
+ if err != nil {
+ return nil, err
+ }
+ return &BigDecimal{Scale: scale, UnscaledValue: unscaled}, nil
+}
+
+func (d *StreamingDeserializer) readDateTime() (time.Time, error) {
+ year, err := d.readInt32()
+ if err != nil {
+ return time.Time{}, err
+ }
+ month, err := d.readByte()
+ if err != nil {
+ return time.Time{}, err
+ }
+ day, err := d.readByte()
+ if err != nil {
+ return time.Time{}, err
+ }
+ totalNS, err := d.readInt64()
+ if err != nil {
+ return time.Time{}, err
+ }
+ offset, err := d.readInt32()
+ if err != nil {
+ return time.Time{}, err
+ }
+ ns := totalNS % 1e9
+ totalS := totalNS / 1e9
+ s := totalS % 60
+ totalM := totalS / 60
+ m := totalM % 60
+ h := totalM / 60
+ return time.Date(int(year), time.Month(month), int(day), int(h), int(m), int(s), int(ns), GetTimezoneFromOffset(int(offset))), nil
+}
+
+func (d *StreamingDeserializer) readDuration() (time.Duration, error) {
+ seconds, err := d.readInt64()
+ if err != nil {
+ return 0, err
+ }
+ nanos, err := d.readInt32()
+ if err != nil {
+ return 0, err
+ }
+ return time.Duration(seconds*int64(time.Second) + int64(nanos)), nil
+}
+
+func (d *StreamingDeserializer) readByteBuffer() (*ByteBuffer, error) {
+ length, err := d.readInt32()
+ if err != nil {
+ return nil, err
+ }
+ data, err := d.readBytes(int(length))
+ if err != nil {
+ return nil, err
+ }
+ return &ByteBuffer{Data: data}, nil
+}
+
+func (d *StreamingDeserializer) readEnum() (string, error) {
+ if _, err := d.readByte(); err != nil { // type code (string)
+ return "", err
+ }
+ if _, err := d.readByte(); err != nil { // null flag
+ return "", err
+ }
+ return d.readString()
+}
+
+// ReadStatus reads the response status after the EndOfStream marker.
+// Returns the status code, message, exception string, and any error encountered.
+// This should be called after ReadFullyQualified() returns an EndOfStream marker.
+func (d *StreamingDeserializer) ReadStatus() (code uint32, message string, exception string, err error) {
+ code, err = d.readUint32()
+ if err != nil {
+ return 0, "", "", err
+ }
+ flag, err := d.readByte()
+ if err != nil {
+ return code, "", "", err
+ }
+ if flag != valueFlagNull {
+ message, err = d.readString()
+ if err != nil {
+ return code, "", "", err
+ }
+ }
+ flag, err = d.readByte()
+ if err != nil {
+ return code, message, "", err
+ }
+ if flag != valueFlagNull {
+ exception, err = d.readString()
+ if err != nil {
+ return code, message, "", err
+ }
+ }
+ return code, message, exception, nil
+}
diff --git a/gremlin-go/driver/streamingDeserializer_test.go b/gremlin-go/driver/streamingDeserializer_test.go
new file mode 100644
index 0000000000..96cc7cdbe0
--- /dev/null
+++ b/gremlin-go/driver/streamingDeserializer_test.go
@@ -0,0 +1,402 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements. See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership. The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License. You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied. See the License for the
+specific language governing permissions and limitations
+under the License.
+*/
+
+package gremlingo
+
+import (
+ "bytes"
+ "io"
+ "sync"
+ "testing"
+ "time"
+
+ "github.com/stretchr/testify/assert"
+)
+
+// slowReader simulates a network stream that delivers data in chunks with delays.
+// This mimics how Go's HTTP client receives chunked transfer-encoded responses
+// where chunk boundaries don't align with GraphBinary object boundaries.
+type slowReader struct {
+ chunks [][]byte
+ delay time.Duration
+ index int
+ offset int
+ mu sync.Mutex
+}
+
+func newSlowReader(chunks [][]byte, delay time.Duration) *slowReader {
+ return &slowReader{chunks: chunks, delay: delay}
+}
+
+func (r *slowReader) Read(p []byte) (n int, err error) {
+ r.mu.Lock()
+ defer r.mu.Unlock()
+
+ if r.index >= len(r.chunks) {
+ return 0, io.EOF
+ }
+
+ // Simulate network delay between chunks
+ if r.offset == 0 && r.index > 0 {
+ r.mu.Unlock()
+ time.Sleep(r.delay)
+ r.mu.Lock()
+ }
+
+ chunk := r.chunks[r.index]
+ remaining := chunk[r.offset:]
+ n = copy(p, remaining)
+ r.offset += n
+
+ if r.offset >= len(chunk) {
+ r.index++
+ r.offset = 0
+ }
+
+ return n, nil
+}
+
+func TestStreamingDeserializer(t *testing.T) {
+ t.Run("readInt32", func(t *testing.T) {
+ data := []byte{0x00, 0x00, 0x00, 0x2A} // 42
+ d := NewStreamingDeserializer(bytes.NewReader(data))
+ val, err := d.readInt32()
+ assert.Nil(t, err)
+ assert.Equal(t, int32(42), val)
+ })
+
+ t.Run("readInt64", func(t *testing.T) {
+ data := []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x64} // 100
+ d := NewStreamingDeserializer(bytes.NewReader(data))
+ val, err := d.readInt64()
+ assert.Nil(t, err)
+ assert.Equal(t, int64(100), val)
+ })
+
+ t.Run("readString", func(t *testing.T) {
+ data := []byte{0x00, 0x00, 0x00, 0x05, 'h', 'e', 'l', 'l', 'o'}
+ d := NewStreamingDeserializer(bytes.NewReader(data))
+ val, err := d.readString()
+ assert.Nil(t, err)
+ assert.Equal(t, "hello", val)
+ })
+
+ t.Run("readString empty", func(t *testing.T) {
+ data := []byte{0x00, 0x00, 0x00, 0x00}
+ d := NewStreamingDeserializer(bytes.NewReader(data))
+ val, err := d.readString()
+ assert.Nil(t, err)
+ assert.Equal(t, "", val)
+ })
+
+ t.Run("error on incomplete data", func(t *testing.T) {
+ data := []byte{0x00, 0x00} // incomplete int32
+ d := NewStreamingDeserializer(bytes.NewReader(data))
+ _, err := d.readInt32()
+ assert.ErrorIs(t, err, io.ErrUnexpectedEOF)
+ })
+
+ t.Run("sticky error", func(t *testing.T) {
+ data := []byte{0x00} // too short
+ d := NewStreamingDeserializer(bytes.NewReader(data))
+
+ _, err1 := d.readInt32()
+ assert.Error(t, err1)
+
+ // Subsequent reads should also fail
+ _, err2 := d.readInt32()
+ assert.Error(t, err2)
+ })
+}
+
+func TestStreamingChannelDelivery(t *testing.T) {
+ t.Run("results arrive incrementally via channel", func(t *testing.T) {
+ rs := newChannelResultSet()
+
+ // Simulate streaming - send results with delays
+ go func() {
+ for i := 0; i < 5; i++ {
+ time.Sleep(10 * time.Millisecond)
+ rs.Channel() <- &Result{i}
+ }
+ rs.Close()
+ }()
+
+ var times []time.Duration
+ start := time.Now()
+
+ for {
+ _, ok, _ := rs.One()
+ if !ok {
+ break
+ }
+ times = append(times, time.Since(start))
+ }
+
+ assert.Equal(t, 5, len(times))
+
+ // Results should arrive ~10ms apart, not all at once
+ for i := 1; i < len(times); i++ {
+ gap := times[i] - times[i-1]
+ assert.GreaterOrEqual(t, gap, 5*time.Millisecond,
+ "Results %d and %d arrived too close together: %v", i-1, i, gap)
+ }
+ })
+}
+
+// TestStreamingBlocksOnPartialData verifies that the deserializer correctly blocks
+// when it receives partial data, waiting for the rest of the object to arrive.
+// This simulates the real-world scenario where Go's HTTP client receives chunks
+// that don't align with server-sent GraphBinary object boundaries.
+func TestStreamingBlocksOnPartialData(t *testing.T) {
+ t.Run("blocks until complete int32 is available", func(t *testing.T) {
+ // Split a 4-byte int32 across two chunks
+ chunk1 := []byte{0x00, 0x00} // First 2 bytes
+ chunk2 := []byte{0x00, 0x2A} // Last 2 bytes (total = 42)
+
+ reader := newSlowReader([][]byte{chunk1, chunk2}, 20*time.Millisecond)
+ d := NewStreamingDeserializer(reader)
+
+ start := time.Now()
+ val, err := d.readInt32()
+ elapsed := time.Since(start)
+
+ assert.Nil(t, err)
+ assert.Equal(t, int32(42), val)
+ // Should have blocked waiting for second chunk
+ assert.GreaterOrEqual(t, elapsed, 15*time.Millisecond,
+ "Should have blocked waiting for remaining bytes")
+ })
+
+ t.Run("blocks until complete string is available", func(t *testing.T) {
+ // String "hello" split across chunks:
+ // Chunk 1: length (4 bytes) + partial content
+ // Chunk 2: remaining content
+ chunk1 := []byte{0x00, 0x00, 0x00, 0x05, 'h', 'e'} // length=5, "he"
+ chunk2 := []byte{'l', 'l', 'o'} // "llo"
+
+ reader := newSlowReader([][]byte{chunk1, chunk2}, 20*time.Millisecond)
+ d := NewStreamingDeserializer(reader)
+
+ start := time.Now()
+ val, err := d.readString()
+ elapsed := time.Since(start)
+
+ assert.Nil(t, err)
+ assert.Equal(t, "hello", val)
+ assert.GreaterOrEqual(t, elapsed, 15*time.Millisecond,
+ "Should have blocked waiting for remaining string bytes")
+ })
+}
+
+// TestStreamingMultipleObjects verifies that multiple GraphBinary objects
+// can be read from a stream, with each object returned as soon as it's complete.
+func TestStreamingMultipleObjects(t *testing.T) {
+ t.Run("reads multiple objects as they arrive", func(t *testing.T) {
+ // Build a stream with 3 fully-qualified int32 values
+ // Each int32: type(1) + flag(1) + value(4) = 6 bytes
+ obj1 := []byte{0x01, 0x00, 0x00, 0x00, 0x00, 0x01} // int32 = 1
+ obj2 := []byte{0x01, 0x00, 0x00, 0x00, 0x00, 0x02} // int32 = 2
+ obj3 := []byte{0x01, 0x00, 0x00, 0x00, 0x00, 0x03} // int32 = 3
+
+ // Deliver each object as a separate chunk with delay
+ reader := newSlowReader([][]byte{obj1, obj2, obj3}, 15*time.Millisecond)
+ d := NewStreamingDeserializer(reader)
+
+ var results []int32
+ var times []time.Duration
+ start := time.Now()
+
+ for i := 0; i < 3; i++ {
+ val, err := d.ReadFullyQualified()
+ assert.Nil(t, err)
+ results = append(results, val.(int32))
+ times = append(times, time.Since(start))
+ }
+
+ assert.Equal(t, []int32{1, 2, 3}, results)
+
+ // Objects should arrive with delays between them
+ for i := 1; i < len(times); i++ {
+ gap := times[i] - times[i-1]
+ assert.GreaterOrEqual(t, gap, 10*time.Millisecond,
+ "Object %d should have arrived after a delay", i)
+ }
+ })
+
+ t.Run("handles object split across chunk boundary", func(t *testing.T) {
+ // First chunk: complete object + partial second object
+ // Second chunk: rest of second object + complete third object
+ chunk1 := []byte{
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, // int32 = 1 (complete)
+ 0x01, 0x00, 0x00, // partial int32 (type + flag + 2 bytes of value)
+ }
+ chunk2 := []byte{
+ 0x00, 0x00, 0x02, // rest of int32 = 2
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x03, // int32 = 3 (complete)
+ }
+
+ reader := newSlowReader([][]byte{chunk1, chunk2}, 20*time.Millisecond)
+ d := NewStreamingDeserializer(reader)
+
+ // First object should return immediately
+ val1, err := d.ReadFullyQualified()
+ assert.Nil(t, err)
+ assert.Equal(t, int32(1), val1)
+
+ // Second object should block waiting for chunk2
+ start := time.Now()
+ val2, err := d.ReadFullyQualified()
+ elapsed := time.Since(start)
+ assert.Nil(t, err)
+ assert.Equal(t, int32(2), val2)
+ assert.GreaterOrEqual(t, elapsed, 15*time.Millisecond,
+ "Should have blocked waiting for rest of object")
+
+ // Third object should return immediately (already in buffer)
+ val3, err := d.ReadFullyQualified()
+ assert.Nil(t, err)
+ assert.Equal(t, int32(3), val3)
+ })
+}
+
+// TestStreamingWithEndOfStreamMarker verifies that the deserializer correctly
+// handles the EndOfStream marker and subsequent status reading.
+func TestStreamingWithEndOfStreamMarker(t *testing.T) {
+ t.Run("reads EndOfStream marker and status", func(t *testing.T) {
+ // Build a complete response:
+ // - Header (2 bytes): version + flags
+ // - One int32 result
+ // - EndOfStream marker
+ // - Status: code(4) + message(nullable) + exception(nullable)
+ data := []byte{
+ 0x81, 0x00, // Header: version byte + no bulking
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x2A, // int32 = 42
+ 0xfd, 0x00, 0x00, // Marker type + flag + value=0 (EndOfStream)
+ 0x00, 0x00, 0x00, 0xC8, // Status code = 200
+ 0x01, // Message is null
+ 0x01, // Exception is null
+ }
+
+ d := NewStreamingDeserializer(bytes.NewReader(data))
+
+ // Read header
+ err := d.ReadHeader()
+ assert.Nil(t, err)
+
+ // Read the result
+ val, err := d.ReadFullyQualified()
+ assert.Nil(t, err)
+ assert.Equal(t, int32(42), val)
+
+ // Read EndOfStream marker
+ marker, err := d.ReadFullyQualified()
+ assert.Nil(t, err)
+ assert.Equal(t, EndOfStream(), marker)
+
+ // Read status
+ code, msg, exc, err := d.ReadStatus()
+ assert.Nil(t, err)
+ assert.Equal(t, uint32(200), code)
+ assert.Equal(t, "", msg)
+ assert.Equal(t, "", exc)
+ })
+
+ t.Run("reads status with message", func(t *testing.T) {
+ // Status with a message
+ data := []byte{
+ 0xfd, 0x00, 0x00, // EndOfStream marker
+ 0x00, 0x00, 0x01, 0x90, // Status code = 400
+ 0x00, // Message is not null
+ 0x00, 0x00, 0x00, 0x05, 'e', 'r', 'r', 'o', 'r', // Message = "error"
+ 0x01, // Exception is null
+ }
+
+ d := NewStreamingDeserializer(bytes.NewReader(data))
+
+ marker, err := d.ReadFullyQualified()
+ assert.Nil(t, err)
+ assert.Equal(t, EndOfStream(), marker)
+
+ code, msg, exc, err := d.ReadStatus()
+ assert.Nil(t, err)
+ assert.Equal(t, uint32(400), code)
+ assert.Equal(t, "error", msg)
+ assert.Equal(t, "", exc)
+ })
+}
+
+// TestStreamingComplexTypes verifies streaming deserialization of complex types
+// like vertices, edges, and paths.
+func TestStreamingComplexTypes(t *testing.T) {
+ t.Run("reads vertex from stream", func(t *testing.T) {
+ // Vertex: id(int32) + labels(list of string) + properties(nullable)
+ data := []byte{
+ 0x11, 0x00, // Vertex type + flag
+ // ID: int32 = 1
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x01,
+ // Labels: list with one string "person"
+ 0x00, 0x00, 0x00, 0x01, // list length = 1
+ 0x03, 0x00, // string type + flag
+ 0x00, 0x00, 0x00, 0x06, 'p', 'e', 'r', 's', 'o', 'n',
+ // Properties: null
+ 0xfe, 0x01,
+ }
+
+ d := NewStreamingDeserializer(bytes.NewReader(data))
+ val, err := d.ReadFullyQualified()
+ assert.Nil(t, err)
+
+ v, ok := val.(*Vertex)
+ assert.True(t, ok)
+ assert.Equal(t, int32(1), v.Id)
+ assert.Equal(t, "person", v.Label)
+ })
+
+ t.Run("reads list of integers from chunked stream", func(t *testing.T) {
+ // List type split across chunks
+ chunk1 := []byte{
+ 0x09, 0x00, // List type + flag
+ 0x00, 0x00, 0x00, 0x03, // length = 3
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x0A, // int32 = 10
+ }
+ chunk2 := []byte{
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x14, // int32 = 20
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x1E, // int32 = 30
+ }
+
+ reader := newSlowReader([][]byte{chunk1, chunk2}, 15*time.Millisecond)
+ d := NewStreamingDeserializer(reader)
+
+ start := time.Now()
+ val, err := d.ReadFullyQualified()
+ elapsed := time.Since(start)
+
+ assert.Nil(t, err)
+ list, ok := val.([]interface{})
+ assert.True(t, ok)
+ assert.Equal(t, 3, len(list))
+ assert.Equal(t, int32(10), list[0])
+ assert.Equal(t, int32(20), list[1])
+ assert.Equal(t, int32(30), list[2])
+
+ // Should have blocked for second chunk
+ assert.GreaterOrEqual(t, elapsed, 10*time.Millisecond)
+ })
+}
diff --git a/gremlin-go/driver/translator.go b/gremlin-go/driver/translator.go
deleted file mode 100644
index 10629a51c3..0000000000
--- a/gremlin-go/driver/translator.go
+++ /dev/null
@@ -1,365 +0,0 @@
-/*
-Licensed to the Apache Software Foundation (ASF) under one
-or more contributor license agreements. See the NOTICE file
-distributed with this work for additional information
-regarding copyright ownership. The ASF licenses this file
-to you under the Apache License, Version 2.0 (the
-"License"); you may not use this file except in compliance
-with the License. You may obtain a copy of the License at
-
-http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing,
-software distributed under the License is distributed on an
-"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-KIND, either express or implied. See the License for the
-specific language governing permissions and limitations
-under the License.
-*/
-
-package gremlingo
-
-import (
- "fmt"
- "reflect"
- "strings"
- "time"
-)
-
-type Translator interface {
- Translate(bytecode *Bytecode) (string, error)
-}
-
-type translator struct {
- source string
-}
-
-func NewTranslator(source string) Translator {
- return &translator{source}
-}
-
-func (t *translator) Translate(bytecode *Bytecode) (string, error) {
- return t.translate(bytecode, true)
-}
-
-func (t *translator) translate(bytecode *Bytecode, initial bool) (string, error) {
- translated := ""
-
- if initial {
- translated += t.source
- appendDot(&translated)
- }
-
- for _, instruction := range bytecode.sourceInstructions {
- if len(instruction.arguments) == 0 {
- continue
- }
-
- appendDot(&translated)
-
- translatedInstruction, err := t.translateInstruction(instruction)
- if err != nil {
- return "", err
- }
- translated += translatedInstruction
- }
-
- appendDot(&translated)
-
- for _, instruction := range bytecode.stepInstructions {
-
- appendDot(&translated)
-
- translatedInstruction, err := t.translateInstruction(instruction)
- if err != nil {
- return "", err
- }
- translated += translatedInstruction
- }
-
- return translated, nil
-}
-
-func (t *translator) translateInstruction(instruction instruction) (string, error) {
- instructionString := fmt.Sprintf("%v(", instruction.operator)
-
- for index, arg := range instruction.arguments {
- if index > 0 {
- instructionString += ","
- }
-
- if instruction.operator == "with" {
- switch v := arg.(type) {
- case int32, string:
- translatedArg, err := t.translateWithOptions(v)
- if err != nil {
- return "", err
- }
- instructionString += translatedArg
- continue
- }
- }
-
- argString, err := t.toString(arg)
- if err != nil {
- return "", err
- }
- instructionString += argString
-
- }
-
- instructionString += ")"
-
- return instructionString, nil
-}
-
-func (t *translator) translateWithOptions(arg interface{}) (string, error) {
- if groovyWithOptions[arg] != "" {
- return groovyWithOptions[arg], nil
- }
-
- return t.toString(arg)
-}
-
-func (t *translator) translateMap(arg interface{}, isClassParamsMap bool) (string, error) {
- instructionString := ""
- iter := reflect.ValueOf(arg).MapRange()
-
- index := 0
- for iter.Next() {
- if index == 0 && !isClassParamsMap {
- instructionString += "{"
- }
-
- k := iter.Key().Interface()
- kString, err := t.toString(k)
- if err != nil {
- return "", err
- }
- if isClassParamsMap {
- kString = strings.Replace(kString, "'", "", -1)
- }
- v := iter.Value().Interface()
- vString, err := t.toString(v)
- if err != nil {
- return "", err
- }
- instructionString += fmt.Sprintf("%v:%v", kString, vString)
-
- if index < reflect.ValueOf(arg).Len()-1 {
- instructionString += ","
- }
-
- if index == reflect.ValueOf(arg).Len()-1 && !isClassParamsMap {
- instructionString += "}"
- }
- index++
-
- }
-
- return instructionString, nil
-}
-
-func (t *translator) translateSlice(arg interface{}) (string, error) {
- instructionString := "["
- oldSlice := reflect.ValueOf(arg)
- if oldSlice.Len() == 0 {
- return "", nil
- }
-
- for i := 0; i < oldSlice.Len(); i++ {
- if i > 0 {
- instructionString += ","
- }
- instructionStepString, err := t.toString(oldSlice.Index(i).Interface())
- if err != nil {
- return "", err
- }
- instructionString += fmt.Sprintf("%v", instructionStepString)
- }
- instructionString += "]"
-
- return instructionString, nil
-}
-
-func (t *translator) translateTextPredicate(v *textP) (string, error) {
- if v.operator == "" || len(v.values) == 0 {
- return "", nil
- }
-
- instructionString := ""
- instructionString += v.operator
- instructionString += "("
- if len(v.values) == 1 {
- argString, err := t.toString(v.values[0])
- if err != nil {
- return "", err
- }
- instructionString += argString
- } else if len(v.values) > 1 {
- for index, arg := range v.values {
- argString, err := t.toString(arg)
- if err != nil {
- return "", err
- }
-
- instructionString += argString
- if index < len(v.values)-1 && argString != "" {
- instructionString += ","
- }
- }
- }
-
- instructionString += ")"
-
- return instructionString, nil
-}
-
-func (t *translator) translatePredicate(v *p) (string, error) {
-
- if v.operator == "" || len(v.values) == 0 {
- return "", nil
- }
-
- instructionString := ""
- instructionString += v.operator
- instructionString += "("
- if len(v.values) == 1 {
- argString, err := t.toString(v.values[0])
- if err != nil {
- return "", err
- }
- instructionString += argString
- } else if len(v.values) > 1 {
- if v.operator != "between" && v.operator != "inside" {
- instructionString += "["
- }
- for index, arg := range v.values {
- argString, err := t.toString(arg)
- if err != nil {
- return "", err
- }
-
- instructionString += argString
- if index < len(v.values)-1 && argString != "" {
- instructionString += ","
- }
- }
- if v.operator != "between" && v.operator != "inside" {
- instructionString += "]"
- }
- }
-
- instructionString += ")"
-
- return instructionString, nil
-}
-
-func (t *translator) translateTraversalStrategy(v *traversalStrategy) (string, error) {
-
- instructionString := ""
- className := getGroovyClassName(v.name)
- instructionString += fmt.Sprintf("new %v(", className)
- instructionConfigurationString, err := t.translateMap(interface{}(v.configuration), true)
- if err != nil {
- return "", err
- }
- instructionString += instructionConfigurationString
- instructionString += ")"
-
- return instructionString, nil
-
-}
-
-func (t *translator) toString(arg interface{}) (string, error) {
- if arg == nil {
- return "null", nil
- }
- reflectedArg := reflect.TypeOf(arg)
- switch reflectedArg.Kind() {
-
- case reflect.Map:
- return t.translateMap(arg, false)
- case reflect.Slice:
- return t.translateSlice(arg)
- default:
- switch v := arg.(type) {
- case withOptions:
- {
- return groovyWithOptions[v], nil
- }
- case AnonymousTraversal:
- case *AnonymousTraversal:
- return t.toString(arg)
- case *Binding:
- return v.String(), nil
- case GraphTraversal:
- case *GraphTraversal:
- return t.translate(v.Bytecode, false)
- case traversalStrategy:
- case *traversalStrategy:
- return t.translateTraversalStrategy(v)
- case *Bytecode:
- return t.translate(v, false)
- case textP:
- case *textP:
- return t.translateTextPredicate(v)
- case p:
- case *p:
- return t.translatePredicate(v)
- default:
- {
- switch v := arg.(type) {
- case time.Time:
- {
- return timeToGroovyTime(v), nil
- }
- case string:
- {
- return fmt.Sprintf("'%v'", v), nil
- }
- default:
- {
- return fmt.Sprintf("%v", v), nil
- }
- }
- }
- }
- }
-
- return "", nil
-
-}
-
-func appendDot(s *string) {
- if len(*s) == 0 {
- return
- }
-
- if (*s)[len(*s)-1] != '.' {
- *s += "."
- }
-}
-
-func getGroovyClassName(classPath string) string {
- classPathArray := strings.Split(classPath, ".")
- return classPathArray[len(classPathArray)-1]
-}
-
-func timeToGroovyTime(t time.Time) string {
- return fmt.Sprintf("new Date(%v,%v,%v,%v,%v,%v)", t.Year()-1900, int(t.Month()), t.Day(), t.Hour(), t.Minute(), t.Second())
-
-}
-
-var groovyWithOptions map[any]string = map[any]string{
- WithOptions.Tokens: "WithOptions.tokens",
- WithOptions.None: "WithOptions.none",
- WithOptions.Ids: "WithOptions.ids",
- WithOptions.Labels: "WithOptions.labels",
- WithOptions.Keys: "WithOptions.keys",
- WithOptions.Values: "WithOptions.values",
- WithOptions.All: "WithOptions.all",
- WithOptions.Indexer: "WithOptions.indexer",
- WithOptions.List: "WithOptions.list",
- WithOptions.Map: "WithOptions.map",
-}
diff --git a/gremlin-go/driver/traversal.go b/gremlin-go/driver/traversal.go
index d23a8b18c2..40e8d79785 100644
--- a/gremlin-go/driver/traversal.go
+++ b/gremlin-go/driver/traversal.go
@@ -32,10 +32,10 @@ type Traverser struct {
// Traversal is the primary way in which graphs are processed.
type Traversal struct {
- graph *Graph
- Bytecode *Bytecode
- remote *DriverRemoteConnection
- results ResultSet
+ graph *Graph
+ GremlinLang *GremlinLang
+ remote *DriverRemoteConnection
+ results ResultSet
}
// ToList returns the result in a list.
@@ -44,7 +44,7 @@ func (t *Traversal) ToList() ([]*Result, error) {
return nil, newError(err0901ToListAnonTraversalError)
}
- results, err := t.remote.submitBytecode(t.Bytecode)
+ results, err := t.remote.submitGremlinLang(t.GremlinLang)
if err != nil {
return nil, err
}
@@ -77,12 +77,9 @@ func (t *Traversal) Iterate() <-chan error {
return
}
- if err := t.Bytecode.AddStep("discard"); err != nil {
- r <- err
- return
- }
+ t.GremlinLang.AddStep("discard")
- res, err := t.remote.submitBytecode(t.Bytecode)
+ res, err := t.remote.submitGremlinLang(t.GremlinLang)
if err != nil {
r <- err
return
@@ -125,7 +122,7 @@ func (t *Traversal) Next() (*Result, error) {
// GetResultSet submits the traversal and returns the ResultSet.
func (t *Traversal) GetResultSet() (ResultSet, error) {
if t.results == nil {
- results, err := t.remote.submitBytecode(t.Bytecode)
+ results, err := t.remote.submitGremlinLang(t.GremlinLang)
if err != nil {
return nil, err
}
@@ -161,33 +158,33 @@ var Cardinality = cardinalities{
}
type cv struct {
- Bytecode *Bytecode
+ GremlinLang *GremlinLang
}
type CardValue interface {
- Single(val interface{}) Bytecode
- Set(val interface{}) Bytecode
- List(val interface{}) Bytecode
+ Single(val interface{}) GremlinLang
+ Set(val interface{}) GremlinLang
+ List(val interface{}) GremlinLang
}
var CardinalityValue CardValue = &cv{}
-func (*cv) Single(val interface{}) Bytecode {
- bc := Bytecode{}
- bc.AddSource("CardinalityValueTraversal", Cardinality.Single, val)
- return bc
+func (*cv) Single(val interface{}) GremlinLang {
+ gl := GremlinLang{}
+ gl.AddSource("CardinalityValueTraversal", Cardinality.Single, val)
+ return gl
}
-func (*cv) Set(val interface{}) Bytecode {
- bc := Bytecode{}
- bc.AddSource("CardinalityValueTraversal", Cardinality.Set, val)
- return bc
+func (*cv) Set(val interface{}) GremlinLang {
+ gl := GremlinLang{}
+ gl.AddSource("CardinalityValueTraversal", Cardinality.Set, val)
+ return gl
}
-func (*cv) List(val interface{}) Bytecode {
- bc := Bytecode{}
- bc.AddSource("CardinalityValueTraversal", Cardinality.List, val)
- return bc
+func (*cv) List(val interface{}) GremlinLang {
+ gl := GremlinLang{}
+ gl.AddSource("CardinalityValueTraversal", Cardinality.List, val)
+ return gl
}
type column string
@@ -517,7 +514,7 @@ func newP(operator string, args ...interface{}) Predicate {
return &p{operator: operator, values: values}
}
-func newPWithP(operator string, pp p, args ...interface{}) Predicate {
+func newPWithP(operator string, pp *p, args ...interface{}) Predicate {
values := make([]interface{}, 1)
values[0] = pp
values = append(values, args...)
@@ -605,12 +602,12 @@ func (*p) Without(args ...interface{}) Predicate {
// And Predicate returns a Predicate composed of two predicates (logical AND of them).
func (pp *p) And(args ...interface{}) Predicate {
- return newPWithP("and", *pp, args...)
+ return newPWithP("and", pp, args...)
}
// Or Predicate returns a Predicate composed of two predicates (logical OR of them).
func (pp *p) Or(args ...interface{}) Predicate {
- return newPWithP("or", *pp, args...)
+ return newPWithP("or", pp, args...)
}
type TextPredicate interface {
@@ -749,32 +746,6 @@ var IO = ioconfig{
Registry: "~tinkerpop.ioconfig.registry",
}
-// 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 {
@@ -782,6 +753,18 @@ type BigDecimal struct {
UnscaledValue *big.Int
}
+func (bd *BigDecimal) Value() *big.Float {
+ // Create the divisor: 10^scale
+ divisor := new(big.Int).Exp(big.NewInt(10), big.NewInt(int64(bd.Scale)), nil)
+
+ // Convert unscaled value and divisor to big.Float
+ unscaled := new(big.Float).SetInt(bd.UnscaledValue)
+ div := new(big.Float).SetInt(divisor)
+
+ // Divide: unscaledValue / 10^scale
+ return new(big.Float).Quo(unscaled, div)
+}
+
// ParseBigDecimal creates a BigDecimal from a string value.
func ParseBigDecimal(strValue string) *BigDecimal {
val := &BigDecimal{}
diff --git a/gremlin-go/driver/traversal_test.go b/gremlin-go/driver/traversal_test.go
index c8d130d7b7..0efded3ac9 100644
--- a/gremlin-go/driver/traversal_test.go
+++ b/gremlin-go/driver/traversal_test.go
@@ -31,363 +31,345 @@ import (
func TestTraversal(t *testing.T) {
t.Run("Test clone traversal", func(t *testing.T) {
- g := cloneGraphTraversalSource(&Graph{}, NewBytecode(nil), nil)
+ g := cloneGraphTraversalSource(&Graph{}, NewGremlinLang(nil), nil)
original := g.V().Out("created")
clone := original.Clone().Out("knows")
cloneClone := clone.Clone().Out("created")
- assert.Equal(t, 2, len(original.Bytecode.stepInstructions))
- assert.Equal(t, 3, len(clone.Bytecode.stepInstructions))
- assert.Equal(t, 4, len(cloneClone.Bytecode.stepInstructions))
+ assert.Equal(t, "g.V().out(\"created\")", original.GremlinLang.GetGremlin())
+ assert.Equal(t, "g.V().out(\"created\").out(\"knows\")", clone.GremlinLang.GetGremlin())
+ assert.Equal(t, "g.V().out(\"created\").out(\"knows\").out(\"created\")", cloneClone.GremlinLang.GetGremlin())
original.Has("person", "name", "marko")
clone.V().Out()
- assert.Equal(t, 3, len(original.Bytecode.stepInstructions))
- assert.Equal(t, 5, len(clone.Bytecode.stepInstructions))
- assert.Equal(t, 4, len(cloneClone.Bytecode.stepInstructions))
+ assert.Equal(t, "g.V().out(\"created\").has(\"person\",\"name\",\"marko\")", original.GremlinLang.GetGremlin())
+ assert.Equal(t, "g.V().out(\"created\").out(\"knows\").V().out()", clone.GremlinLang.GetGremlin())
+ assert.Equal(t, "g.V().out(\"created\").out(\"knows\").out(\"created\")", cloneClone.GremlinLang.GetGremlin())
})
t.Run("Test Iterate with empty removeConnection", func(t *testing.T) {
- g := NewGraphTraversalSource(&Graph{}, nil, NewBytecode(nil))
+ g := NewGraphTraversalSource(&Graph{}, nil, NewGremlinLang(nil))
promise := g.V().Count().Iterate()
assert.NotNil(t, <-promise)
})
- t.Run("Test traversal with bindings", func(t *testing.T) {
- g := cloneGraphTraversalSource(&Graph{}, NewBytecode(nil), nil)
- bytecode := g.V((&Bindings{}).Of("a", []int32{1, 2, 3})).
- Out((&Bindings{}).Of("b", "created")).
- Where(T__.In((&Bindings{}).Of("c", "created"), (&Bindings{}).Of("d", "knows")).
- Count().Is((&Bindings{}).Of("e", P.Gt(2)))).Bytecode
- assert.Equal(t, 5, len(bytecode.bindings))
- assert.Equal(t, []int32{1, 2, 3}, bytecode.bindings["a"])
- assert.Equal(t, "created", bytecode.bindings["b"])
- assert.Equal(t, "created", bytecode.bindings["c"])
- assert.Equal(t, "knows", bytecode.bindings["d"])
- assert.Equal(t, P.Gt(2), bytecode.bindings["e"])
- assert.Equal(t, &Binding{
- Key: "b",
- Value: "created",
- }, bytecode.stepInstructions[1].arguments[0])
- assert.Equal(t, "binding[b=created]", bytecode.stepInstructions[1].arguments[0].(*Binding).String())
- })
-
- t.Run("Test Transaction commit", func(t *testing.T) {
- // Start a transaction traversal.
- remote := newConnection(t)
- g := Traversal_().With(remote)
- startCount := getCount(t, g)
- tx := g.Tx()
-
- // Except transaction to not be open until begin is called.
- assert.False(t, tx.IsOpen())
- gtx, _ := tx.Begin()
- assert.True(t, tx.IsOpen())
-
- addV(t, gtx, "lyndon")
- addV(t, gtx, "valentyn")
- assert.Equal(t, startCount, getCount(t, g))
- assert.Equal(t, startCount+2, getCount(t, gtx))
-
- // Commit the transaction, this should close it.
- // Our vertex count outside the transaction should be 2 + the start count.
- err := tx.Commit()
- assert.Nil(t, err)
-
- assert.False(t, tx.IsOpen())
- assert.Equal(t, startCount+2, getCount(t, g))
-
- dropGraphCheckCount(t, g)
- verifyGtxClosed(t, gtx)
- })
-
- t.Run("Test Transaction rollback", func(t *testing.T) {
- // Start a transaction traversal.
- remote := newConnection(t)
- g := Traversal_().With(remote)
- startCount := getCount(t, g)
- tx := g.Tx()
-
- // Except transaction to not be open until begin is called.
- assert.False(t, tx.IsOpen())
- gtx, _ := tx.Begin()
- assert.True(t, tx.IsOpen())
-
- addV(t, gtx, "lyndon")
- addV(t, gtx, "valentyn")
- assert.Equal(t, startCount, getCount(t, g))
- assert.Equal(t, startCount+2, getCount(t, gtx))
-
- // Rollback the transaction, this should close it.
- // Our vertex count outside the transaction should be the start count.
- err := tx.Rollback()
- assert.Nil(t, err)
-
- assert.False(t, tx.IsOpen())
- assert.Equal(t, startCount, getCount(t, g))
-
- dropGraphCheckCount(t, g)
- verifyGtxClosed(t, gtx)
- })
-
- t.Run("Test Transaction flows", func(t *testing.T) {
- // Start a transaction traversal.
- remote := newConnection(t)
- g := Traversal_().With(remote)
- tx := g.Tx()
- assert.False(t, tx.IsOpen())
-
- // Commit should return error when transaction not started
- err := tx.Commit()
- assert.NotNil(t, err)
-
- // Rollback should return error when transaction not started
- err = tx.Rollback()
- assert.NotNil(t, err)
-
- // Create transaction and verify it is open.
- gtx, err := tx.Begin()
- assert.Nil(t, err)
- assert.NotNil(t, gtx)
- assert.True(t, tx.IsOpen())
-
- // Can't open inner transaction.
- innerTx, err := gtx.Tx().Begin()
- assert.Nil(t, innerTx)
- assert.NotNil(t, err)
-
- // Commit this unused transaction and verify it is no longer open.
- err = tx.Commit()
- assert.Nil(t, err)
- assert.False(t, tx.IsOpen())
-
- // Create another transaction and verify it is open.
- gtx, err = tx.Begin()
- assert.Nil(t, err)
- assert.NotNil(t, gtx)
- assert.True(t, tx.IsOpen())
-
- // Rollback this unused transaction and verify it is no longer open.
- err = tx.Rollback()
- assert.Nil(t, err)
- assert.False(t, tx.IsOpen())
- })
-
- t.Run("Test multi commit Transaction", func(t *testing.T) {
- // Start a transaction traversal.
- remote := newConnection(t)
- g := Traversal_().With(remote)
- startCount := getCount(t, g)
-
- // Create two transactions.
- tx1 := g.Tx()
- tx2 := g.Tx()
-
- // Generate two GraphTraversalSource's for each transaction with begin.
- gtx1, _ := tx1.Begin()
- gtx2, _ := tx2.Begin()
- verifyTxState(t, true, tx1, tx2)
-
- // Add node to gtx1, which should be visible to gtx1, not gtx2.
- addNodeValidateTransactionState(t, g, gtx1, startCount, startCount, tx1, tx2)
-
- // Add node to gtx2, which should be visible to gtx2, not gtx1
- addNodeValidateTransactionState(t, g, gtx2, startCount, startCount, tx1, tx2)
-
- // Add node to gtx1, which should be visible to gtx1, not gtx2. Note previous node also added.
- addNodeValidateTransactionState(t, g, gtx1, startCount, startCount+1, tx1, tx2)
-
- tx1.Commit()
- verifyTxState(t, false, tx1)
- verifyTxState(t, true, tx2)
- assert.Equal(t, startCount+2, getCount(t, g))
-
- tx2.Commit()
- verifyTxState(t, false, tx1, tx2)
- assert.Equal(t, startCount+3, getCount(t, g))
- })
-
- t.Run("Test multi rollback Transaction", func(t *testing.T) {
- // Start a transaction traversal.
- remote := newConnection(t)
- g := Traversal_().With(remote)
- startCount := getCount(t, g)
-
- // Create two transactions.
- tx1 := g.Tx()
- tx2 := g.Tx()
-
- // Generate two GraphTraversalSource's for each transaction with begin.
- gtx1, _ := tx1.Begin()
- gtx2, _ := tx2.Begin()
- verifyTxState(t, true, tx1, tx2)
-
- // Add node to gtx1, which should be visible to gtx1, not gtx2.
- addNodeValidateTransactionState(t, g, gtx1, startCount, startCount, tx1, tx2)
-
- // Add node to gtx2, which should be visible to gtx2, not gtx1
- addNodeValidateTransactionState(t, g, gtx2, startCount, startCount, tx1, tx2)
-
- // Add node to gtx1, which should be visible to gtx1, not gtx2. Note previous node also added.
- addNodeValidateTransactionState(t, g, gtx1, startCount, startCount+1, tx1, tx2)
-
- tx1.Rollback()
- verifyTxState(t, false, tx1)
- verifyTxState(t, true, tx2)
- assert.Equal(t, startCount, getCount(t, g))
-
- tx2.Rollback()
- verifyTxState(t, false, tx1, tx2)
- assert.Equal(t, startCount, getCount(t, g))
- })
-
- t.Run("Test multi commit and rollback Transaction", func(t *testing.T) {
- // Start a transaction traversal.
- remote := newConnection(t)
- g := Traversal_().With(remote)
- startCount := getCount(t, g)
-
- // Create two transactions.
- tx1 := g.Tx()
- tx2 := g.Tx()
-
- // Generate two GraphTraversalSource's for each transaction with begin.
- gtx1, _ := tx1.Begin()
- gtx2, _ := tx2.Begin()
- verifyTxState(t, true, tx1, tx2)
-
- // Add node to gtx1, which should be visible to gtx1, not gtx2.
- addNodeValidateTransactionState(t, g, gtx1, startCount, startCount, tx1, tx2)
-
- // Add node to gtx2, which should be visible to gtx2, not gtx1
- addNodeValidateTransactionState(t, g, gtx2, startCount, startCount, tx1, tx2)
-
- // Add node to gtx1, which should be visible to gtx1, not gtx2. Note previous node also added.
- addNodeValidateTransactionState(t, g, gtx1, startCount, startCount+1, tx1, tx2)
-
- tx1.Commit()
- verifyTxState(t, false, tx1)
- verifyTxState(t, true, tx2)
- assert.Equal(t, startCount+2, getCount(t, g))
-
- tx2.Rollback()
- verifyTxState(t, false, tx1, tx2)
- assert.Equal(t, startCount+2, getCount(t, g))
- })
-
- t.Run("Test Transaction close", func(t *testing.T) {
- // Start a transaction traversal.
- remote := newConnection(t)
- g := Traversal_().With(remote)
- dropGraphCheckCount(t, g)
-
- // Create two transactions.
- tx1 := g.Tx()
- tx2 := g.Tx()
-
- // Generate two GraphTraversalSource's for each transaction with begin.
- gtx1, _ := tx1.Begin()
- gtx2, _ := tx2.Begin()
- verifyTxState(t, true, tx1, tx2)
-
- // Add stuff to both gtx.
- addNodeValidateTransactionState(t, g, gtx1, 0, 0, tx1, tx2)
- addNodeValidateTransactionState(t, g, gtx2, 0, 0, tx1, tx2)
- addNodeValidateTransactionState(t, g, gtx2, 0, 1, tx1, tx2)
- addNodeValidateTransactionState(t, g, gtx2, 0, 2, tx1, tx2)
-
- // someone gets lazy and doesn't commit/rollback and just calls close() - the graph
- // will decide how to treat the transaction, but for neo4j/gremlin server in this
- // test configuration it should rollback
- tx1.Close()
- tx2.Close()
-
- verifyGtxClosed(t, gtx1)
- verifyGtxClosed(t, gtx2)
-
- remote = newConnection(t)
- g = Traversal_().With(remote)
- assert.Equal(t, int32(0), getCount(t, g))
- })
-
- t.Run("Test Transaction close tx from parent", func(t *testing.T) {
- // Start a transaction traversal.
- remote := newConnection(t)
- g := Traversal_().With(remote)
- dropGraphCheckCount(t, g)
-
- // Create two transactions.
- tx1 := g.Tx()
- tx2 := g.Tx()
-
- // Generate two GraphTraversalSource's for each transaction with begin.
- gtx1, _ := tx1.Begin()
- gtx2, _ := tx2.Begin()
- verifyTxState(t, true, tx1, tx2)
-
- // Add stuff to both gtx.
- addNodeValidateTransactionState(t, g, gtx1, 0, 0, tx1, tx2)
- addNodeValidateTransactionState(t, g, gtx2, 0, 0, tx1, tx2)
- addNodeValidateTransactionState(t, g, gtx2, 0, 1, tx1, tx2)
- addNodeValidateTransactionState(t, g, gtx2, 0, 2, tx1, tx2)
-
- // someone gets lazy and doesn't commit/rollback and just calls Close() but on the parent
- // DriverRemoteConnection for all the session that were created via Tx() - the graph
- // will decide how to treat the transaction, but for neo4j/gremlin server in this
- // test configuration it should rollback.
- remote.Close()
-
- assert.False(t, tx1.IsOpen())
- assert.False(t, tx2.IsOpen())
- verifyGtxClosed(t, gtx1)
- verifyGtxClosed(t, gtx2)
-
- remote = newConnection(t)
- g = Traversal_().With(remote)
- assert.Equal(t, int32(0), getCount(t, g))
- })
-
- t.Run("Test commit if no transaction started", func(t *testing.T) {
- // Start a traversal.
- g := newWithOptionsConnection(t)
-
- // Create transactions
- tx := g.Tx()
... 680 lines suppressed ...