harrisonhjones commented on a change in pull request #1569:
URL: https://github.com/apache/tinkerpop/pull/1569#discussion_r813058702
##########
File path: gremlin-go/README.md
##########
@@ -91,65 +121,190 @@ Note: The exact import name as well as the module prefix
for `NewDriverRemoteCon
data-flow language that enables users to succinctly express complex traversals
on (or queries of) their application's
property graph.
-Gremlin-Go implements Gremlin within the Go language and can be used on any Go
runtime greater than v1.17. Go's syntax
-has the same constructs as Java including "dot notation" for function chaining
(a.b.c), round bracket function arguments
-(a(b,c)), and support for global namespaces (a(b()) vs a(__.b())). One
important distinction with Go and Java is that
-the functions are capitalized, as is required to export functions is Go. As
such, anyone familiar with Gremlin-Java
-will immediately be able to work with Gremlin-Go.
+Gremlin-Go implements Gremlin within the Go language and can be used on any Go
runtime greater than v1.17. One important distinction with Go and Java is that
+the functions are capitalized, as is required to export functions is Go.
Review comment:
nitpick: it looks like the existing readme's text was wrapped at ~120
characters. Might want to do that here as well?
##########
File path: gremlin-go/driver/client.go
##########
@@ -41,7 +41,7 @@ type Client struct {
}
// NewClient creates a Client and configures it with the given parameters.
Review comment:
Probably also want to call out that it also creates the connection (or
attempts to) and will re-turn a non-nil error if creating this connection fails.
##########
File path: gremlin-go/README.md
##########
@@ -91,65 +121,190 @@ Note: The exact import name as well as the module prefix
for `NewDriverRemoteCon
data-flow language that enables users to succinctly express complex traversals
on (or queries of) their application's
property graph.
-Gremlin-Go implements Gremlin within the Go language and can be used on any Go
runtime greater than v1.17. Go's syntax
-has the same constructs as Java including "dot notation" for function chaining
(a.b.c), round bracket function arguments
-(a(b,c)), and support for global namespaces (a(b()) vs a(__.b())). One
important distinction with Go and Java is that
-the functions are capitalized, as is required to export functions is Go. As
such, anyone familiar with Gremlin-Java
-will immediately be able to work with Gremlin-Go.
+Gremlin-Go implements Gremlin within the Go language and can be used on any Go
runtime greater than v1.17. One important distinction with Go and Java is that
+the functions are capitalized, as is required to export functions is Go.
Gremlin-Go is designed to connect to a "server" that is hosting a
TinkerPop-enabled graph system. That "server"
could be [Gremlin Server][gs] or a [remote Gremlin provider][rgp] that exposes
protocols by which Gremlin-Go
can connect.
A typical connection to a server running on "localhost" that supports the
Gremlin Server protocol using websockets
looks like this:
-<!--
-TODO: Add Go code example of connection to server.
--->
+```go
+package main
+
+import (
+ "fmt"
+ "github.com/lyndonb-bq/tinkerpop/gremlin-go/driver"
+)
+func main() {
+ // Creating the connection to the server with default settings.
+ driverRemoteConnection, err :=
gremlingo.NewDriverRemoteConnection("localhost", 8182)
+ // Handle error
+ if err != nil {
+ fmt.Println(err)
+ return
+ }
+ // Create an anonymous traversal source with remote
+ g := gremlingo.Traversal_().WithRemote(driverRemoteConnection)
+}
+```
+We can also customize the remote connection settings. (See code documentation
for additional parameters and their usage).
+```go
+package main
+
+import (
+ "fmt"
+ "github.com/lyndonb-bq/tinkerpop/gremlin-go/driver"
+)
+
+func main() {
+ // Creating the connection to the server, changing the log verbosity to
Debug.
+ driverRemoteConnection, err :=
gremlingo.NewDriverRemoteConnection("localhost", 8182, func(settings
*gremlingo.DriverRemoteConnectionSettings) {
+ settings.LogVerbosity = gremlingo.Debug
+ })
+ // Handle error
+ if err != nil {
+ fmt.Println(err)
+ return
+ }
+ // Create an anonymous traversal source with remote
+ g := gremlingo.Traversal_().WithRemote(driverRemoteConnection)
+ // Use g with traversal as detailed in the next section.
+}
+```
Once "g" has been created using a connection, it is then possible to start
writing Gremlin traversals to query the
remote graph:
-<!--
-TODO: Add Go code example of a Gremlin traversal query.
--->
+```go
+package main
+
+import (
+ "fmt"
+ "github.com/lyndonb-bq/tinkerpop/gremlin-go/driver"
+)
-# The following material is currently Work-in-progress:
+func main() {
+ // Creating the connection to the server.
+ driverRemoteConnection, err :=
gremlingo.NewDriverRemoteConnection("localhost", 8182)
+ // Handle error
+ if err != nil {
+ fmt.Println(err)
+ return
+ }
+ // Create an anonymous traversal source with remote
+ g := gremlingo.Traversal_().WithRemote(driverRemoteConnection)
+
+ // Add a vertex with properties to the graph with the terminal step
Iterate()
+ _, promise, err := g.AddV("gremlin").Property("language",
"go").Iterate()
+ if err != nil {
+ fmt.Println(err)
+ return
+ }
+ // The returned promised is a go channel, to wait for all submitted
steps to finish execution.
+ <-promise
+
+ // Get the value of the property
+ result, err := g.V().HasLabel("gremlin").Values("language").ToList()
+ if err != nil {
+ fmt.Println(err)
+ return
+ }
+ // Print the result
+ for _, r := range result {
+ fmt.Println(r.GetString())
+ }
+}
+```
## Sample Traversals
<!--
TODO: Add Go specific changes to following paragraph:
-examples:
-"For the most part, these examples should generally translate to Go with
[little modification][differences]"
-"Given the strong correspondence between canonical Gremlin in Java and its
variants like Go, there is a limited amount
-of Go-specific documentation and examples."
-->
The Gremlin language allows users to write highly expressive graph traversals
and has a broad list of functions that
-cover a wide body of features. The [Reference Documentation][steps] describes
these functions and other aspects of the
+cover a wide body of features. Traversal in `Go` is very similar to other GLV,
with the exception that all step functions are capitalized.
+
+<!--
+The [Reference Documentation][steps] describes these functions and other
aspects of the
TinkerPop ecosystem including some specifics on [Gremlin in Go][docs] itself.
Most of the examples found in the
documentation use Groovy language syntax in the [Gremlin Console][console].
For the most part, these examples
should generally translate to Go with [little modification][differences].
Given the strong correspondence
between canonical Gremlin in Java and its variants like Go, there is a limited
amount of Go-specific
documentation and examples. This strong correspondence among variants ensures
that the general Gremlin reference
documentation is applicable to all variants and that users moving between
development languages can easily adopt the
Gremlin variant for that language.
-
-### Create Vertex
-<!--
-TODO: Add Go code to create a vertex.
-->
+### Create Vertex
+Adding a vertex with properties.
+```go
+_, promise, err :=g.AddV("gremlin").Property("language", "java").Iterate()
Review comment:
1. nitpick: missing space between `:=` and `g.AddV`
2. Should the language here still be `java`?
##########
File path: gremlin-go/README.md
##########
@@ -91,65 +121,190 @@ Note: The exact import name as well as the module prefix
for `NewDriverRemoteCon
data-flow language that enables users to succinctly express complex traversals
on (or queries of) their application's
property graph.
-Gremlin-Go implements Gremlin within the Go language and can be used on any Go
runtime greater than v1.17. Go's syntax
-has the same constructs as Java including "dot notation" for function chaining
(a.b.c), round bracket function arguments
-(a(b,c)), and support for global namespaces (a(b()) vs a(__.b())). One
important distinction with Go and Java is that
-the functions are capitalized, as is required to export functions is Go. As
such, anyone familiar with Gremlin-Java
-will immediately be able to work with Gremlin-Go.
+Gremlin-Go implements Gremlin within the Go language and can be used on any Go
runtime greater than v1.17. One important distinction with Go and Java is that
+the functions are capitalized, as is required to export functions is Go.
Gremlin-Go is designed to connect to a "server" that is hosting a
TinkerPop-enabled graph system. That "server"
could be [Gremlin Server][gs] or a [remote Gremlin provider][rgp] that exposes
protocols by which Gremlin-Go
can connect.
A typical connection to a server running on "localhost" that supports the
Gremlin Server protocol using websockets
looks like this:
-<!--
-TODO: Add Go code example of connection to server.
--->
+```go
+package main
+
+import (
+ "fmt"
+ "github.com/lyndonb-bq/tinkerpop/gremlin-go/driver"
+)
+func main() {
+ // Creating the connection to the server with default settings.
+ driverRemoteConnection, err :=
gremlingo.NewDriverRemoteConnection("localhost", 8182)
+ // Handle error
+ if err != nil {
+ fmt.Println(err)
+ return
+ }
+ // Create an anonymous traversal source with remote
+ g := gremlingo.Traversal_().WithRemote(driverRemoteConnection)
+}
+```
+We can also customize the remote connection settings. (See code documentation
for additional parameters and their usage).
+```go
+package main
+
+import (
+ "fmt"
+ "github.com/lyndonb-bq/tinkerpop/gremlin-go/driver"
+)
+
+func main() {
+ // Creating the connection to the server, changing the log verbosity to
Debug.
+ driverRemoteConnection, err :=
gremlingo.NewDriverRemoteConnection("localhost", 8182, func(settings
*gremlingo.DriverRemoteConnectionSettings) {
+ settings.LogVerbosity = gremlingo.Debug
+ })
+ // Handle error
+ if err != nil {
+ fmt.Println(err)
+ return
+ }
+ // Create an anonymous traversal source with remote
+ g := gremlingo.Traversal_().WithRemote(driverRemoteConnection)
+ // Use g with traversal as detailed in the next section.
+}
+```
Once "g" has been created using a connection, it is then possible to start
writing Gremlin traversals to query the
remote graph:
-<!--
-TODO: Add Go code example of a Gremlin traversal query.
--->
+```go
+package main
+
+import (
+ "fmt"
+ "github.com/lyndonb-bq/tinkerpop/gremlin-go/driver"
+)
-# The following material is currently Work-in-progress:
+func main() {
+ // Creating the connection to the server.
+ driverRemoteConnection, err :=
gremlingo.NewDriverRemoteConnection("localhost", 8182)
+ // Handle error
+ if err != nil {
+ fmt.Println(err)
+ return
+ }
+ // Create an anonymous traversal source with remote
+ g := gremlingo.Traversal_().WithRemote(driverRemoteConnection)
+
+ // Add a vertex with properties to the graph with the terminal step
Iterate()
+ _, promise, err := g.AddV("gremlin").Property("language",
"go").Iterate()
+ if err != nil {
+ fmt.Println(err)
+ return
+ }
+ // The returned promised is a go channel, to wait for all submitted
steps to finish execution.
+ <-promise
+
+ // Get the value of the property
+ result, err := g.V().HasLabel("gremlin").Values("language").ToList()
+ if err != nil {
+ fmt.Println(err)
+ return
+ }
+ // Print the result
+ for _, r := range result {
+ fmt.Println(r.GetString())
+ }
+}
+```
## Sample Traversals
<!--
TODO: Add Go specific changes to following paragraph:
-examples:
-"For the most part, these examples should generally translate to Go with
[little modification][differences]"
-"Given the strong correspondence between canonical Gremlin in Java and its
variants like Go, there is a limited amount
-of Go-specific documentation and examples."
-->
The Gremlin language allows users to write highly expressive graph traversals
and has a broad list of functions that
-cover a wide body of features. The [Reference Documentation][steps] describes
these functions and other aspects of the
+cover a wide body of features. Traversal in `Go` is very similar to other GLV,
with the exception that all step functions are capitalized.
+
+<!--
+The [Reference Documentation][steps] describes these functions and other
aspects of the
TinkerPop ecosystem including some specifics on [Gremlin in Go][docs] itself.
Most of the examples found in the
documentation use Groovy language syntax in the [Gremlin Console][console].
For the most part, these examples
should generally translate to Go with [little modification][differences].
Given the strong correspondence
between canonical Gremlin in Java and its variants like Go, there is a limited
amount of Go-specific
documentation and examples. This strong correspondence among variants ensures
that the general Gremlin reference
documentation is applicable to all variants and that users moving between
development languages can easily adopt the
Gremlin variant for that language.
-
-### Create Vertex
-<!--
-TODO: Add Go code to create a vertex.
-->
+### Create Vertex
+Adding a vertex with properties.
+```go
+_, promise, err :=g.AddV("gremlin").Property("language", "java").Iterate()
+// Handle error
+if err != nil {
+ fmt.Println(err)
+ return
Review comment:
nitpick: mixing tabs (Go) and spaces (not Go) here
##########
File path: gremlin-go/driver/client.go
##########
@@ -41,7 +41,7 @@ type Client struct {
}
// NewClient creates a Client and configures it with the given parameters.
-func NewClient(host string, port int, configurations ...func(settings
*ClientSettings)) *Client {
+func NewClient(host string, port int, configurations ...func(settings
*ClientSettings)) (*Client, error) {
Review comment:
This _looks_ like the functional options pattern but all the settings
are exported which isn't usually the case with that pattern. Do the settings
need to be exported?
##########
File path: gremlin-go/driver/connection.go
##########
@@ -19,50 +19,64 @@ under the License.
package gremlingo
+import (
+ "errors"
+)
+
+type connectionState int
+
+const (
+ initialized connectionState = iota + 1
+ established
+ closed
+ closedDueToError
+)
+
type connection struct {
- host string
- port int
- transporterType TransporterType
- logHandler *logHandler
- transporter transporter
- protocol protocol
- results map[string]ResultSet
+ logHandler *logHandler
+ protocol protocol
+ results map[string]ResultSet
+ state connectionState
}
-func (connection *connection) close() (err error) {
- if connection.transporter != nil {
- err = connection.transporter.Close()
- }
- return
+func (connection *connection) errorCallback() {
+ connection.logHandler.log(Error, errorCallback)
+ connection.state = closedDueToError
+ _ = connection.protocol.close()
}
-func (connection *connection) connect() error {
- if connection.transporter != nil {
- closeErr := connection.transporter.Close()
- connection.logHandler.logf(Warning, transportCloseFailed,
closeErr)
+func (connection *connection) close() error {
+ if connection.state != established {
+ return errors.New("cannot close connection that has already
been closed or has not been connected")
}
- connection.protocol = newGremlinServerWSProtocol(connection.logHandler)
- connection.transporter = getTransportLayer(connection.transporterType,
connection.host, connection.port)
- err := connection.transporter.Connect()
- if err != nil {
- return err
+ connection.logHandler.log(Info, closeConnection)
+ var err error
+ if connection.protocol != nil {
+ err = connection.protocol.close()
}
- connection.protocol.connectionMade(connection.transporter)
- return nil
+ connection.state = closed
+ return err
}
func (connection *connection) write(request *request) (ResultSet, error) {
- if connection.transporter == nil || connection.transporter.IsClosed() {
- err := connection.connect()
- if err != nil {
- return nil, err
- }
- }
- if connection.results == nil {
- connection.results = map[string]ResultSet{}
- }
+ connection.logHandler.log(Info, writeRequest)
Review comment:
thoughts on a helper `func (conn *connection) logf(...)` that simply
called `connection.logHandler.log`? Would make things a bit less verbose.
##########
File path: gremlin-go/driver/connection.go
##########
@@ -19,50 +19,64 @@ under the License.
package gremlingo
+import (
+ "errors"
+)
+
+type connectionState int
+
+const (
+ initialized connectionState = iota + 1
+ established
+ closed
+ closedDueToError
+)
+
type connection struct {
- host string
- port int
- transporterType TransporterType
- logHandler *logHandler
- transporter transporter
- protocol protocol
- results map[string]ResultSet
+ logHandler *logHandler
+ protocol protocol
+ results map[string]ResultSet
+ state connectionState
}
-func (connection *connection) close() (err error) {
- if connection.transporter != nil {
- err = connection.transporter.Close()
- }
- return
+func (connection *connection) errorCallback() {
+ connection.logHandler.log(Error, errorCallback)
+ connection.state = closedDueToError
+ _ = connection.protocol.close()
Review comment:
Any reason we are swallowing this error?
##########
File path: gremlin-go/driver/client.go
##########
@@ -71,17 +75,14 @@ func (client *Client) Close() error {
// Submit submits a Gremlin script to the server and returns a ResultSet
func (client *Client) Submit(traversalString string) (ResultSet, error) {
Review comment:
is a "traversalString" the same as a "Gremlin" script?
##########
File path: gremlin-go/driver/connection.go
##########
@@ -19,50 +19,64 @@ under the License.
package gremlingo
+import (
+ "errors"
+)
+
+type connectionState int
+
+const (
+ initialized connectionState = iota + 1
+ established
+ closed
+ closedDueToError
+)
+
type connection struct {
- host string
- port int
- transporterType TransporterType
- logHandler *logHandler
- transporter transporter
- protocol protocol
- results map[string]ResultSet
+ logHandler *logHandler
+ protocol protocol
+ results map[string]ResultSet
+ state connectionState
}
-func (connection *connection) close() (err error) {
- if connection.transporter != nil {
- err = connection.transporter.Close()
- }
- return
+func (connection *connection) errorCallback() {
+ connection.logHandler.log(Error, errorCallback)
+ connection.state = closedDueToError
+ _ = connection.protocol.close()
}
-func (connection *connection) connect() error {
- if connection.transporter != nil {
- closeErr := connection.transporter.Close()
- connection.logHandler.logf(Warning, transportCloseFailed,
closeErr)
+func (connection *connection) close() error {
+ if connection.state != established {
+ return errors.New("cannot close connection that has already
been closed or has not been connected")
}
- connection.protocol = newGremlinServerWSProtocol(connection.logHandler)
- connection.transporter = getTransportLayer(connection.transporterType,
connection.host, connection.port)
- err := connection.transporter.Connect()
- if err != nil {
- return err
+ connection.logHandler.log(Info, closeConnection)
+ var err error
+ if connection.protocol != nil {
+ err = connection.protocol.close()
}
- connection.protocol.connectionMade(connection.transporter)
- return nil
+ connection.state = closed
+ return err
}
func (connection *connection) write(request *request) (ResultSet, error) {
- if connection.transporter == nil || connection.transporter.IsClosed() {
- err := connection.connect()
- if err != nil {
- return nil, err
- }
- }
- if connection.results == nil {
- connection.results = map[string]ResultSet{}
- }
+ connection.logHandler.log(Info, writeRequest)
+ requestID := request.requestID.String()
+ connection.logHandler.logf(Info, creatingRequest, requestID)
+ connection.results[requestID] = newChannelResultSet(requestID)
+ return connection.results[requestID], connection.protocol.write(request)
+}
- // Write through protocol layer.
- responseID, err := connection.protocol.write(request,
connection.results)
- return connection.results[responseID], err
+func createConnection(host string, port int, logHandler *logHandler)
(*connection, error) {
+ conn := &connection{logHandler, nil, map[string]ResultSet{},
initialized}
+ logHandler.log(Info, connectConnection)
+ protocol, err := newGremlinServerWSProtocol(logHandler, Gorilla, host,
port, conn.results, conn.errorCallback)
+ conn.protocol = protocol
+ if err == nil {
+ conn.state = established
+ return conn, err
+ } else {
Review comment:
1. you don't need this else since you have the early return
2. usually this would be written as:
```go
if err != nil {
logHandler.logf(Error, failedConnection)
conn.state = closedDueToError
return nil, err
}
conn.state = established
return conn, err
```
3. In the `error` case does `protocol` need to be set?
##########
File path: gremlin-go/driver/serializer.go
##########
@@ -39,23 +41,47 @@ type serializer interface {
// graphBinarySerializer serializes/deserializes message to/from GraphBinary
type graphBinarySerializer struct {
- readerClass *graphBinaryReader
- writerClass *graphBinaryWriter
- mimeType string `default:"application/vnd.graphbinary-v1.0"`
+ ser *graphBinaryTypeSerializer
}
func newGraphBinarySerializer(handler *logHandler) serializer {
- reader := graphBinaryReader{handler}
- writer := graphBinaryWriter{handler}
- return graphBinarySerializer{&reader, &writer, graphBinaryMimeType}
+ serializer := graphBinaryTypeSerializer{NullType, nil, nil, nil,
handler}
+ return graphBinarySerializer{&serializer}
}
const versionByte byte = 0x81
+func convertArgs(request *request, gs graphBinarySerializer)
(map[string]interface{}, error) {
+ // TODO AN-981: Remote transaction session processor is same as bytecode
+ if request.processor == bytecodeProcessor {
+ // Convert to format:
+ // args["gremlin"]: <serialized args["gremlin"]>
+ gremlin := request.args["gremlin"]
+ switch gremlin.(type) {
+ case bytecode:
+ buffer := bytes.Buffer{}
+ gremlinBuffer, err := gs.ser.write(gremlin, &buffer)
+ if err != nil {
+ return nil, err
+ }
+ request.args["gremlin"] = gremlinBuffer
+ return request.args, nil
+ default:
+ return nil, errors.New(fmt.Sprintf("Failed to find
serializer for type '%s'.", reflect.TypeOf(gremlin).Name()))
+ }
+ } else {
+ // Use standard processor, which effectively does nothing.
+ return request.args, nil
+ }
Review comment:
it looks like everycase in the above `if` has an early return so this
else shouldn't be required.
Also, if `bytecodeProcessor` is the "normal" path consider re-writing this
as:
```go
if request.processor != bytecodeProcess {
```
##########
File path: gremlin-go/driver/connection_test.go
##########
@@ -1,50 +1,208 @@
+/*
+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 (
+ "sort"
"testing"
"github.com/stretchr/testify/assert"
"golang.org/x/text/language"
)
-const runIntegration = false
+const runIntegration = true
+const testHost string = "localhost"
+const testPort int = 8182
+const personLabel = "Person"
+const testLabel = "Test"
+const nameKey = "name"
+
+func dropGraph(t *testing.T, g *GraphTraversalSource) {
+ // Drop vertices that were added.
+ _, promise, err := g.V().Drop().Iterate()
+ assert.Nil(t, err)
+ assert.NotNil(t, promise)
+ <-promise
+}
+
+func getTestNames() []string {
+ return []string{"Lyndon", "Yang", "Simon", "Rithin", "Alexey"}
+}
Review comment:
dumb question: any reason not to just define these as a package variable?
ie:
```
package gremlingo
var testNames = []string{"Lyndon", "Yang", "Simon", "Rithin", "Alexey"}
```
##########
File path: gremlin-go/driver/serializer.go
##########
@@ -39,23 +41,47 @@ type serializer interface {
// graphBinarySerializer serializes/deserializes message to/from GraphBinary
type graphBinarySerializer struct {
- readerClass *graphBinaryReader
- writerClass *graphBinaryWriter
- mimeType string `default:"application/vnd.graphbinary-v1.0"`
+ ser *graphBinaryTypeSerializer
}
func newGraphBinarySerializer(handler *logHandler) serializer {
- reader := graphBinaryReader{handler}
- writer := graphBinaryWriter{handler}
- return graphBinarySerializer{&reader, &writer, graphBinaryMimeType}
+ serializer := graphBinaryTypeSerializer{NullType, nil, nil, nil,
handler}
+ return graphBinarySerializer{&serializer}
}
const versionByte byte = 0x81
+func convertArgs(request *request, gs graphBinarySerializer)
(map[string]interface{}, error) {
+ // TODO AN-981: Remote transaction session processor is same as bytecode
+ if request.processor == bytecodeProcessor {
+ // Convert to format:
+ // args["gremlin"]: <serialized args["gremlin"]>
+ gremlin := request.args["gremlin"]
+ switch gremlin.(type) {
+ case bytecode:
+ buffer := bytes.Buffer{}
+ gremlinBuffer, err := gs.ser.write(gremlin, &buffer)
+ if err != nil {
+ return nil, err
+ }
+ request.args["gremlin"] = gremlinBuffer
+ return request.args, nil
+ default:
+ return nil, errors.New(fmt.Sprintf("Failed to find
serializer for type '%s'.", reflect.TypeOf(gremlin).Name()))
Review comment:
1. nitpick: normally it's frowned upon to capitalize an error message.
2. you can use `%q` instead of `'%s'` unless you _want_ `'` instead of `"`
##########
File path: gremlin-go/driver/bytecode.go
##########
@@ -123,14 +126,14 @@ func (bytecode *bytecode) convertArgument(arg
interface{}) (interface{}, error)
key: v.key,
value: convertedValue,
}, nil
- // TODO: AN-970 Traversal
- //case Traversal:
- //if arg.graph != nil {
- // return errors.New()
- //}
- //for k, v := range arg.bytecode.bindings {
- // bytecode.bindings[k] = v
- //}
+ case Traversal:
+ if v.graph != nil {
+ return nil, errors.New("the child traversal was
not spawned anonymously - use the __ class rather than a TraversalSource to
construct the child traversal")
Review comment:
what is `__`?
##########
File path: gremlin-go/driver/connection.go
##########
@@ -19,50 +19,64 @@ under the License.
package gremlingo
+import (
+ "errors"
+)
+
+type connectionState int
+
+const (
+ initialized connectionState = iota + 1
+ established
+ closed
+ closedDueToError
+)
+
type connection struct {
- host string
- port int
- transporterType TransporterType
- logHandler *logHandler
- transporter transporter
- protocol protocol
- results map[string]ResultSet
+ logHandler *logHandler
+ protocol protocol
+ results map[string]ResultSet
+ state connectionState
}
-func (connection *connection) close() (err error) {
- if connection.transporter != nil {
- err = connection.transporter.Close()
- }
- return
+func (connection *connection) errorCallback() {
+ connection.logHandler.log(Error, errorCallback)
+ connection.state = closedDueToError
+ _ = connection.protocol.close()
}
-func (connection *connection) connect() error {
- if connection.transporter != nil {
- closeErr := connection.transporter.Close()
- connection.logHandler.logf(Warning, transportCloseFailed,
closeErr)
+func (connection *connection) close() error {
+ if connection.state != established {
+ return errors.New("cannot close connection that has already
been closed or has not been connected")
}
- connection.protocol = newGremlinServerWSProtocol(connection.logHandler)
- connection.transporter = getTransportLayer(connection.transporterType,
connection.host, connection.port)
- err := connection.transporter.Connect()
- if err != nil {
- return err
+ connection.logHandler.log(Info, closeConnection)
+ var err error
+ if connection.protocol != nil {
+ err = connection.protocol.close()
}
- connection.protocol.connectionMade(connection.transporter)
- return nil
+ connection.state = closed
Review comment:
here and everywhere else `connection` is accessed/mutated: does
`connection` need to be safe to access concurrently? If so, we might want to
introduce a mutex for locking
##########
File path: gremlin-go/driver/logger.go
##########
@@ -32,7 +34,7 @@ type LogVerbosity int
const (
// Debug verbosity will log everything, including fine details.
- Debug LogVerbosity = iota
+ Debug LogVerbosity = iota + 1
Review comment:
Why the plus one? Trying to avoid the 0 zero value?
##########
File path: gremlin-go/driver/result.go
##########
@@ -21,18 +21,206 @@ package gremlingo
// AN-968 Finish Result implementation.
-import "fmt"
+import (
+ "errors"
+ "fmt"
+ "reflect"
+ "strconv"
+)
// Result Struct to abstract the Result and provide functions to use it.
type Result struct {
result interface{}
}
-// AsString returns the current Result formatted as a string.
-func (r *Result) AsString() string {
+// ToString returns the string representation of the Result struct in
Go-syntax format
+func (r *Result) ToString() string {
Review comment:
`ToString` is kind of a JavaScript-ism to me. `String` would be more
idiomatic imo
##########
File path: gremlin-go/driver/client.go
##########
@@ -71,17 +75,14 @@ func (client *Client) Close() error {
// Submit submits a Gremlin script to the server and returns a ResultSet
func (client *Client) Submit(traversalString string) (ResultSet, error) {
// TODO AN-982: Obtain connection from pool of connections held by the
client.
+ client.logHandler.logf(Debug, submitStartedString, traversalString)
request := makeStringRequest(traversalString)
- if client.connection == nil {
- client.connection = &connection{
- host: client.host,
- port: client.port,
- transporterType: client.transporterType,
- logHandler: client.logHandler,
- transporter: nil,
- protocol: nil,
- }
- }
+ return client.connection.write(&request)
+}
+// SubmitBytecode submits bytecode to the server to execute and returns a
ResultSet
Review comment:
nitpick: ideally doc comments (according to https://go.dev/blog/godoc)
should be sentences ended with proper punctuation.
##########
File path: gremlin-go/README.md
##########
@@ -91,65 +121,190 @@ Note: The exact import name as well as the module prefix
for `NewDriverRemoteCon
data-flow language that enables users to succinctly express complex traversals
on (or queries of) their application's
property graph.
-Gremlin-Go implements Gremlin within the Go language and can be used on any Go
runtime greater than v1.17. Go's syntax
-has the same constructs as Java including "dot notation" for function chaining
(a.b.c), round bracket function arguments
-(a(b,c)), and support for global namespaces (a(b()) vs a(__.b())). One
important distinction with Go and Java is that
-the functions are capitalized, as is required to export functions is Go. As
such, anyone familiar with Gremlin-Java
-will immediately be able to work with Gremlin-Go.
+Gremlin-Go implements Gremlin within the Go language and can be used on any Go
runtime greater than v1.17. One important distinction with Go and Java is that
+the functions are capitalized, as is required to export functions is Go.
Gremlin-Go is designed to connect to a "server" that is hosting a
TinkerPop-enabled graph system. That "server"
could be [Gremlin Server][gs] or a [remote Gremlin provider][rgp] that exposes
protocols by which Gremlin-Go
can connect.
A typical connection to a server running on "localhost" that supports the
Gremlin Server protocol using websockets
looks like this:
-<!--
-TODO: Add Go code example of connection to server.
--->
+```go
+package main
+
+import (
+ "fmt"
+ "github.com/lyndonb-bq/tinkerpop/gremlin-go/driver"
+)
+func main() {
+ // Creating the connection to the server with default settings.
+ driverRemoteConnection, err :=
gremlingo.NewDriverRemoteConnection("localhost", 8182)
+ // Handle error
+ if err != nil {
+ fmt.Println(err)
+ return
+ }
+ // Create an anonymous traversal source with remote
+ g := gremlingo.Traversal_().WithRemote(driverRemoteConnection)
+}
+```
+We can also customize the remote connection settings. (See code documentation
for additional parameters and their usage).
+```go
+package main
+
+import (
+ "fmt"
+ "github.com/lyndonb-bq/tinkerpop/gremlin-go/driver"
+)
+
+func main() {
+ // Creating the connection to the server, changing the log verbosity to
Debug.
+ driverRemoteConnection, err :=
gremlingo.NewDriverRemoteConnection("localhost", 8182, func(settings
*gremlingo.DriverRemoteConnectionSettings) {
+ settings.LogVerbosity = gremlingo.Debug
+ })
+ // Handle error
+ if err != nil {
+ fmt.Println(err)
+ return
+ }
+ // Create an anonymous traversal source with remote
+ g := gremlingo.Traversal_().WithRemote(driverRemoteConnection)
+ // Use g with traversal as detailed in the next section.
+}
+```
Once "g" has been created using a connection, it is then possible to start
writing Gremlin traversals to query the
remote graph:
-<!--
-TODO: Add Go code example of a Gremlin traversal query.
--->
+```go
+package main
+
+import (
+ "fmt"
+ "github.com/lyndonb-bq/tinkerpop/gremlin-go/driver"
+)
-# The following material is currently Work-in-progress:
+func main() {
+ // Creating the connection to the server.
+ driverRemoteConnection, err :=
gremlingo.NewDriverRemoteConnection("localhost", 8182)
+ // Handle error
+ if err != nil {
+ fmt.Println(err)
+ return
+ }
+ // Create an anonymous traversal source with remote
+ g := gremlingo.Traversal_().WithRemote(driverRemoteConnection)
+
+ // Add a vertex with properties to the graph with the terminal step
Iterate()
+ _, promise, err := g.AddV("gremlin").Property("language",
"go").Iterate()
+ if err != nil {
+ fmt.Println(err)
+ return
+ }
+ // The returned promised is a go channel, to wait for all submitted
steps to finish execution.
+ <-promise
+
+ // Get the value of the property
+ result, err := g.V().HasLabel("gremlin").Values("language").ToList()
+ if err != nil {
+ fmt.Println(err)
+ return
+ }
+ // Print the result
+ for _, r := range result {
+ fmt.Println(r.GetString())
+ }
+}
+```
## Sample Traversals
<!--
TODO: Add Go specific changes to following paragraph:
-examples:
-"For the most part, these examples should generally translate to Go with
[little modification][differences]"
-"Given the strong correspondence between canonical Gremlin in Java and its
variants like Go, there is a limited amount
-of Go-specific documentation and examples."
-->
The Gremlin language allows users to write highly expressive graph traversals
and has a broad list of functions that
-cover a wide body of features. The [Reference Documentation][steps] describes
these functions and other aspects of the
+cover a wide body of features. Traversal in `Go` is very similar to other GLV,
with the exception that all step functions are capitalized.
+
+<!--
+The [Reference Documentation][steps] describes these functions and other
aspects of the
TinkerPop ecosystem including some specifics on [Gremlin in Go][docs] itself.
Most of the examples found in the
documentation use Groovy language syntax in the [Gremlin Console][console].
For the most part, these examples
should generally translate to Go with [little modification][differences].
Given the strong correspondence
between canonical Gremlin in Java and its variants like Go, there is a limited
amount of Go-specific
documentation and examples. This strong correspondence among variants ensures
that the general Gremlin reference
documentation is applicable to all variants and that users moving between
development languages can easily adopt the
Gremlin variant for that language.
-
-### Create Vertex
-<!--
-TODO: Add Go code to create a vertex.
-->
+### Create Vertex
+Adding a vertex with properties.
+```go
+_, promise, err :=g.AddV("gremlin").Property("language", "java").Iterate()
+// Handle error
+if err != nil {
+ fmt.Println(err)
+ return
+}
+// Wait for all steps to finish execution
+<-promise
+```
### Find Vertices
-<!--
-TODO: Add Go code for Find Vertices.
--->
+Getting the property value associated with the added vertex. We currently only
support `ToList()` for submitting the remote traversal. Support for `Next()`
will be implemented in the subsequent milestones.
+```go
+result, err := g.V().HasLabel("gremlin").Values("language").ToList()
+// Handle error
+if err != nil {
+fmt.Println(err)
+return
Review comment:
nitpick: missing tabs here (and a bit further down)
##########
File path: gremlin-go/driver/connection.go
##########
@@ -19,50 +19,64 @@ under the License.
package gremlingo
+import (
+ "errors"
+)
+
+type connectionState int
+
+const (
+ initialized connectionState = iota + 1
+ established
+ closed
+ closedDueToError
+)
+
type connection struct {
- host string
- port int
- transporterType TransporterType
- logHandler *logHandler
- transporter transporter
- protocol protocol
- results map[string]ResultSet
+ logHandler *logHandler
+ protocol protocol
+ results map[string]ResultSet
+ state connectionState
}
-func (connection *connection) close() (err error) {
- if connection.transporter != nil {
- err = connection.transporter.Close()
- }
- return
+func (connection *connection) errorCallback() {
+ connection.logHandler.log(Error, errorCallback)
+ connection.state = closedDueToError
+ _ = connection.protocol.close()
}
-func (connection *connection) connect() error {
- if connection.transporter != nil {
- closeErr := connection.transporter.Close()
- connection.logHandler.logf(Warning, transportCloseFailed,
closeErr)
+func (connection *connection) close() error {
+ if connection.state != established {
+ return errors.New("cannot close connection that has already
been closed or has not been connected")
}
- connection.protocol = newGremlinServerWSProtocol(connection.logHandler)
- connection.transporter = getTransportLayer(connection.transporterType,
connection.host, connection.port)
- err := connection.transporter.Connect()
- if err != nil {
- return err
+ connection.logHandler.log(Info, closeConnection)
+ var err error
+ if connection.protocol != nil {
+ err = connection.protocol.close()
}
- connection.protocol.connectionMade(connection.transporter)
- return nil
+ connection.state = closed
+ return err
}
func (connection *connection) write(request *request) (ResultSet, error) {
Review comment:
here and elsewhere. Does `connection` need to be so verbose? Thoughts on
`func (conn *connection)` instead?
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]