diff --git a/neo4j/config_resolver.go b/neo4j/config_resolver.go index e8287901..3dbb6bf7 100644 --- a/neo4j/config_resolver.go +++ b/neo4j/config_resolver.go @@ -38,7 +38,7 @@ type ServerAddress interface { // resolve the initial address used to create the driver. type ServerAddressResolver func(address ServerAddress) []ServerAddress -func newServerAddressUrl(hostname string, port string) *url.URL { +func newServerAddressURL(hostname string, port string) *url.URL { if hostname == "" { return nil } @@ -51,12 +51,12 @@ func newServerAddressUrl(hostname string, port string) *url.URL { return &url.URL{Host: hostAndPort} } -// A helper function that generates a ServerAddress with provided hostname and port information. +// NewServerAddress generates a ServerAddress with provided hostname and port information. func NewServerAddress(hostname string, port string) ServerAddress { - return newServerAddressUrl(hostname, port) + return newServerAddressURL(hostname, port) } -func wrapAddressResolverOrNil(addressResolver ServerAddressResolver) gobolt.UrlAddressResolver { +func wrapAddressResolverOrNil(addressResolver ServerAddressResolver) gobolt.URLAddressResolver { if addressResolver == nil { return nil } @@ -65,7 +65,7 @@ func wrapAddressResolverOrNil(addressResolver ServerAddressResolver) gobolt.UrlA var result []*url.URL for _, address := range addressResolver(address) { - result = append(result, newServerAddressUrl(address.Hostname(), address.Port())) + result = append(result, newServerAddressURL(address.Hostname(), address.Port())) } return result diff --git a/neo4j/error.go b/neo4j/error.go index 13bb92b0..ef38d582 100644 --- a/neo4j/error.go +++ b/neo4j/error.go @@ -24,31 +24,142 @@ import ( "github.com/neo4j-drivers/gobolt" ) +type databaseError struct { + classification string + code string + message string +} + +type connectorError struct { + state int + code int + description string +} + type driverError struct { message string } -func newDriverError(format string, args ...interface{}) error { +type sessionExpiredError struct { + message string +} + +func (failure *databaseError) BoltError() bool { + return true +} + +func (failure *databaseError) Classification() string { + return failure.classification +} + +func (failure *databaseError) Code() string { + return failure.code +} + +func (failure *databaseError) Message() string { + return failure.message +} + +func (failure *databaseError) Error() string { + return fmt.Sprintf("database returned error [%s]: %s", failure.code, failure.message) +} + +func (failure *connectorError) BoltError() bool { + return true +} + +func (failure *connectorError) State() int { + return failure.state +} + +func (failure *connectorError) Code() int { + return failure.code +} + +func (failure *connectorError) Description() string { + return failure.description +} + +func (failure *connectorError) Error() string { + return fmt.Sprintf("expected connection to be in READY state, where it is %d [error is %d]", failure.state, failure.code) +} + +func (failure *driverError) BoltError() bool { + return true +} + +func (failure *driverError) Message() string { + return failure.message +} + +func (failure *driverError) Error() string { + return failure.message +} + +func (failure *sessionExpiredError) BoltError() bool { + return true +} + +func (failure *sessionExpiredError) Error() string { + return failure.message +} + +func newDriverError(format string, args ...interface{}) gobolt.GenericError { return &driverError{message: fmt.Sprintf(format, args...)} } +func newSessionExpiredError(format string, args ...interface{}) error { + return &sessionExpiredError{message: fmt.Sprintf(format, args...)} +} + +func newDatabaseError(classification, code, message string) gobolt.DatabaseError { + return &databaseError{code: code, message: message, classification: classification} +} + +func newConnectorError(state int, code int, description string) gobolt.ConnectorError { + return &connectorError{state: state, code: code, description: description} +} + func isRetriableError(err error) bool { return gobolt.IsServiceUnavailable(err) || gobolt.IsTransientError(err) || gobolt.IsWriteError(err) } -func (err *driverError) Error() string { - return err.message +// IsSecurityError is a utility method to check if the provided error is related with any +// TLS failure or authentication issues. +func IsSecurityError(err error) bool { + return gobolt.IsSecurityError(err) } -func IsServiceUnavailable(err error) bool { - return gobolt.IsServiceUnavailable(err) +// IsAuthenticationError is a utility method to check if the provided error is related with any +// authentication issues. +func IsAuthenticationError(err error) bool { + return gobolt.IsAuthenticationError(err) } -func IsDriverError(err error) bool { - _, ok := err.(*driverError) - return ok +// IsClientError is a utility method to check if the provided error is related with the client +// carrying out an invalid operation. +func IsClientError(err error) bool { + return gobolt.IsClientError(err) } +// IsTransientError is a utility method to check if the provided error is related with a temporary +// failure that may be worked around by retrying. func IsTransientError(err error) bool { return gobolt.IsTransientError(err) } + +// IsSessionExpired is a utility method to check if the session no longer satisfy the criteria +// under which it was acquired, e.g. a server no longer accepts write requests. +func IsSessionExpired(err error) bool { + if _, ok := err.(*sessionExpiredError); ok { + return true + } + + return gobolt.IsSessionExpired(err) +} + +// IsServiceUnavailable is a utility method to check if the provided error can be classified +// to be in service unavailable category. +func IsServiceUnavailable(err error) bool { + return gobolt.IsServiceUnavailable(err) +} diff --git a/neo4j/error_test.go b/neo4j/error_test.go new file mode 100644 index 00000000..aa3c8939 --- /dev/null +++ b/neo4j/error_test.go @@ -0,0 +1,373 @@ +/* + * Copyright (c) 2002-2018 "Neo4j," + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Licensed 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 neo4j + +import ( + "fmt" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/gomega" +) + +var _ = Describe("Error", func() { + + Context("IsSecurityError", func() { + When("provided with a ConnectorError with BOLT_TLS_ERROR code", func() { + const ( + BOLT_TLS_ERROR = 13 + BOLT_DEFUNCT = 4 + ) + + err := newConnectorError(BOLT_DEFUNCT, BOLT_TLS_ERROR, "some description") + + It("should return true", func() { + Expect(IsSecurityError(err)).To(BeTrue()) + }) + }) + + When("provided with a DatabaseError with Neo.ClientError.Security.Unauthorized code", func() { + err := newDatabaseError("ClientError", "Neo.ClientError.Security.Unauthorized", "unauthorized") + + It("should return true", func() { + Expect(IsSecurityError(err)).To(BeTrue()) + }) + }) + + When("provided with a ConnectorError with random code", func() { + const ( + BOLT_CONNECTION_RESET = 4 + BOLT_DEFUNCT = 4 + ) + err := newConnectorError(BOLT_DEFUNCT, BOLT_CONNECTION_RESET, "some description") + + It("should return false", func() { + Expect(IsSecurityError(err)).To(BeFalse()) + }) + }) + + When("provided with a DatabaseError with random code", func() { + err := newDatabaseError("TransientError", "Neo.TransientError.Transaction.Terminated", "terminated") + + It("should return false", func() { + Expect(IsSecurityError(err)).To(BeFalse()) + }) + }) + + When("provided with generic error type", func() { + err := newDriverError("some error") + + It("should return false", func() { + Expect(IsSecurityError(err)).To(BeFalse()) + }) + }) + + When("provided with another error type", func() { + err := fmt.Errorf("some error") + + It("should return false", func() { + Expect(IsSecurityError(err)).To(BeFalse()) + }) + }) + }) + + Context("IsAuthenticationError", func() { + When("provided with a ConnectorError with BOLT_PERMISSION_DENIED code", func() { + const ( + BOLT_PERMISSION_DENIED = 7 + BOLT_DEFUNCT = 4 + ) + + err := newConnectorError(BOLT_DEFUNCT, BOLT_PERMISSION_DENIED, "some description") + + It("should return true", func() { + Expect(IsAuthenticationError(err)).To(BeTrue()) + }) + }) + + When("provided with a DatabaseError with Neo.ClientError.Security.Unauthorized code", func() { + err := newDatabaseError("ClientError", "Neo.ClientError.Security.Unauthorized", "unauthorized") + + It("should return true", func() { + Expect(IsAuthenticationError(err)).To(BeTrue()) + }) + }) + + When("provided with a ConnectorError with random code", func() { + const ( + BOLT_CONNECTION_RESET = 4 + BOLT_DEFUNCT = 4 + ) + err := newConnectorError(BOLT_DEFUNCT, BOLT_CONNECTION_RESET, "some description") + + It("should return false", func() { + Expect(IsAuthenticationError(err)).To(BeFalse()) + }) + }) + + When("provided with a DatabaseError with random code", func() { + err := newDatabaseError("TransientError", "Neo.TransientError.Transaction.Terminated", "terminated") + + It("should return false", func() { + Expect(IsAuthenticationError(err)).To(BeFalse()) + }) + }) + + When("provided with generic error type", func() { + err := newDriverError("some error") + + It("should return false", func() { + Expect(IsAuthenticationError(err)).To(BeFalse()) + }) + }) + + When("provided with another error type", func() { + err := fmt.Errorf("some error") + + It("should return false", func() { + Expect(IsAuthenticationError(err)).To(BeFalse()) + }) + }) + }) + + Context("IsClientError", func() { + When("provided with a DatabaseError with ClientError classification", func() { + err := newDatabaseError("ClientError", "Neo.ClientError.Statement.Invalid", "invalid statement") + + It("should return true", func() { + Expect(IsClientError(err)).To(BeTrue()) + }) + }) + + When("provided with a generic error", func() { + err := newDriverError("some error") + + It("should return true", func() { + Expect(IsClientError(err)).To(BeTrue()) + }) + }) + + When("provided with a DatabaseError with Neo.ClientError.Security.Unauthorized code", func() { + err := newDatabaseError("ClientError", "Neo.ClientError.Security.Unauthorized", "unauthorized") + + It("should return false", func() { + Expect(IsClientError(err)).To(BeFalse()) + }) + }) + + When("provided with a ConnectorError", func() { + err := newConnectorError(0, 13, "some connector error") + + It("should return false", func() { + Expect(IsClientError(err)).To(BeFalse()) + }) + }) + + When("provided with another error type", func() { + err := fmt.Errorf("some error") + + It("should return false", func() { + Expect(IsClientError(err)).To(BeFalse()) + }) + }) + + }) + + Context("IsTransientError", func() { + When("provided with a DatabaseError with TransientError classification", func() { + err := newDatabaseError("TransientError", "Neo.TransientError.Transaction.Timeout", "timeout") + + It("should return true", func() { + Expect(IsTransientError(err)).To(BeTrue()) + }) + }) + + When("provided with a DatabaseError with Neo.TransientError.Transaction.Terminated code", func() { + err := newDatabaseError("TransientError", "Neo.TransientError.Transaction.Terminated", "terminated") + + It("should return false", func() { + Expect(IsTransientError(err)).To(BeFalse()) + }) + }) + + When("provided with a DatabaseError with Neo.TransientError.Transaction.LockClientStopped code", func() { + err := newDatabaseError("TransientError", "Neo.TransientError.Transaction.LockClientStopped", "terminated") + + It("should return false", func() { + Expect(IsTransientError(err)).To(BeFalse()) + }) + }) + + When("provided with a generic error", func() { + err := newDriverError("some error") + + It("should return false", func() { + Expect(IsTransientError(err)).To(BeFalse()) + }) + }) + + When("provided with a ConnectorError", func() { + err := newConnectorError(0, 13, "some connector error") + + It("should return false", func() { + Expect(IsTransientError(err)).To(BeFalse()) + }) + }) + + When("provided with another error type", func() { + err := fmt.Errorf("some error") + + It("should return false", func() { + Expect(IsTransientError(err)).To(BeFalse()) + }) + }) + }) + + Context("IsSessionExpired", func() { + When("provided with a sessionExpiredError", func() { + err := &sessionExpiredError{message: "error"} + + It("should return true", func() { + Expect(IsSessionExpired(err)).To(BeTrue()) + }) + }) + + When("provided with a ConnectorError with BOLT_ROUTING_NO_SERVERS_TO_SELECT code", func() { + const ( + BOLT_ROUTING_NO_SERVERS_TO_SELECT = 0x801 + BOLT_DEFUNCT = 4 + ) + + err := newConnectorError(BOLT_DEFUNCT, BOLT_ROUTING_NO_SERVERS_TO_SELECT, "some description") + + It("should return true", func() { + Expect(IsSessionExpired(err)).To(BeTrue()) + }) + }) + + When("provided with a ConnectorError with random code", func() { + const ( + BOLT_TLS_ERROR = 13 + BOLT_DEFUNCT = 4 + ) + + err := newConnectorError(BOLT_DEFUNCT, BOLT_TLS_ERROR, "some description") + + It("should return true", func() { + Expect(IsSessionExpired(err)).To(BeFalse()) + }) + }) + + When("provided with a database error", func() { + err := newDatabaseError("ClientError", "Neo.TransientError.Transaction.LockClientStopped", "some error") + + It("should return false", func() { + Expect(IsSessionExpired(err)).To(BeFalse()) + }) + }) + + When("provided with a generic error", func() { + err := newDriverError("some error") + + It("should return false", func() { + Expect(IsSessionExpired(err)).To(BeFalse()) + }) + }) + + When("provided with a ConnectorError", func() { + err := newConnectorError(0, 13, "some connector error") + + It("should return false", func() { + Expect(IsSessionExpired(err)).To(BeFalse()) + }) + }) + + When("provided with another error type", func() { + err := fmt.Errorf("some error") + + It("should return false", func() { + Expect(IsSessionExpired(err)).To(BeFalse()) + }) + }) + }) + + Context("IsServiceUnavailable", func() { + Context("should return true", func() { + DescribeTable("when provided with a connector error code", + func(code int) { + err := newConnectorError(4, code, "some description") + + Expect(IsServiceUnavailable(err)).To(BeTrue()) + }, + Entry("BOLT_INTERRUPTED", 3), + Entry("BOLT_CONNECTION_RESET", 4), + Entry("BOLT_NO_VALID_ADDRESS", 5), + Entry("BOLT_TIMED_OUT", 6), + Entry("BOLT_CONNECTION_REFUSED", 11), + Entry("BOLT_NETWORK_UNREACHABLE", 12), + Entry("BOLT_TLS_ERROR", 13), + Entry("BOLT_END_OF_TRANSMISSION", 15), + Entry("BOLT_POOL_FULL", 0x600), + Entry("BOLT_ADDRESS_NOT_RESOLVED", 0x700), + Entry("BOLT_ROUTING_UNABLE_TO_RETRIEVE_ROUTING_TABLE", 0x800), + Entry("BOLT_ROUTING_UNABLE_TO_REFRESH_ROUTING_TABLE", 0x803), + Entry("BOLT_ROUTING_NO_SERVERS_TO_SELECT", 0x801), + ) + }) + + Context("should return false", func() { + DescribeTable("when provided with an unrelevant connector error code", + func(code int) { + err := newConnectorError(4, code, "some description") + + Expect(IsServiceUnavailable(err)).To(BeFalse()) + }, + Entry("BOLT_SERVER_FAILURE", 16), + Entry("BOLT_UNKNOWN_ERROR", 1), + Entry("BOLT_TRANSPORT_UNSUPPORTED", 0x400), + Entry("BOLT_PROTOCOL_VIOLATION", 0x500), + ) + }) + + When("provided with a database error", func() { + err := newDatabaseError("ClientError", "Neo.TransientError.Transaction.LockClientStopped", "some error") + + It("should return false", func() { + Expect(IsServiceUnavailable(err)).To(BeFalse()) + }) + }) + + When("provided with a generic error", func() { + err := newDriverError("some error") + + It("should return false", func() { + Expect(IsServiceUnavailable(err)).To(BeFalse()) + }) + }) + + When("provided with another error type", func() { + err := fmt.Errorf("some error") + + It("should return false", func() { + Expect(IsServiceUnavailable(err)).To(BeFalse()) + }) + }) + }) +}) diff --git a/neo4j/gobolt_driver.go b/neo4j/gobolt_driver.go index 34a1ea44..8173a7e1 100644 --- a/neo4j/gobolt_driver.go +++ b/neo4j/gobolt_driver.go @@ -62,6 +62,9 @@ func configToGoboltConfig(config *Config) *gobolt.Config { &dateTimeValueHandler{}, &durationValueHandler{}, }, + GenericErrorFactory: newDriverError, + ConnectorErrorFactory: newConnectorError, + DatabaseErrorFactory: newDatabaseError, } } diff --git a/neo4j/runner.go b/neo4j/runner.go index 61426df7..a5aab08a 100644 --- a/neo4j/runner.go +++ b/neo4j/runner.go @@ -144,8 +144,12 @@ func handleRecordsPhase(runner *statementRunner, activeResult *neoResult) error } func transformError(runner *statementRunner, err error) error { - if gobolt.IsWriteError(err) && runner.accessMode == AccessModeRead { - return &driverError{"write queries cannot be performed in read access mode"} + if gobolt.IsWriteError(err) { + if runner.accessMode == AccessModeRead { + return newDriverError("write queries cannot be performed in read access mode") + } + + return newSessionExpiredError("server at %s no longer accepts writes", runner.connection.RemoteAddress()) } return err diff --git a/neo4j/summary_collection.go b/neo4j/summary_collection.go index 185e5df3..d75a8870 100644 --- a/neo4j/summary_collection.go +++ b/neo4j/summary_collection.go @@ -32,14 +32,14 @@ func extractIntValue(dict *map[string]interface{}, key string, defaultValue int6 if value, ok := (*dict)[key]; ok { if valueAsLong, ok := value.(int64); ok { return valueAsLong - } else { - valueAsLong, err := strconv.ParseInt(fmt.Sprintf("%v", value), 10, 64) - if err != nil { - return defaultValue - } + } - return valueAsLong + valueAsLong, err := strconv.ParseInt(fmt.Sprintf("%v", value), 10, 64) + if err != nil { + return defaultValue } + + return valueAsLong } return defaultValue @@ -53,13 +53,13 @@ func extractStringValue(dict *map[string]interface{}, key string, defaultValue s if value, ok := (*dict)[key]; ok { if valueAsStr, ok := value.(string); ok { return valueAsStr - } else { - if isNil(value) { - return defaultValue - } + } - return fmt.Sprintf("%v", value) + if isNil(value) { + return defaultValue } + + return fmt.Sprintf("%v", value) } return defaultValue diff --git a/neo4j/test-integration/auth_test.go b/neo4j/test-integration/auth_test.go index e80a6c3a..e49af5b6 100644 --- a/neo4j/test-integration/auth_test.go +++ b/neo4j/test-integration/auth_test.go @@ -40,7 +40,7 @@ var _ = Describe("Authentication", func() { Specify("when wrong credentials are provided, it should fail with authentication error", func() { token := neo4j.BasicAuth("wrong", "wrong", "") - driver, err := neo4j.NewDriver(server.BoltUri(), token, server.Config()) + driver, err := neo4j.NewDriver(server.BoltURI(), token, server.Config()) Expect(err).To(BeNil()) defer driver.Close() @@ -54,7 +54,7 @@ var _ = Describe("Authentication", func() { verifyConnect := func(token neo4j.AuthToken) func() { return func() { - driver, err := neo4j.NewDriver(server.BoltUri(), token) + driver, err := neo4j.NewDriver(server.BoltURI(), token) Expect(err).To(BeNil()) defer driver.Close() diff --git a/neo4j/test-integration/bookmark_test.go b/neo4j/test-integration/bookmark_test.go index c3906fd3..506d971a 100644 --- a/neo4j/test-integration/bookmark_test.go +++ b/neo4j/test-integration/bookmark_test.go @@ -93,7 +93,7 @@ var _ = Describe("Bookmark", func() { }) Specify("when a node is created in auto-commit mode, last bookmark should be empty", func() { - if VersionOfDriver(driver).GreaterThanOrEqual(V3_5_0) { + if versionOfDriver(driver).GreaterThanOrEqual(V350) { Skip("this test is targeted for server version less than neo4j 3.5.0") } @@ -107,7 +107,7 @@ var _ = Describe("Bookmark", func() { }) Specify("when a node is created in auto-commit mode, last bookmark should not be empty", func() { - if VersionOfDriver(driver).LessThan(V3_5_0) { + if versionOfDriver(driver).LessThan(V350) { Skip("this test is targeted for server version after neo4j 3.5.0") } diff --git a/neo4j/test-integration/control/causal-cluster.go b/neo4j/test-integration/control/causal-cluster.go index d2e2c34e..dee67f63 100644 --- a/neo4j/test-integration/control/causal-cluster.go +++ b/neo4j/test-integration/control/causal-cluster.go @@ -31,23 +31,29 @@ import ( "github.com/neo4j/neo4j-go-driver/neo4j" ) +// ClusterMemberRole is the type of the server that's part of the causal cluster type ClusterMemberRole string const ( - LEADER ClusterMemberRole = "LEADER" - FOLLOWER ClusterMemberRole = "FOLLOWER" - READ_REPLICA ClusterMemberRole = "READ_REPLICA" + // Leader role + Leader ClusterMemberRole = "LEADER" + // Follower role + Follower ClusterMemberRole = "FOLLOWER" + // ReadReplica role + ReadReplica ClusterMemberRole = "READ_REPLICA" ) type configFunc func(config *neo4j.Config) +// ClusterMember holds information about a single server in the cluster type ClusterMember struct { path string hostnameAndPort string - boltUri string - routingUri string + boltURI string + routingURI string } +// Cluster holds information about the cluster type Cluster struct { path string username string @@ -69,6 +75,7 @@ var cluster *Cluster var clusterErr error var clusterLock sync.Mutex +// EnsureCluster either returns an existing cluster instance or starts up a new one func EnsureCluster() (*Cluster, error) { if cluster == nil && clusterErr == nil { clusterLock.Lock() @@ -81,6 +88,7 @@ func EnsureCluster() (*Cluster, error) { return cluster, clusterErr } +// StopCluster stops the cluster func StopCluster() { if cluster != nil { clusterLock.Lock() @@ -115,14 +123,14 @@ func newCluster(path string) (*Cluster, error) { paramsScanner.Split(bufio.ScanWords) index := 0 - memberBoltUri := "" + memberBoltURI := "" memberPath := "" for paramsScanner.Scan() { switch index { case 0: break case 1: - memberBoltUri = paramsScanner.Text() + memberBoltURI = paramsScanner.Text() case 2: memberPath = paramsScanner.Text() default: @@ -135,7 +143,7 @@ func newCluster(path string) (*Cluster, error) { return nil, err } - if clusterMember, err = newClusterMember(memberBoltUri, memberPath); err != nil { + if clusterMember, err = newClusterMember(memberBoltURI, memberPath); err != nil { return nil, err } @@ -176,34 +184,41 @@ func newCluster(path string) (*Cluster, error) { return result, nil } +// Username returns the configured username func (cluster *Cluster) Username() string { return cluster.username } +// Password returns the configured password func (cluster *Cluster) Password() string { return cluster.password } +// AuthToken returns the configured authentication token func (cluster *Cluster) AuthToken() neo4j.AuthToken { return cluster.authToken } +// Config returns the configured configurer function func (cluster *Cluster) Config() func(config *neo4j.Config) { return cluster.config } +// Leader returns the current leader in the cluster func (cluster *Cluster) Leader() *ClusterMember { - var leaders, _ = cluster.membersWithRole(LEADER) + var leaders, _ = cluster.membersWithRole(Leader) if len(leaders) > 0 { return leaders[0] } return nil } +// LeaderAddress returns the current leader's address func (cluster *Cluster) LeaderAddress() neo4j.ServerAddress { return &url.URL{Host: cluster.Leader().hostnameAndPort} } +// Cores returns the current core members in the cluster func (cluster *Cluster) Cores() []*ClusterMember { var cores []*ClusterMember @@ -213,6 +228,7 @@ func (cluster *Cluster) Cores() []*ClusterMember { return cores } +// CoreAddresses returns the current core members' addresses func (cluster *Cluster) CoreAddresses() []neo4j.ServerAddress { var urls []neo4j.ServerAddress @@ -223,11 +239,13 @@ func (cluster *Cluster) CoreAddresses() []neo4j.ServerAddress { return urls } +// Followers returns the current follower members in the cluster func (cluster *Cluster) Followers() []*ClusterMember { - filtered, _ := cluster.membersWithRole(FOLLOWER) + filtered, _ := cluster.membersWithRole(Follower) return filtered } +// AnyFollower returns a follower from the list of current followers func (cluster *Cluster) AnyFollower() *ClusterMember { followers := cluster.Followers() if len(followers) > 0 { @@ -237,11 +255,13 @@ func (cluster *Cluster) AnyFollower() *ClusterMember { return nil } +// ReadReplicas returns the current read replica members in the cluster func (cluster *Cluster) ReadReplicas() []*ClusterMember { - filtered, _ := cluster.membersWithRole(READ_REPLICA) + filtered, _ := cluster.membersWithRole(ReadReplica) return filtered } +// ReadReplicaAddresses returns the current read replica members' addresses func (cluster *Cluster) ReadReplicaAddresses() []neo4j.ServerAddress { var urls []neo4j.ServerAddress @@ -252,6 +272,7 @@ func (cluster *Cluster) ReadReplicaAddresses() []neo4j.ServerAddress { return urls } +// AnyReadReplica returns a read replica from the list of current read replicas func (cluster *Cluster) AnyReadReplica() *ClusterMember { readReplicas := cluster.ReadReplicas() if len(readReplicas) > 0 { @@ -263,7 +284,7 @@ func (cluster *Cluster) AnyReadReplica() *ClusterMember { func (cluster *Cluster) memberWithAddress(address string) *ClusterMember { for _, member := range cluster.members { - if member.BoltUri() == address { + if member.BoltURI() == address { return member } } @@ -290,13 +311,13 @@ func (cluster *Cluster) waitMembersToBeOnline() error { for time.Since(startTime) < clusterStartupTimeout { allMembers = make(map[string]bool, len(cluster.members)) for _, member := range cluster.members { - allMembers[member.boltUri] = false + allMembers[member.boltURI] = false } if onlineMembers, err = cluster.discoverOnlineMembers(); err != nil { lastErr = err } else { - for memberBoltAddress, _ := range onlineMembers { + for memberBoltAddress := range onlineMembers { delete(allMembers, memberBoltAddress) } @@ -391,7 +412,7 @@ func (cluster *Cluster) driverToAnyCore() (neo4j.Driver, error) { if result.Next() { role, _ := result.Record().Get("role") - if role.(string) != string(READ_REPLICA) { + if role.(string) != string(ReadReplica) { return driver, nil } } @@ -405,7 +426,7 @@ func (drivers *clusterDrivers) driverFor(member *ClusterMember) (neo4j.Driver, e return stored.(neo4j.Driver), nil } - driver, err := neo4j.NewDriver(member.BoltUri(), drivers.authToken, drivers.config) + driver, err := neo4j.NewDriver(member.BoltURI(), drivers.authToken, drivers.config) if err != nil { return nil, err } @@ -419,23 +440,28 @@ func (drivers *clusterDrivers) driverFor(member *ClusterMember) (neo4j.Driver, e return driver, nil } +// Path returns the folder where the member is installed func (member *ClusterMember) Path() string { return member.path } -func (member *ClusterMember) BoltUri() string { - return member.boltUri +// BoltURI returns the bolt:// uri used to connect to the member +func (member *ClusterMember) BoltURI() string { + return member.boltURI } -func (member *ClusterMember) RoutingUri() string { - return member.routingUri +// RoutingURI returns the bolt+routing:// uri used to connect to the cluster with this member +// as the initial router +func (member *ClusterMember) RoutingURI() string { + return member.routingURI } +// Address returns hostname:port identifier for the member func (member *ClusterMember) Address() neo4j.ServerAddress { var uri *url.URL var err error - if uri, err = url.Parse(member.boltUri); err != nil { + if uri, err = url.Parse(member.boltURI); err != nil { return nil } @@ -443,24 +469,24 @@ func (member *ClusterMember) Address() neo4j.ServerAddress { } func (member *ClusterMember) String() string { - return fmt.Sprintf("ClusterMember{boltUri=%s, boltAddress=%s, path=%s}", member.boltUri, member.hostnameAndPort, member.path) + return fmt.Sprintf("ClusterMember{boltURI=%s, boltAddress=%s, path=%s}", member.boltURI, member.hostnameAndPort, member.path) } func newClusterMember(uri string, path string) (*ClusterMember, error) { - parsedUri, err := url.Parse(uri) + parsedURI, err := url.Parse(uri) if err != nil { return nil, err } - port := parsedUri.Port() + port := parsedURI.Port() if port == "" { port = "7687" } return &ClusterMember{ path: path, - hostnameAndPort: fmt.Sprintf("%s:%s", parsedUri.Hostname(), port), - boltUri: fmt.Sprintf("bolt://%s:%s", parsedUri.Hostname(), port), - routingUri: fmt.Sprintf("bolt+routing://%s:%s", parsedUri.Hostname(), port), + hostnameAndPort: fmt.Sprintf("%s:%s", parsedURI.Hostname(), port), + boltURI: fmt.Sprintf("bolt://%s:%s", parsedURI.Hostname(), port), + routingURI: fmt.Sprintf("bolt+routing://%s:%s", parsedURI.Hostname(), port), }, nil } diff --git a/neo4j/test-integration/control/single-instance.go b/neo4j/test-integration/control/single-instance.go index e2c0caac..d7412739 100644 --- a/neo4j/test-integration/control/single-instance.go +++ b/neo4j/test-integration/control/single-instance.go @@ -31,17 +31,19 @@ import ( "github.com/neo4j/neo4j-go-driver/neo4j" ) +// SingleInstance holds information about the single instance server type SingleInstance struct { path string authToken neo4j.AuthToken config configFunc - boltUri string + boltURI string } var singleInstance *SingleInstance var singleInstanceErr error var singleInstanceLock sync.Mutex +// EnsureSingleInstance either returns an existing server instance or starts up a new one func EnsureSingleInstance() (*SingleInstance, error) { if singleInstance == nil && singleInstanceErr == nil { singleInstanceLock.Lock() @@ -54,6 +56,7 @@ func EnsureSingleInstance() (*SingleInstance, error) { return singleInstance, singleInstanceErr } +// StopSingleInstance stops the server func StopSingleInstance() { if singleInstance != nil { singleInstanceLock.Lock() @@ -80,12 +83,12 @@ func newSingleInstance(path string) (*SingleInstance, error) { return nil, err } - boltUri := "" + boltURI := "" scanner := bufio.NewScanner(strings.NewReader(output)) scannerIndex := 0 for scanner.Scan() { if scannerIndex == 1 { - boltUri = scanner.Text() + boltURI = scanner.Text() } scannerIndex++ @@ -103,7 +106,7 @@ func newSingleInstance(path string) (*SingleInstance, error) { path: path, authToken: authToken, config: config, - boltUri: boltUri, + boltURI: boltURI, } if err = result.deleteData(); err != nil { @@ -113,10 +116,12 @@ func newSingleInstance(path string) (*SingleInstance, error) { return result, nil } +// Path returns the folder where the server is installed func (server *SingleInstance) Path() string { return server.path } +// TLSCertificate returns the installed certificate used by the server func (server *SingleInstance) TLSCertificate() *x509.Certificate { bytes, err := ioutil.ReadFile(path.Join(server.path, "neo4jhome", "certificates", "neo4j.cert")) if err != nil { @@ -136,28 +141,34 @@ func (server *SingleInstance) TLSCertificate() *x509.Certificate { return cert } -func (server *SingleInstance) BoltUri() string { - return server.boltUri +// BoltURI returns the bolt uri used to connect to the member +func (server *SingleInstance) BoltURI() string { + return server.boltURI } +// Username returns the configured username func (server *SingleInstance) Username() string { return username } +// Password returns the configured password func (server *SingleInstance) Password() string { return password } +// AuthToken returns the configured authentication token func (server *SingleInstance) AuthToken() neo4j.AuthToken { return server.authToken } +// Config returns the configured configurer function func (server *SingleInstance) Config() func(config *neo4j.Config) { return server.config } +// Driver returns a driver instance to the server func (server *SingleInstance) Driver() (neo4j.Driver, error) { - return neo4j.NewDriver(server.boltUri, server.authToken, server.config) + return neo4j.NewDriver(server.boltURI, server.authToken, server.config) } func (server *SingleInstance) deleteData() error { diff --git a/neo4j/test-integration/control/utils.go b/neo4j/test-integration/control/utils.go index 4bf5398f..74f40147 100644 --- a/neo4j/test-integration/control/utils.go +++ b/neo4j/test-integration/control/utils.go @@ -69,7 +69,7 @@ func logLevel() neo4j.LogLevel { } func resolveServerPath(isCluster bool) string { - var serverPath string = os.TempDir() + var serverPath = os.TempDir() if _, file, _, ok := runtime.Caller(1); ok { serverPath = path.Join(path.Dir(file), "..", "..", "..", "build", "server") diff --git a/neo4j/test-integration/driver_test.go b/neo4j/test-integration/driver_test.go index 4c697fd5..c1fb08aa 100644 --- a/neo4j/test-integration/driver_test.go +++ b/neo4j/test-integration/driver_test.go @@ -108,7 +108,7 @@ var _ = Describe("Driver", func() { ) BeforeEach(func() { - driver, err = neo4j.NewDriver(server.BoltUri(), server.AuthToken(), server.Config(), func(config *neo4j.Config) { + driver, err = neo4j.NewDriver(server.BoltURI(), server.AuthToken(), server.Config(), func(config *neo4j.Config) { config.MaxConnectionPoolSize = 2 config.ConnectionAcquisitionTimeout = 0 }) @@ -152,7 +152,7 @@ var _ = Describe("Driver", func() { ) BeforeEach(func() { - driver, err = neo4j.NewDriver(server.BoltUri(), server.AuthToken(), server.Config(), func(config *neo4j.Config) { + driver, err = neo4j.NewDriver(server.BoltURI(), server.AuthToken(), server.Config(), func(config *neo4j.Config) { config.MaxConnectionPoolSize = 2 config.ConnectionAcquisitionTimeout = 10 * time.Second }) diff --git a/neo4j/test-integration/examples_test.go b/neo4j/test-integration/examples_test.go index 5de73501..d747e1c7 100644 --- a/neo4j/test-integration/examples_test.go +++ b/neo4j/test-integration/examples_test.go @@ -47,7 +47,7 @@ var _ = Describe("Examples", func() { Fail(err.Error()) } - uri = singleInstance.BoltUri() + uri = singleInstance.BoltURI() username = singleInstance.Username() password = singleInstance.Password() }) @@ -334,9 +334,9 @@ func createDriverWithTrustStrategy(uri, username, password string) (neo4j.Driver // end::config-trust[] // tag::config-custom-resolver[] -func createDriverWithAddressResolver(virtualUri, username, password string, addresses ...neo4j.ServerAddress) (neo4j.Driver, error) { +func createDriverWithAddressResolver(virtualURI, username, password string, addresses ...neo4j.ServerAddress) (neo4j.Driver, error) { // Address resolver is only valid for bolt+routing uri - return neo4j.NewDriver(virtualUri, neo4j.BasicAuth(username, password, ""), func(config *neo4j.Config) { + return neo4j.NewDriver(virtualURI, neo4j.BasicAuth(username, password, ""), func(config *neo4j.Config) { config.AddressResolver = func(address neo4j.ServerAddress) []neo4j.ServerAddress { return addresses } @@ -349,8 +349,8 @@ func addPerson(name string) error { driver neo4j.Driver session neo4j.Session result neo4j.Result - username string = "neo4j" - password string = "some password" + username = "neo4j" + password = "some password" ) driver, err = createDriverWithAddressResolver("bolt+routing://x.acme.com", username, password, diff --git a/neo4j/test-integration/routing_test.go b/neo4j/test-integration/routing_test.go index 526e6950..b6409588 100644 --- a/neo4j/test-integration/routing_test.go +++ b/neo4j/test-integration/routing_test.go @@ -46,7 +46,7 @@ var _ = Describe("Routing", func() { testReadAndWriteOnSameSession := func(member *control.ClusterMember, name string) { Expect(member).NotTo(BeNil()) - driver, err = neo4j.NewDriver(member.RoutingUri(), cluster.AuthToken(), cluster.Config()) + driver, err = neo4j.NewDriver(member.RoutingURI(), cluster.AuthToken(), cluster.Config()) Expect(err).To(BeNil()) session, err = driver.Session(neo4j.AccessModeWrite) @@ -105,7 +105,7 @@ var _ = Describe("Routing", func() { readReplica := cluster.AnyReadReplica() Expect(readReplica).NotTo(BeNil()) - driver, err = neo4j.NewDriver(readReplica.RoutingUri(), cluster.AuthToken(), cluster.Config()) + driver, err = neo4j.NewDriver(readReplica.RoutingURI(), cluster.AuthToken(), cluster.Config()) Expect(err).To(BeNil()) session, err = driver.Session(neo4j.AccessModeRead) @@ -123,7 +123,7 @@ var _ = Describe("Routing", func() { leader := cluster.Leader() Expect(leader).NotTo(BeNil()) - driver, err = neo4j.NewDriver(leader.RoutingUri(), cluster.AuthToken(), cluster.Config()) + driver, err = neo4j.NewDriver(leader.RoutingURI(), cluster.AuthToken(), cluster.Config()) Expect(err).To(BeNil()) session, err = driver.Session(neo4j.AccessModeWrite) diff --git a/neo4j/test-integration/session_test.go b/neo4j/test-integration/session_test.go index 798918f8..7a05243f 100644 --- a/neo4j/test-integration/session_test.go +++ b/neo4j/test-integration/session_test.go @@ -313,7 +313,7 @@ var _ = Describe("Session", func() { Expect(err).To(BeNil()) Expect(driver).NotTo(BeNil()) - if VersionOfDriver(driver).LessThan(V3_5_0) { + if versionOfDriver(driver).LessThan(V350) { Skip("this test is targeted for server version after neo4j 3.5.0") } @@ -431,7 +431,7 @@ var _ = Describe("Session", func() { Expect(err).To(BeNil()) Expect(driver).NotTo(BeNil()) - if VersionOfDriver(driver).GreaterThanOrEqual(V3_5_0) { + if versionOfDriver(driver).GreaterThanOrEqual(V350) { Skip("this test is targeted for server versions less than neo4j 3.5.0") } diff --git a/neo4j/test-integration/stress/stress.go b/neo4j/test-integration/stress/stress.go index aaac3f21..aa6985d7 100644 --- a/neo4j/test-integration/stress/stress.go +++ b/neo4j/test-integration/stress/stress.go @@ -29,6 +29,7 @@ import ( "sync/atomic" ) +// TestContext provides state data shared across tests type TestContext struct { driver neo4j.Driver stop int32 @@ -42,6 +43,7 @@ type TestContext struct { leaderSwitchCount int32 } +// NewTestContext returns a new TestContext func NewTestContext(driver neo4j.Driver) *TestContext { result := &TestContext{ driver: driver, @@ -59,10 +61,12 @@ func NewTestContext(driver neo4j.Driver) *TestContext { return result } +// ShouldStop returns whether a stop is signalled func (ctx *TestContext) ShouldStop() bool { return atomic.LoadInt32(&ctx.stop) > 0 } +// Stop signals a stop func (ctx *TestContext) Stop() { atomic.CompareAndSwapInt32(&ctx.stop, 0, 1) } @@ -94,7 +98,7 @@ func (ctx *TestContext) processSummary(summary neo4j.ResultSummary) { return } - var count int32 = 0 + var count int32 lastCountInt, _ := ctx.readNodeCountsByServer.LoadOrStore(summary.Server().Address(), &count) lastCount := lastCountInt.(*int32) @@ -110,6 +114,7 @@ func (ctx *TestContext) handleFailure(err error) bool { return false } +// PrintStats writes summary information about the completed test func (ctx *TestContext) PrintStats() { fmt.Printf("Stats:\n") fmt.Printf("\tNodes Created: %d\n", ctx.createdNodeCount) @@ -164,6 +169,7 @@ func newStressTransaction(session neo4j.Session, useBookmark bool, ctx *TestCont return tx } +// ReadQueryExecutor returns a new test executor which reads using Session.Run func ReadQueryExecutor(driver neo4j.Driver, useBookmark bool) func(ctx *TestContext) { return func(ctx *TestContext) { session := newStressSession(driver, useBookmark, neo4j.AccessModeRead, ctx) @@ -190,6 +196,7 @@ func ReadQueryExecutor(driver neo4j.Driver, useBookmark bool) func(ctx *TestCont } } +// ReadQueryInTxExecutor returns a new test executor which reads using Transaction.Run func ReadQueryInTxExecutor(driver neo4j.Driver, useBookmark bool) func(ctx *TestContext) { return func(ctx *TestContext) { session := newStressSession(driver, useBookmark, neo4j.AccessModeRead, ctx) @@ -222,6 +229,7 @@ func ReadQueryInTxExecutor(driver neo4j.Driver, useBookmark bool) func(ctx *Test } } +// ReadQueryWithReadTransactionExecutor returns a new test executor which reads using Session.ReadTransaction func ReadQueryWithReadTransactionExecutor(driver neo4j.Driver, useBookmark bool) func(ctx *TestContext) { return func(ctx *TestContext) { session := newStressSession(driver, useBookmark, neo4j.AccessModeRead, ctx) @@ -251,6 +259,7 @@ func ReadQueryWithReadTransactionExecutor(driver neo4j.Driver, useBookmark bool) } } +// WriteQueryExecutor returns a new test executor which writes using Session.Run func WriteQueryExecutor(driver neo4j.Driver, useBookmark bool) func(ctx *TestContext) { return func(ctx *TestContext) { session := newStressSession(driver, useBookmark, neo4j.AccessModeWrite, ctx) @@ -271,6 +280,7 @@ func WriteQueryExecutor(driver neo4j.Driver, useBookmark bool) func(ctx *TestCon } } +// WriteQueryInTxExecutor returns a new test executor which writes using Transaction.Run func WriteQueryInTxExecutor(driver neo4j.Driver, useBookmark bool) func(ctx *TestContext) { return func(ctx *TestContext) { session := newStressSession(driver, useBookmark, neo4j.AccessModeWrite, ctx) @@ -297,6 +307,7 @@ func WriteQueryInTxExecutor(driver neo4j.Driver, useBookmark bool) func(ctx *Tes } } +// WriteQueryWithWriteTransactionExecutor returns a new test executor which writes using Session.WriteTransaction func WriteQueryWithWriteTransactionExecutor(driver neo4j.Driver, useBookmark bool) func(ctx *TestContext) { return func(ctx *TestContext) { session := newStressSession(driver, useBookmark, neo4j.AccessModeWrite, ctx) @@ -321,6 +332,7 @@ func WriteQueryWithWriteTransactionExecutor(driver neo4j.Driver, useBookmark boo } } +// WriteQueryInReadSessionExecutor returns a new test executor which tries writes using Session.Run with read access mode func WriteQueryInReadSessionExecutor(driver neo4j.Driver, useBookmark bool) func(ctx *TestContext) { return func(ctx *TestContext) { session := newStressSession(driver, useBookmark, neo4j.AccessModeRead, ctx) @@ -330,11 +342,12 @@ func WriteQueryInReadSessionExecutor(driver neo4j.Driver, useBookmark bool) func Expect(err).To(BeNil()) summary, err := result.Consume() - Expect(err).To(BeDriverError(ContainSubstring("write queries cannot be performed in read access mode"))) + Expect(err).To(BeGenericError(ContainSubstring("write queries cannot be performed in read access mode"))) Expect(summary).To(BeNil()) } } +// WriteQueryInTxInReadSessionExecutor returns a new test executor which tries writes using Transaction.Run with read access mode func WriteQueryInTxInReadSessionExecutor(driver neo4j.Driver, useBookmark bool) func(ctx *TestContext) { return func(ctx *TestContext) { session := newStressSession(driver, useBookmark, neo4j.AccessModeRead, ctx) @@ -347,11 +360,12 @@ func WriteQueryInTxInReadSessionExecutor(driver neo4j.Driver, useBookmark bool) Expect(err).To(BeNil()) summary, err := result.Consume() - Expect(err).To(BeDriverError(ContainSubstring("write queries cannot be performed in read access mode"))) + Expect(err).To(BeGenericError(ContainSubstring("write queries cannot be performed in read access mode"))) Expect(summary).To(BeNil()) } } +// FailingQueryExecutor returns a new test executor which fails in streaming using Session.Run func FailingQueryExecutor(driver neo4j.Driver, useBookmark bool) func(ctx *TestContext) { return func(ctx *TestContext) { session := newStressSession(driver, useBookmark, neo4j.AccessModeRead, ctx) @@ -366,6 +380,7 @@ func FailingQueryExecutor(driver neo4j.Driver, useBookmark bool) func(ctx *TestC } } +// FailingQueryInTxExecutor returns a new test executor which fails in streaming using Transaction.Run func FailingQueryInTxExecutor(driver neo4j.Driver, useBookmark bool) func(ctx *TestContext) { return func(ctx *TestContext) { session := newStressSession(driver, useBookmark, neo4j.AccessModeRead, ctx) @@ -383,6 +398,7 @@ func FailingQueryInTxExecutor(driver neo4j.Driver, useBookmark bool) func(ctx *T } } +// FailingQueryWithReadTransactionExecutor returns a new test executor which fails in streaming using Session.ReadTransaction func FailingQueryWithReadTransactionExecutor(driver neo4j.Driver, useBookmark bool) func(ctx *TestContext) { return func(ctx *TestContext) { session := newStressSession(driver, useBookmark, neo4j.AccessModeRead, ctx) @@ -400,6 +416,7 @@ func FailingQueryWithReadTransactionExecutor(driver neo4j.Driver, useBookmark bo } } +// FailingQueryWithWriteTransactionExecutor returns a new test executor which fails in streaming using Session.WriteTransaction func FailingQueryWithWriteTransactionExecutor(driver neo4j.Driver, useBookmark bool) func(ctx *TestContext) { return func(ctx *TestContext) { session := newStressSession(driver, useBookmark, neo4j.AccessModeRead, ctx) @@ -417,6 +434,7 @@ func FailingQueryWithWriteTransactionExecutor(driver neo4j.Driver, useBookmark b } } +// WrongQueryExecutor returns a new test executor which fails using Session.Run func WrongQueryExecutor(driver neo4j.Driver) func(ctx *TestContext) { return func(ctx *TestContext) { session := newStressSession(driver, false, neo4j.AccessModeRead, ctx) @@ -430,6 +448,7 @@ func WrongQueryExecutor(driver neo4j.Driver) func(ctx *TestContext) { } } +// WrongQueryInTxExecutor returns a new test executor which fails using Transaction.Run func WrongQueryInTxExecutor(driver neo4j.Driver) func(ctx *TestContext) { return func(ctx *TestContext) { session := newStressSession(driver, false, neo4j.AccessModeWrite, ctx) diff --git a/neo4j/test-integration/stress_test.go b/neo4j/test-integration/stress_test.go index 8531cca9..aa459f9e 100644 --- a/neo4j/test-integration/stress_test.go +++ b/neo4j/test-integration/stress_test.go @@ -81,7 +81,7 @@ var _ = Describe("Stress Test", func() { Expect(err).To(BeNil()) Expect(server).NotTo(BeNil()) - driver, err = neo4j.NewDriver(server.BoltUri(), server.AuthToken(), server.Config()) + driver, err = neo4j.NewDriver(server.BoltURI(), server.AuthToken(), server.Config()) Expect(err).To(BeNil()) Expect(driver).NotTo(BeNil()) @@ -138,7 +138,7 @@ var _ = Describe("Stress Test", func() { Expect(err).To(BeNil()) Expect(cluster).NotTo(BeNil()) - driver, err = neo4j.NewDriver(cluster.AnyFollower().RoutingUri(), cluster.AuthToken(), cluster.Config()) + driver, err = neo4j.NewDriver(cluster.AnyFollower().RoutingURI(), cluster.AuthToken(), cluster.Config()) Expect(err).To(BeNil()) Expect(driver).NotTo(BeNil()) diff --git a/neo4j/test-integration/timeout_test.go b/neo4j/test-integration/timeout_test.go index 4e009d84..11c85dad 100644 --- a/neo4j/test-integration/timeout_test.go +++ b/neo4j/test-integration/timeout_test.go @@ -49,7 +49,7 @@ var _ = Describe("Timeout and Lifetime", func() { var driver neo4j.Driver var session1, session2 neo4j.Session - driver, err = neo4j.NewDriver(server.BoltUri(), server.AuthToken(), server.Config(), func(config *neo4j.Config) { + driver, err = neo4j.NewDriver(server.BoltURI(), server.AuthToken(), server.Config(), func(config *neo4j.Config) { config.Log = log config.ConnectionAcquisitionTimeout = 1 * time.Second config.MaxConnectionPoolSize = 1 @@ -75,7 +75,7 @@ var _ = Describe("Timeout and Lifetime", func() { var driver neo4j.Driver var session1, session2 neo4j.Session - driver, err = neo4j.NewDriver(server.BoltUri(), server.AuthToken(), server.Config(), func(config *neo4j.Config) { + driver, err = neo4j.NewDriver(server.BoltURI(), server.AuthToken(), server.Config(), func(config *neo4j.Config) { config.Log = log config.MaxConnectionLifetime = 5 * time.Second config.MaxConnectionPoolSize = 1 @@ -120,7 +120,7 @@ var _ = Describe("Timeout and Lifetime", func() { // var session neo4j.Session // var result neo4j.Result // - // driver, err = neo4j.NewDriver(server.BoltUri(), server.AuthToken(), server.Config(), func(config *neo4j.Config) { + // driver, err = neo4j.NewDriver(server.BoltURI(), server.AuthToken(), server.Config(), func(config *neo4j.Config) { // config.Log = log // config.SocketReceiveTimeout = 1 * time.Second // }) @@ -144,7 +144,7 @@ var _ = Describe("Timeout and Lifetime", func() { var session neo4j.Session var result neo4j.Result - driver, err = neo4j.NewDriver(server.BoltUri(), server.AuthToken(), server.Config(), func(config *neo4j.Config) { + driver, err = neo4j.NewDriver(server.BoltURI(), server.AuthToken(), server.Config(), func(config *neo4j.Config) { config.Encrypted = false config.Log = log config.SocketReceiveTimeout = 1 * time.Second diff --git a/neo4j/test-integration/transaction_test.go b/neo4j/test-integration/transaction_test.go index d8d6348f..05144e4d 100644 --- a/neo4j/test-integration/transaction_test.go +++ b/neo4j/test-integration/transaction_test.go @@ -21,9 +21,9 @@ package test_integration import ( "fmt" + "github.com/neo4j/neo4j-go-driver/neo4j/test-integration/utils" "time" - "github.com/neo4j-drivers/gobolt" "github.com/neo4j/neo4j-go-driver/neo4j" "github.com/neo4j/neo4j-go-driver/neo4j/test-integration/control" @@ -63,10 +63,7 @@ var _ = Describe("Transaction", func() { }) Context("Retry Mechanism", func() { - transientError := gobolt.NewDatabaseError(map[string]interface{}{ - "code": "Neo.TransientError.Transaction.Outdated", - "message": "some transient error", - }) + transientError := utils.NewDatabaseErrorForTest("TransientError", "Neo.TransientError.Transaction.Outdated", "some transient error") It("should work on WriteTransaction", func() { times := 0 @@ -217,7 +214,7 @@ var _ = Describe("Transaction", func() { Context("V3", func() { BeforeEach(func() { - if VersionOfDriver(driver).LessThan(V3_5_0) { + if versionOfDriver(driver).LessThan(V350) { Skip("this test is targeted for server version after neo4j 3.5.0") } }) @@ -265,7 +262,7 @@ var _ = Describe("Transaction", func() { Context("V3 API on V1 & V2", func() { BeforeEach(func() { - if VersionOfDriver(driver).GreaterThanOrEqual(V3_5_0) { + if versionOfDriver(driver).GreaterThanOrEqual(V350) { Skip("this test is targeted for server versions less than neo4j 3.5.0") } }) diff --git a/neo4j/test-integration/utils/error_utils.go b/neo4j/test-integration/utils/error_utils.go new file mode 100644 index 00000000..93f6bbbc --- /dev/null +++ b/neo4j/test-integration/utils/error_utils.go @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2002-2018 "Neo4j," + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Licensed 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 utils + +import ( + "fmt" + "github.com/neo4j-drivers/gobolt" +) + +type testDatabaseError struct { + classification string + code string + message string +} + +type testConnectorError struct { + state int + code int + description string +} + +type testDriverError struct { + message string +} + +func (failure *testDatabaseError) BoltError() bool { + return true +} + +func (failure *testDatabaseError) Classification() string { + return failure.classification +} + +func (failure *testDatabaseError) Code() string { + return failure.code +} + +func (failure *testDatabaseError) Message() string { + return failure.message +} + +func (failure *testDatabaseError) Error() string { + return fmt.Sprintf("database returned error [%s]: %s", failure.code, failure.message) +} + +func (failure *testConnectorError) BoltError() bool { + return true +} + +func (failure *testConnectorError) State() int { + return failure.state +} + +func (failure *testConnectorError) Code() int { + return failure.code +} + +func (failure *testConnectorError) Description() string { + return failure.description +} + +func (failure *testConnectorError) Error() string { + return fmt.Sprintf("expected connection to be in READY state, where it is %d [error is %d]", failure.state, failure.code) +} + +func (failure *testDriverError) BoltError() bool { + return true +} + +func (failure *testDriverError) Message() string { + return failure.message +} + +func (failure *testDriverError) Error() string { + return failure.message +} + +func NewDriverErrorForTest(format string, args ...interface{}) gobolt.GenericError { + return &testDriverError{message: fmt.Sprintf(format, args...)} +} + +func NewDatabaseErrorForTest(classification, code, message string) gobolt.DatabaseError { + return &testDatabaseError{code: code, message: message, classification: classification} +} + +func NewConnectorErrorForTest(state int, code int, description string) gobolt.ConnectorError { + return &testConnectorError{state: state, code: code, description: description} +} diff --git a/neo4j/test-integration/utils/mem_log.go b/neo4j/test-integration/utils/mem_log.go index f399d4dd..b45c18f7 100644 --- a/neo4j/test-integration/utils/mem_log.go +++ b/neo4j/test-integration/utils/mem_log.go @@ -21,41 +21,55 @@ package utils import "fmt" +// MemoryLogging collects all log messages into its own data structures, mostly to +// be used in tests type MemoryLogging struct { + // Errors contains all log messages written at Error level Errors []string + // Warnings contains all log messages written at Warning level Warnings []string + // Infos contains all log messages written at Info level Infos []string + // Debugs contains all log messages written at Debug level Debugs []string } +// ErrorEnabled returns whether Error level is enabled func (log *MemoryLogging) ErrorEnabled() bool { return true } +// WarningEnabled returns whether Warning level is enabled func (log *MemoryLogging) WarningEnabled() bool { return true } +// InfoEnabled returns whether Info level is enabled func (log *MemoryLogging) InfoEnabled() bool { return true } +// DebugEnabled returns whether Debug level is enabled func (log *MemoryLogging) DebugEnabled() bool { return true } +// Errorf writes a log message at Error level func (log *MemoryLogging) Errorf(message string, args ...interface{}) { log.Errors = append(log.Errors, fmt.Sprintf(message, args...)) } +// Warningf writes a log message at Warning level func (log *MemoryLogging) Warningf(message string, args ...interface{}) { log.Warnings = append(log.Warnings, fmt.Sprintf(message, args...)) } +// Infof writes a log message at Info level func (log *MemoryLogging) Infof(message string, args ...interface{}) { log.Infos = append(log.Infos, fmt.Sprintf(message, args...)) } +// Debugf writes a log message at Debug level func (log *MemoryLogging) Debugf(message string, args ...interface{}) { log.Debugs = append(log.Debugs, fmt.Sprintf(message, args...)) } diff --git a/neo4j/test-integration/values_spatial_test.go b/neo4j/test-integration/values_spatial_test.go index b9bf6df0..edb7fc72 100644 --- a/neo4j/test-integration/values_spatial_test.go +++ b/neo4j/test-integration/values_spatial_test.go @@ -34,10 +34,10 @@ import ( var _ = Describe("Spatial Types", func() { const ( - WGS84SrId int = 4326 - WGS843DSrId int = 4979 - CartesianSrId int = 7203 - Cartesian3DSrId int = 9157 + WGS84SrID int = 4326 + WGS843DSrID int = 4979 + CartesianSrID int = 7203 + Cartesian3DSrID int = 9157 ) var server *control.SingleInstance @@ -57,7 +57,7 @@ var _ = Describe("Spatial Types", func() { Expect(err).To(BeNil()) Expect(driver).NotTo(BeNil()) - if VersionOfDriver(driver).LessThan(V3_4_0) { + if versionOfDriver(driver).LessThan(V340) { Skip("spatial types are only available after neo4j 3.4.0 release") } @@ -135,13 +135,13 @@ var _ = Describe("Spatial Types", func() { switch sequence % 4 { case 0: - return neo4j.NewPoint2D(WGS84SrId, randomDouble(), randomDouble()) + return neo4j.NewPoint2D(WGS84SrID, randomDouble(), randomDouble()) case 1: - return neo4j.NewPoint3D(WGS843DSrId, randomDouble(), randomDouble(), randomDouble()) + return neo4j.NewPoint3D(WGS843DSrID, randomDouble(), randomDouble(), randomDouble()) case 2: - return neo4j.NewPoint2D(CartesianSrId, randomDouble(), randomDouble()) + return neo4j.NewPoint2D(CartesianSrID, randomDouble(), randomDouble()) case 3: - return neo4j.NewPoint3D(Cartesian3DSrId, randomDouble(), randomDouble(), randomDouble()) + return neo4j.NewPoint3D(Cartesian3DSrID, randomDouble(), randomDouble(), randomDouble()) default: panic("not expected") } @@ -164,13 +164,13 @@ var _ = Describe("Spatial Types", func() { var point2 = result.Record().GetByIndex(1).(*neo4j.Point) Expect(point1).NotTo(BeNil()) - Expect(point1.SrId()).To(Equal(CartesianSrId)) + Expect(point1.SrId()).To(Equal(CartesianSrID)) Expect(point1.X()).To(Equal(39.111748)) Expect(point1.Y()).To(Equal(-76.775635)) Expect(point1.Z()).To(BeNaN()) Expect(point2).NotTo(BeNil()) - Expect(point2.SrId()).To(Equal(Cartesian3DSrId)) + Expect(point2.SrId()).To(Equal(Cartesian3DSrID)) Expect(point2.X()).To(Equal(39.111748)) Expect(point2.Y()).To(Equal(-76.775635)) Expect(point2.Z()).To(Equal(35.120)) @@ -180,8 +180,8 @@ var _ = Describe("Spatial Types", func() { }) It("should be able to send points", func() { - point1 := neo4j.NewPoint2D(WGS84SrId, 51.5044585, -0.105658) - point2 := neo4j.NewPoint3D(WGS843DSrId, 51.5044585, -0.105658, 35.120) + point1 := neo4j.NewPoint2D(WGS84SrID, 51.5044585, -0.105658) + point2 := neo4j.NewPoint3D(WGS843DSrID, 51.5044585, -0.105658, 35.120) result, err = session.Run("CREATE (n:POI { location1: $point1, location2: $point2 }) RETURN n", map[string]interface{}{ "point1": point1, @@ -211,8 +211,8 @@ var _ = Describe("Spatial Types", func() { }) It("should be able to send points - pass by value", func() { - point1 := neo4j.NewPoint2D(WGS84SrId, 51.5044585, -0.105658) - point2 := neo4j.NewPoint3D(WGS843DSrId, 51.5044585, -0.105658, 35.120) + point1 := neo4j.NewPoint2D(WGS84SrID, 51.5044585, -0.105658) + point2 := neo4j.NewPoint3D(WGS843DSrID, 51.5044585, -0.105658, 35.120) result, err = session.Run("CREATE (n:POI { location1: $point1, location2: $point2 }) RETURN n", map[string]interface{}{ "point1": *point1, @@ -242,10 +242,10 @@ var _ = Describe("Spatial Types", func() { }) It("should send and receive point", func() { - testSendAndReceive(neo4j.NewPoint2D(WGS84SrId, 51.24923585, 0.92723724)) - testSendAndReceive(neo4j.NewPoint3D(WGS843DSrId, 22.86211019, 171.61820439, 0.1230987)) - testSendAndReceive(neo4j.NewPoint2D(CartesianSrId, 39.111748, -76.775635)) - testSendAndReceive(neo4j.NewPoint3D(Cartesian3DSrId, 39.111748, -76.775635, 19.2937302840)) + testSendAndReceive(neo4j.NewPoint2D(WGS84SrID, 51.24923585, 0.92723724)) + testSendAndReceive(neo4j.NewPoint3D(WGS843DSrID, 22.86211019, 171.61820439, 0.1230987)) + testSendAndReceive(neo4j.NewPoint2D(CartesianSrID, 39.111748, -76.775635)) + testSendAndReceive(neo4j.NewPoint3D(Cartesian3DSrID, 39.111748, -76.775635, 19.2937302840)) }) It("should send and receive points - randomised", func() { diff --git a/neo4j/test-integration/values_temporal_test.go b/neo4j/test-integration/values_temporal_test.go index 5725f8bd..b2d5d9be 100644 --- a/neo4j/test-integration/values_temporal_test.go +++ b/neo4j/test-integration/values_temporal_test.go @@ -53,7 +53,7 @@ var _ = Describe("Temporal Types", func() { Expect(err).To(BeNil()) Expect(driver).NotTo(BeNil()) - if VersionOfDriver(driver).LessThan(V3_4_0) { + if versionOfDriver(driver).LessThan(V340) { Skip("temporal types are only available after neo4j 3.4.0 release") } diff --git a/neo4j/test-integration/values_unsupported_test.go b/neo4j/test-integration/values_unsupported_test.go index a854277c..d01f0d0a 100644 --- a/neo4j/test-integration/values_unsupported_test.go +++ b/neo4j/test-integration/values_unsupported_test.go @@ -32,10 +32,8 @@ import ( var _ = Describe("Unsupported Types [V1]", func() { const ( - WGS84SrId int = 4326 - WGS843DSrId int = 4979 - CartesianSrId int = 7203 - Cartesian3DSrId int = 9157 + WGS84SrID int = 4326 + WGS843DSrID int = 4979 ) var server *control.SingleInstance @@ -53,7 +51,7 @@ var _ = Describe("Unsupported Types [V1]", func() { Expect(err).To(BeNil()) Expect(driver).NotTo(BeNil()) - if VersionOfDriver(driver).GreaterThanOrEqual(V3_4_0) { + if versionOfDriver(driver).GreaterThanOrEqual(V340) { Skip("this test is targeted for server version less than neo4j 3.4.0") } @@ -75,16 +73,16 @@ var _ = Describe("Unsupported Types [V1]", func() { testSend := func(data interface{}) { result, err = session.Run("WITH $x RETURN 1", map[string]interface{}{"x": data}) Expect(err).To(BeConnectorErrorWithCode(0x501)) - Expect(err).To(BeConnectorErrorWithDescription("unable to generate RUN message")) + Expect(err).To(BeConnectorErrorWithDescription("unable to generate run message")) } Context("Send", func() { It("should fail sending Point (2D)", func() { - testSend(neo4j.NewPoint2D(WGS84SrId, 1.0, 1.0)) + testSend(neo4j.NewPoint2D(WGS84SrID, 1.0, 1.0)) }) It("should fail sending Point (3D)", func() { - testSend(neo4j.NewPoint3D(WGS843DSrId, 1.0, 1.0, 1.0)) + testSend(neo4j.NewPoint3D(WGS843DSrID, 1.0, 1.0, 1.0)) }) It("should fail sending Duration", func() { diff --git a/neo4j/test-integration/version.go b/neo4j/test-integration/version.go index 10a9f6fb..a6aa29b3 100644 --- a/neo4j/test-integration/version.go +++ b/neo4j/test-integration/version.go @@ -6,11 +6,13 @@ import ( ) var ( - V3_4_0 = utils.VersionOf("3.4.0") - V3_5_0 = utils.VersionOf("3.5.0") + // V340 identifies server version 3.4.0 + V340 = utils.VersionOf("3.4.0") + // V350 identifies server version 3.5.0 + V350 = utils.VersionOf("3.5.0") ) -func VersionOfDriver(driver neo4j.Driver) utils.Version { +func versionOfDriver(driver neo4j.Driver) utils.Version { session, err := driver.Session(neo4j.AccessModeRead) if err != nil { return utils.VersionOf("0.0.0") diff --git a/neo4j/test-stub/control/boltstub.go b/neo4j/test-stub/control/boltstub.go index 3e8ad5ae..074c5825 100644 --- a/neo4j/test-stub/control/boltstub.go +++ b/neo4j/test-stub/control/boltstub.go @@ -70,7 +70,7 @@ func (server *StubServer) exitError() string { // NewStubServer launches the stub server on the given port with the given script func NewStubServer(port int, script string) *StubServer { - var testScriptsDir string = os.TempDir() + var testScriptsDir = os.TempDir() if _, file, _, ok := runtime.Caller(1); ok { testScriptsDir = path.Join(path.Dir(file), "scripts") @@ -173,6 +173,7 @@ func (server *StubServer) Finished() bool { return true } +// Close releases all process related resources func (server *StubServer) Close() { server.stub.Process.Release() } diff --git a/neo4j/utils/test/omega_error_matchers.go b/neo4j/utils/test/omega_error_matchers.go index 05a6c55c..d98bf05c 100644 --- a/neo4j/utils/test/omega_error_matchers.go +++ b/neo4j/utils/test/omega_error_matchers.go @@ -21,15 +21,13 @@ package test import ( "fmt" - "github.com/neo4j/neo4j-go-driver/neo4j" - "github.com/neo4j-drivers/gobolt" "github.com/onsi/gomega" "github.com/onsi/gomega/types" ) -func BeDriverError(messageMatcher types.GomegaMatcher) types.GomegaMatcher { - return &driverErrorMatcher{ +func BeGenericError(messageMatcher types.GomegaMatcher) types.GomegaMatcher { + return &genericErrorMatcher{ messageMatcher: messageMatcher, } } @@ -99,7 +97,7 @@ func ContainMessage(part string) types.GomegaMatcher { } } -type driverErrorMatcher struct { +type genericErrorMatcher struct { messageMatcher types.GomegaMatcher } @@ -119,7 +117,7 @@ type connectorErrorMatcher struct { } func (matcher *databaseErrorMatcher) Match(actual interface{}) (success bool, err error) { - databaseError, ok := actual.(*gobolt.DatabaseError) + databaseError, ok := actual.(gobolt.DatabaseError) if !ok { return false, nil } @@ -140,7 +138,7 @@ func (matcher *databaseErrorMatcher) Match(actual interface{}) (success bool, er } func (matcher *databaseErrorMatcher) FailureMessage(actual interface{}) (message string) { - databaseError, ok := actual.(*gobolt.DatabaseError) + databaseError, ok := actual.(gobolt.DatabaseError) if !ok { return fmt.Sprintf("Expected\n\t%#v\nto be a DatabaseError", actual) } @@ -161,7 +159,7 @@ func (matcher *databaseErrorMatcher) FailureMessage(actual interface{}) (message } func (matcher *databaseErrorMatcher) NegatedFailureMessage(actual interface{}) (message string) { - databaseError, ok := actual.(*gobolt.DatabaseError) + databaseError, ok := actual.(gobolt.DatabaseError) if !ok { return fmt.Sprintf("Expected\n\t%#v\nnot to be a DatabaseError", actual) } @@ -209,7 +207,7 @@ func (matcher *serviceUnavailableErrorMatcher) NegatedFailureMessage(actual inte } func (matcher *connectorErrorMatcher) Match(actual interface{}) (success bool, err error) { - connectorError, ok := actual.(*gobolt.ConnectorError) + connectorError, ok := actual.(gobolt.ConnectorError) if !ok { return false, nil } @@ -230,7 +228,7 @@ func (matcher *connectorErrorMatcher) Match(actual interface{}) (success bool, e } func (matcher *connectorErrorMatcher) FailureMessage(actual interface{}) (message string) { - connectorError, ok := actual.(*gobolt.ConnectorError) + connectorError, ok := actual.(gobolt.ConnectorError) if !ok { return fmt.Sprintf("Expected\n\t%#v\nto be a ConnectorError", actual) } @@ -251,7 +249,7 @@ func (matcher *connectorErrorMatcher) FailureMessage(actual interface{}) (messag } func (matcher *connectorErrorMatcher) NegatedFailureMessage(actual interface{}) (message string) { - connectorError, ok := actual.(*gobolt.ConnectorError) + connectorError, ok := actual.(gobolt.ConnectorError) if !ok { return fmt.Sprintf("Expected\n\t%#v\nnot to be a ConnectorError", actual) } @@ -271,40 +269,40 @@ func (matcher *connectorErrorMatcher) NegatedFailureMessage(actual interface{}) return fmt.Sprintf("Unexpected condition in matcher") } -func (matcher *driverErrorMatcher) Match(actual interface{}) (success bool, err error) { - err, ok := actual.(error) - if !ok || !neo4j.IsDriverError(err) { +func (matcher *genericErrorMatcher) Match(actual interface{}) (success bool, err error) { + genericError, ok := actual.(gobolt.GenericError) + if !ok { return false, nil } if matcher.messageMatcher != nil { - return matcher.messageMatcher.Match(err.Error()) + return matcher.messageMatcher.Match(genericError.Error()) } return true, nil } -func (matcher *driverErrorMatcher) FailureMessage(actual interface{}) (message string) { - err, ok := actual.(error) - if !ok || !neo4j.IsDriverError(err) { - return fmt.Sprintf("Expected\n\t%#v\nto be a DriverError", actual) +func (matcher *genericErrorMatcher) FailureMessage(actual interface{}) (message string) { + genericError, ok := actual.(gobolt.GenericError) + if !ok { + return fmt.Sprintf("Expected\n\t%#v\nto be a GenericError", actual) } if matcher.messageMatcher != nil { - return fmt.Sprintf("Expected\n\t%#v\nto have its description to match %s", actual, matcher.messageMatcher.FailureMessage(err.Error())) + return fmt.Sprintf("Expected\n\t%#v\nto have its description to match %s", actual, matcher.messageMatcher.FailureMessage(genericError.Error())) } return fmt.Sprintf("Unexpected condition in matcher") } -func (matcher *driverErrorMatcher) NegatedFailureMessage(actual interface{}) (message string) { - err, ok := actual.(error) - if !ok || !neo4j.IsDriverError(err) { - return fmt.Sprintf("Expected\n\t%#v\nnot to be a DriverError", actual) +func (matcher *genericErrorMatcher) NegatedFailureMessage(actual interface{}) (message string) { + genericError, ok := actual.(gobolt.GenericError) + if ok { + return fmt.Sprintf("Expected\n\t%#v\nnot to be a GenericError", actual) } if matcher.messageMatcher != nil { - return fmt.Sprintf("Expected\n\t%#v\nnot to have its description to match %s", actual, matcher.messageMatcher.FailureMessage(err.Error())) + return fmt.Sprintf("Expected\n\t%#v\nnot to have its description to match %s", actual, matcher.messageMatcher.NegatedFailureMessage(genericError.Error())) } return fmt.Sprintf("Unexpected condition in matcher") diff --git a/neo4j/values_graph_handlers.go b/neo4j/values_graph_handlers.go index 2f316974..ed8885cb 100644 --- a/neo4j/values_graph_handlers.go +++ b/neo4j/values_graph_handlers.go @@ -20,7 +20,6 @@ package neo4j import ( - "fmt" "reflect" "github.com/neo4j-drivers/gobolt" @@ -45,7 +44,7 @@ func (handler *nodeValueHandler) WritableTypes() []reflect.Type { func (handler *nodeValueHandler) Read(signature int8, values []interface{}) (interface{}, error) { if len(values) != 3 { - return nil, gobolt.NewValueHandlerError(fmt.Sprintf("expected node struct to have %d fields but received %d", 3, len(values))) + return nil, gobolt.NewValueHandlerError("expected node struct to have %d fields but received %d", 3, len(values)) } idValue := values[0].(int64) @@ -66,7 +65,7 @@ func (handler *nodeValueHandler) Read(signature int8, values []interface{}) (int } func (handler *nodeValueHandler) Write(value interface{}) (int8, []interface{}, error) { - return 0, nil, &gobolt.ValueHandlerNotSupportedError{} + return 0, nil, gobolt.NewValueHandlerError("Write is not supported for node values") } func (handler *relationshipValueHandler) ReadableStructs() []int8 { @@ -80,7 +79,7 @@ func (handler *relationshipValueHandler) WritableTypes() []reflect.Type { func (handler *relationshipValueHandler) Read(signature int8, values []interface{}) (interface{}, error) { if signature == 'R' { if len(values) != 5 { - return nil, gobolt.NewValueHandlerError(fmt.Sprintf("expected relationship struct to have %d fields but received %d", 5, len(values))) + return nil, gobolt.NewValueHandlerError("expected relationship struct to have %d fields but received %d", 5, len(values)) } idValue := values[0].(int64) @@ -96,27 +95,27 @@ func (handler *relationshipValueHandler) Read(signature int8, values []interface relType: relTypeValue, props: propsValue, }, nil - } else { - if len(values) != 3 { - return nil, gobolt.NewValueHandlerError(fmt.Sprintf("expected unbound relationship struct to have %d fields but received %d", 3, len(values))) - } - - idValue := values[0].(int64) - relTypeValue := values[1].(string) - propsValue := values[2].(map[string]interface{}) + } - return &relationshipValue{ - id: idValue, - startId: int64(-1), - endId: int64(-1), - relType: relTypeValue, - props: propsValue, - }, nil + if len(values) != 3 { + return nil, gobolt.NewValueHandlerError("expected unbound relationship struct to have %d fields but received %d", 3, len(values)) } + + idValue := values[0].(int64) + relTypeValue := values[1].(string) + propsValue := values[2].(map[string]interface{}) + + return &relationshipValue{ + id: idValue, + startId: int64(-1), + endId: int64(-1), + relType: relTypeValue, + props: propsValue, + }, nil } func (handler *relationshipValueHandler) Write(value interface{}) (int8, []interface{}, error) { - return 0, nil, &gobolt.ValueHandlerNotSupportedError{} + return 0, nil, gobolt.NewValueHandlerError("Write is not supported for relationship values") } func (handler *pathValueHandler) ReadableStructs() []int8 { @@ -129,7 +128,7 @@ func (handler *pathValueHandler) WritableTypes() []reflect.Type { func (handler *pathValueHandler) Read(signature int8, values []interface{}) (interface{}, error) { if len(values) != 3 { - return nil, gobolt.NewValueHandlerError(fmt.Sprintf("expected path struct to have %d fields but received %d", 3, len(values))) + return nil, gobolt.NewValueHandlerError("expected path struct to have %d fields but received %d", 3, len(values)) } uniqueNodesValue := values[0].([]interface{}) @@ -181,5 +180,5 @@ func (handler *pathValueHandler) Read(signature int8, values []interface{}) (int } func (handler *pathValueHandler) Write(value interface{}) (int8, []interface{}, error) { - return 0, nil, &gobolt.ValueHandlerNotSupportedError{} + return 0, nil, gobolt.NewValueHandlerError("Write is not supported for path values") } diff --git a/neo4j/values_spatial.go b/neo4j/values_spatial.go index 0f0560b1..80394016 100644 --- a/neo4j/values_spatial.go +++ b/neo4j/values_spatial.go @@ -80,7 +80,7 @@ func (point *Point) Z() float64 { func (point *Point) String() string { if point.dimension == 2 { return fmt.Sprintf("Point{srId=%d, x=%f, y=%f}", point.srId, point.x, point.y) - } else { - return fmt.Sprintf("Point{srId=%d, x=%f, y=%f, z=%f}", point.srId, point.x, point.y, point.z) } + + return fmt.Sprintf("Point{srId=%d, x=%f, y=%f, z=%f}", point.srId, point.x, point.y, point.z) } diff --git a/neo4j/values_spatial_handlers.go b/neo4j/values_spatial_handlers.go index 69c498c3..c0504379 100644 --- a/neo4j/values_spatial_handlers.go +++ b/neo4j/values_spatial_handlers.go @@ -20,7 +20,6 @@ package neo4j import ( - "fmt" "math" "reflect" @@ -55,7 +54,7 @@ func (handler *pointValueHandler) Read(signature int8, values []interface{}) (in switch signature { case point2DSignature: if len(values) != point2DSize { - return nil, gobolt.NewValueHandlerError(fmt.Sprintf("expected Point2D struct to have %d fields but received %d", point2DSize, len(values))) + return nil, gobolt.NewValueHandlerError("expected Point2D struct to have %d fields but received %d", point2DSize, len(values)) } dimension = 2 @@ -65,7 +64,7 @@ func (handler *pointValueHandler) Read(signature int8, values []interface{}) (in z = math.NaN() case point3DSignature: if len(values) != point3DSize { - return nil, gobolt.NewValueHandlerError(fmt.Sprintf("expected Point3D struct to have %d fields but received %d", point3DSize, len(values))) + return nil, gobolt.NewValueHandlerError("expected Point3D struct to have %d fields but received %d", point3DSize, len(values)) } dimension = 3 @@ -74,7 +73,7 @@ func (handler *pointValueHandler) Read(signature int8, values []interface{}) (in y = values[2].(float64) z = values[3].(float64) default: - return nil, gobolt.NewValueHandlerError(fmt.Sprintf("unexpected struct signature provided to PointValueHandler: %#x", signature)) + return nil, gobolt.NewValueHandlerError("unexpected struct signature provided to PointValueHandler: %#x", signature) } return &Point{ @@ -114,5 +113,5 @@ func (handler *pointValueHandler) Write(value interface{}) (int8, []interface{}, } } - return 0, nil, gobolt.NewValueHandlerError(fmt.Sprintf("passed in value %v is not supported by PointValueHandler", value)) + return 0, nil, gobolt.NewValueHandlerError("passed in value %v is not supported by PointValueHandler", value) } diff --git a/neo4j/values_temporal.go b/neo4j/values_temporal.go index ec9eb495..3376a64b 100644 --- a/neo4j/values_temporal.go +++ b/neo4j/values_temporal.go @@ -59,7 +59,7 @@ const ( ) var ( - epochUtc time.Time = time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC) + epochUtc = time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC) ) // DateOf creates a local date from the provided instant by extracting year, month and day fields. @@ -269,7 +269,7 @@ func (duration Duration) Nanos() int { func (duration Duration) String() string { sign := "" if duration.seconds < 0 && duration.nanos > 0 { - duration.seconds += 1 + duration.seconds++ duration.nanos = int(time.Second) - duration.nanos if duration.seconds == 0 { diff --git a/neo4j/values_temporal_handlers.go b/neo4j/values_temporal_handlers.go index baab0b7c..5738cb55 100644 --- a/neo4j/values_temporal_handlers.go +++ b/neo4j/values_temporal_handlers.go @@ -20,7 +20,6 @@ package neo4j import ( - "fmt" "reflect" "time" @@ -73,13 +72,13 @@ func (handler *dateValueHandler) Read(signature int8, values []interface{}) (int switch signature { case dateSignature: if len(values) != dateSize { - return nil, gobolt.NewValueHandlerError(fmt.Sprintf("expected date struct to have %d fields but received %d", dateSize, len(values))) + return nil, gobolt.NewValueHandlerError("expected date struct to have %d fields but received %d", dateSize, len(values)) } epochDays := values[0].(int64) return Date{epochDays}, nil } - return nil, gobolt.NewValueHandlerError(fmt.Sprintf("unexpected struct signature provided to dateTimeValueHandler: %#x", signature)) + return nil, gobolt.NewValueHandlerError("unexpected struct signature provided to dateTimeValueHandler: %#x", signature) } func (handler *dateValueHandler) Write(value interface{}) (int8, []interface{}, error) { @@ -87,7 +86,7 @@ func (handler *dateValueHandler) Write(value interface{}) (int8, []interface{}, var ok bool if date, ok = value.(Date); !ok { - return 0, nil, gobolt.NewValueHandlerError(fmt.Sprintf("passed in value %v is not supported by dateValueHandler", value)) + return 0, nil, gobolt.NewValueHandlerError("passed in value %v is not supported by dateValueHandler", value) } return dateSignature, []interface{}{date.epochDays}, nil @@ -105,13 +104,13 @@ func (handler *localTimeValueHandler) Read(signature int8, values []interface{}) switch signature { case localTimeSignature: if len(values) != localTimeSize { - return nil, gobolt.NewValueHandlerError(fmt.Sprintf("expected local time struct to have %d fields but received %d", localTimeSize, len(values))) + return nil, gobolt.NewValueHandlerError("expected local time struct to have %d fields but received %d", localTimeSize, len(values)) } nanosOfDay := values[0].(int64) return LocalTime{time.Duration(nanosOfDay)}, nil } - return nil, gobolt.NewValueHandlerError(fmt.Sprintf("unexpected struct signature provided to localTimeValueHandler: %#x", signature)) + return nil, gobolt.NewValueHandlerError("unexpected struct signature provided to localTimeValueHandler: %#x", signature) } func (handler *localTimeValueHandler) Write(value interface{}) (int8, []interface{}, error) { @@ -119,7 +118,7 @@ func (handler *localTimeValueHandler) Write(value interface{}) (int8, []interfac var ok bool if localTime, ok = value.(LocalTime); !ok { - return 0, nil, gobolt.NewValueHandlerError(fmt.Sprintf("passed in value %v is not supported by localTimeValueHandler", value)) + return 0, nil, gobolt.NewValueHandlerError("passed in value %v is not supported by localTimeValueHandler", value) } return localTimeSignature, []interface{}{int64(localTime.nanosOfDay)}, nil @@ -137,14 +136,14 @@ func (handler *offsetTimeValueHandler) Read(signature int8, values []interface{} switch signature { case offsetTimeSignature: if len(values) != offsetTimeSize { - return nil, gobolt.NewValueHandlerError(fmt.Sprintf("expected offset time struct to have %d fields but received %d", offsetTimeSize, len(values))) + return nil, gobolt.NewValueHandlerError("expected offset time struct to have %d fields but received %d", offsetTimeSize, len(values)) } nanosOfDay := values[0].(int64) offset := values[1].(int64) return OffsetTime{time.Duration(nanosOfDay), int(offset)}, nil } - return nil, gobolt.NewValueHandlerError(fmt.Sprintf("unexpected struct signature provided to offsetTimeValueHandler: %#x", signature)) + return nil, gobolt.NewValueHandlerError("unexpected struct signature provided to offsetTimeValueHandler: %#x", signature) } func (handler *offsetTimeValueHandler) Write(value interface{}) (int8, []interface{}, error) { @@ -152,7 +151,7 @@ func (handler *offsetTimeValueHandler) Write(value interface{}) (int8, []interfa var ok bool if offsetTime, ok = value.(OffsetTime); !ok { - return 0, nil, gobolt.NewValueHandlerError(fmt.Sprintf("passed in value %v is not supported by offsetTimeValueHandler", value)) + return 0, nil, gobolt.NewValueHandlerError("passed in value %v is not supported by offsetTimeValueHandler", value) } return offsetTimeSignature, []interface{}{int64(offsetTime.nanosOfDay), offsetTime.offset}, nil @@ -170,7 +169,7 @@ func (handler *durationValueHandler) Read(signature int8, values []interface{}) switch signature { case durationSignature: if len(values) != durationSize { - return nil, gobolt.NewValueHandlerError(fmt.Sprintf("expected duration struct to have %d fields but received %d", durationSize, len(values))) + return nil, gobolt.NewValueHandlerError("expected duration struct to have %d fields but received %d", durationSize, len(values)) } months := values[0].(int64) days := values[1].(int64) @@ -179,7 +178,7 @@ func (handler *durationValueHandler) Read(signature int8, values []interface{}) return Duration{months, days, seconds, int(nanos)}, nil } - return nil, gobolt.NewValueHandlerError(fmt.Sprintf("unexpected struct signature provided to durationValueHandler: %#x", signature)) + return nil, gobolt.NewValueHandlerError("unexpected struct signature provided to durationValueHandler: %#x", signature) } func (handler *durationValueHandler) Write(value interface{}) (int8, []interface{}, error) { @@ -187,7 +186,7 @@ func (handler *durationValueHandler) Write(value interface{}) (int8, []interface var ok bool if duration, ok = value.(Duration); !ok { - return 0, nil, gobolt.NewValueHandlerError(fmt.Sprintf("passed in value %v is not supported by durationValueHandler", value)) + return 0, nil, gobolt.NewValueHandlerError("passed in value %v is not supported by durationValueHandler", value) } return durationSignature, []interface{}{duration.months, duration.days, duration.seconds, duration.nanos}, nil @@ -205,7 +204,7 @@ func (handler *localDateTimeValueHandler) Read(signature int8, values []interfac switch signature { case localDateTimeSignature: if len(values) != localDateTimeSize { - return nil, gobolt.NewValueHandlerError(fmt.Sprintf("expected local date time struct to have %d fields but received %d", localDateTimeSize, len(values))) + return nil, gobolt.NewValueHandlerError("expected local date time struct to have %d fields but received %d", localDateTimeSize, len(values)) } sec := values[0].(int64) @@ -214,7 +213,7 @@ func (handler *localDateTimeValueHandler) Read(signature int8, values []interfac return LocalDateTime{sec, int(nsec)}, nil } - return nil, gobolt.NewValueHandlerError(fmt.Sprintf("unexpected struct signature provided to localDateTimeValueHandler: %#x", signature)) + return nil, gobolt.NewValueHandlerError("unexpected struct signature provided to localDateTimeValueHandler: %#x", signature) } func (handler *localDateTimeValueHandler) Write(value interface{}) (int8, []interface{}, error) { @@ -222,7 +221,7 @@ func (handler *localDateTimeValueHandler) Write(value interface{}) (int8, []inte var ok bool if localDateTime, ok = value.(LocalDateTime); !ok { - return 0, nil, gobolt.NewValueHandlerError(fmt.Sprintf("passed in value %v is not supported by localDateTimeValueHandler", value)) + return 0, nil, gobolt.NewValueHandlerError("passed in value %v is not supported by localDateTimeValueHandler", value) } return localDateTimeSignature, []interface{}{ @@ -243,7 +242,7 @@ func (handler *dateTimeValueHandler) Read(signature int8, values []interface{}) switch signature { case dateTimeWithZoneIdSignature: if len(values) != dateTimeSize { - return nil, gobolt.NewValueHandlerError(fmt.Sprintf("expected date time with zone id struct to have %d fields but received %d", dateTimeSize, len(values))) + return nil, gobolt.NewValueHandlerError("expected date time with zone id struct to have %d fields but received %d", dateTimeSize, len(values)) } sec := values[0].(int64) @@ -251,7 +250,7 @@ func (handler *dateTimeValueHandler) Read(signature int8, values []interface{}) zone := values[2].(string) location, err := time.LoadLocation(zone) if err != nil { - return nil, gobolt.NewValueHandlerError(fmt.Sprintf("Unable to load time zone '%s'", zone)) + return nil, gobolt.NewValueHandlerError("Unable to load time zone '%s'", zone) } utcTime := epochUtc.Add(time.Duration(sec)*time.Second + time.Duration(nsec)) @@ -259,7 +258,7 @@ func (handler *dateTimeValueHandler) Read(signature int8, values []interface{}) return time.Date(utcTime.Year(), utcTime.Month(), utcTime.Day(), utcTime.Hour(), utcTime.Minute(), utcTime.Second(), utcTime.Nanosecond(), location), nil case dateTimeWithOffsetSignature: if len(values) != dateTimeSize { - return nil, gobolt.NewValueHandlerError(fmt.Sprintf("expected date time with offset struct to have %d fields but received %d", dateTimeSize, len(values))) + return nil, gobolt.NewValueHandlerError("expected date time with offset struct to have %d fields but received %d", dateTimeSize, len(values)) } sec := values[0].(int64) @@ -272,7 +271,7 @@ func (handler *dateTimeValueHandler) Read(signature int8, values []interface{}) return time.Date(utcTime.Year(), utcTime.Month(), utcTime.Day(), utcTime.Hour(), utcTime.Minute(), utcTime.Second(), utcTime.Nanosecond(), location), nil } - return nil, gobolt.NewValueHandlerError(fmt.Sprintf("unexpected struct signature provided to dateTimeValueHandler: %#x", signature)) + return nil, gobolt.NewValueHandlerError("unexpected struct signature provided to dateTimeValueHandler: %#x", signature) } func (handler *dateTimeValueHandler) Write(value interface{}) (int8, []interface{}, error) { @@ -280,7 +279,7 @@ func (handler *dateTimeValueHandler) Write(value interface{}) (int8, []interface var ok bool if dateTime, ok = value.(time.Time); !ok { - return 0, nil, gobolt.NewValueHandlerError(fmt.Sprintf("passed in value %v is not supported by dateTimeValueHandler", value)) + return 0, nil, gobolt.NewValueHandlerError("passed in value %v is not supported by dateTimeValueHandler", value) } location := dateTime.Location() @@ -296,13 +295,13 @@ func (handler *dateTimeValueHandler) Write(value interface{}) (int8, []interface nsec, offset, }, nil - } else { - // with zone id - return dateTimeWithZoneIdSignature, []interface{}{ - sec, - nsec, - location.String(), - }, nil } + // with zone id + return dateTimeWithZoneIdSignature, []interface{}{ + sec, + nsec, + location.String(), + }, nil + }