parent
e4e9bdb08d
commit
e3e45542d1
@ -0,0 +1 @@
|
||||
package dorm
|
@ -1,30 +0,0 @@
|
||||
package dorm
|
||||
|
||||
import "github.com/neo4j/neo4j-go-driver/v4/neo4j"
|
||||
|
||||
type ConfigNeo4j4Client struct {
|
||||
Dns string
|
||||
Username string
|
||||
Password string
|
||||
realm string
|
||||
}
|
||||
|
||||
type Neo4j4Client struct {
|
||||
Db *neo4j.Driver // 驱动
|
||||
config *ConfigNeo4j4Client // 配置
|
||||
}
|
||||
|
||||
func NewNo44Client(config *ConfigNeo4j4Client) (*Neo4j4Client, error) {
|
||||
|
||||
c := &Neo4j4Client{config: config}
|
||||
|
||||
driver, err := neo4j.NewDriver(c.config.Dns, neo4j.BasicAuth(c.config.Username, c.config.Password, c.config.realm))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer driver.Close()
|
||||
|
||||
c.Db = &driver
|
||||
|
||||
return c, nil
|
||||
}
|
@ -1,33 +0,0 @@
|
||||
package dorm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/neo4j/neo4j-go-driver/v5/neo4j"
|
||||
)
|
||||
|
||||
type ConfigNeo4j5Client struct {
|
||||
Dns string
|
||||
Username string
|
||||
Password string
|
||||
realm string
|
||||
}
|
||||
|
||||
type Neo4j5Client struct {
|
||||
Db *neo4j.DriverWithContext // 驱动
|
||||
config *ConfigNeo4j5Client // 配置
|
||||
}
|
||||
|
||||
func NewNeo4j5Client(config *ConfigNeo4j5Client) (*Neo4j5Client, error) {
|
||||
|
||||
c := &Neo4j5Client{config: config}
|
||||
|
||||
driver, err := neo4j.NewDriverWithContext(c.config.Dns, neo4j.BasicAuth(c.config.Username, c.config.Password, c.config.realm))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer driver.Close(context.Background())
|
||||
|
||||
c.Db = &driver
|
||||
|
||||
return c, nil
|
||||
}
|
@ -1,201 +0,0 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
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.
|
@ -1,98 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) "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 (
|
||||
"time"
|
||||
|
||||
"github.com/neo4j/neo4j-go-driver/v4/neo4j/db"
|
||||
"github.com/neo4j/neo4j-go-driver/v4/neo4j/dbtype"
|
||||
)
|
||||
|
||||
// Aliases to simplify client usage (fewer imports) and to provide some backwards
|
||||
// compatibility with 1.x driver.
|
||||
//
|
||||
// A separate dbtype package is needed to avoid circular package references and to avoid
|
||||
// unnecessary copying/conversions between structs since serializing/deserializing is
|
||||
// handled within bolt package and bolt package is used from this package.
|
||||
type (
|
||||
Point2D = dbtype.Point2D
|
||||
Point3D = dbtype.Point3D
|
||||
Date = dbtype.Date
|
||||
LocalTime = dbtype.LocalTime
|
||||
LocalDateTime = dbtype.LocalDateTime
|
||||
Time = dbtype.Time
|
||||
OffsetTime = dbtype.Time
|
||||
Duration = dbtype.Duration
|
||||
Node = dbtype.Node
|
||||
Relationship = dbtype.Relationship
|
||||
Path = dbtype.Path
|
||||
Record = db.Record
|
||||
)
|
||||
|
||||
// DateOf creates a neo4j.Date from time.Time.
|
||||
// Hour, minute, second and nanoseconds are set to zero and location is set to UTC.
|
||||
//
|
||||
// Conversion can also be done by casting a time.Time to neo4j.Date but beware that time
|
||||
// components and location will be left as is but ignored when used as query parameter.
|
||||
func DateOf(t time.Time) Date {
|
||||
y, m, d := t.Date()
|
||||
t = time.Date(y, m, d, 0, 0, 0, 0, time.UTC)
|
||||
return dbtype.Date(t)
|
||||
}
|
||||
|
||||
// LocalTimeOf creates a neo4j.LocalTime from time.Time.
|
||||
// Year, month and day are set to zero and location is set to local.
|
||||
//
|
||||
// Conversion can also be done by casting a time.Time to neo4j.LocalTime but beware that date
|
||||
// components and location will be left as is but ignored when used as query parameter.
|
||||
func LocalTimeOf(t time.Time) LocalTime {
|
||||
t = time.Date(0, 0, 0, t.Hour(), t.Minute(), t.Second(), t.Nanosecond(), time.Local)
|
||||
return dbtype.LocalTime(t)
|
||||
}
|
||||
|
||||
// LocalDateTimeOf creates a neo4j.Local from time.Time.
|
||||
//
|
||||
// Conversion can also be done by casting a time.Time to neo4j.LocalTime but beware that location
|
||||
// will be left as is but interpreted as local when used as query parameter.
|
||||
func LocalDateTimeOf(t time.Time) LocalDateTime {
|
||||
t = time.Date(t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second(), t.Nanosecond(), time.Local)
|
||||
return dbtype.LocalDateTime(t)
|
||||
}
|
||||
|
||||
// OffsetTimeOf creates a neo4j.OffsetTime from time.Time.
|
||||
// Year, month and day are set to zero and location is set to "Offset" using zone offset from
|
||||
// time.Time.
|
||||
//
|
||||
// Conversion can also be done by casting a time.Time to neo4j.OffsetTime but beware that date
|
||||
// components and location will be left as is but ignored when used as query parameter. Since
|
||||
// location will contain the original value, the value "offset" will not be used by the driver
|
||||
// but the actual name of the location in time.Time and that offset.
|
||||
func OffsetTimeOf(t time.Time) OffsetTime {
|
||||
_, offset := t.Zone()
|
||||
l := time.FixedZone("Offset", int(offset))
|
||||
t = time.Date(0, 0, 0, t.Hour(), t.Minute(), t.Second(), t.Nanosecond(), l)
|
||||
return dbtype.Time(t)
|
||||
}
|
||||
|
||||
// DurationOf creates neo4j.Duration from specified time parts.
|
||||
func DurationOf(months, days, seconds int64, nanos int) Duration {
|
||||
return Duration{Months: months, Days: days, Seconds: seconds, Nanos: nanos}
|
||||
}
|
@ -1,104 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) "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
|
||||
|
||||
// AuthToken contains credentials to be sent over to the neo4j server.
|
||||
type AuthToken struct {
|
||||
tokens map[string]interface{}
|
||||
}
|
||||
|
||||
const keyScheme = "scheme"
|
||||
const schemeNone = "none"
|
||||
const schemeBasic = "basic"
|
||||
const schemeKerberos = "kerberos"
|
||||
const schemeBearer = "bearer"
|
||||
const keyPrincipal = "principal"
|
||||
const keyCredentials = "credentials"
|
||||
const keyRealm = "realm"
|
||||
|
||||
// NoAuth generates an empty authentication token
|
||||
func NoAuth() AuthToken {
|
||||
return AuthToken{tokens: map[string]interface{}{
|
||||
keyScheme: schemeNone,
|
||||
}}
|
||||
}
|
||||
|
||||
// BasicAuth generates a basic authentication token with provided username, password and realm
|
||||
func BasicAuth(username string, password string, realm string) AuthToken {
|
||||
tokens := map[string]interface{}{
|
||||
keyScheme: schemeBasic,
|
||||
keyPrincipal: username,
|
||||
keyCredentials: password,
|
||||
}
|
||||
|
||||
if realm != "" {
|
||||
tokens[keyRealm] = realm
|
||||
}
|
||||
|
||||
return AuthToken{tokens: tokens}
|
||||
}
|
||||
|
||||
// KerberosAuth generates a kerberos authentication token with provided base-64 encoded kerberos ticket
|
||||
func KerberosAuth(ticket string) AuthToken {
|
||||
token := AuthToken{
|
||||
tokens: map[string]interface{}{
|
||||
keyScheme: schemeKerberos,
|
||||
// Backwards compatibility: Neo4j servers pre 4.4 require the presence of the principal.
|
||||
keyPrincipal: "",
|
||||
keyCredentials: ticket,
|
||||
},
|
||||
}
|
||||
|
||||
return token
|
||||
}
|
||||
|
||||
// BearerAuth generates an authentication token with the provided base-64 value generated by a Single Sign-On provider
|
||||
func BearerAuth(token string) AuthToken {
|
||||
result := AuthToken{
|
||||
tokens: map[string]interface{}{
|
||||
keyScheme: schemeBearer,
|
||||
keyCredentials: token,
|
||||
},
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// CustomAuth generates a custom authentication token with provided parameters
|
||||
func CustomAuth(scheme string, username string, password string, realm string, parameters map[string]interface{}) AuthToken {
|
||||
tokens := map[string]interface{}{
|
||||
keyScheme: scheme,
|
||||
keyPrincipal: username,
|
||||
}
|
||||
|
||||
if password != "" {
|
||||
tokens[keyCredentials] = password
|
||||
}
|
||||
|
||||
if realm != "" {
|
||||
tokens[keyRealm] = realm
|
||||
}
|
||||
|
||||
if len(parameters) > 0 {
|
||||
tokens["parameters"] = parameters
|
||||
}
|
||||
|
||||
return AuthToken{tokens: tokens}
|
||||
}
|
@ -1,180 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) "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 (
|
||||
"crypto/x509"
|
||||
"math"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/neo4j/neo4j-go-driver/v4/neo4j/log"
|
||||
)
|
||||
|
||||
// A Config contains options that can be used to customize certain
|
||||
// aspects of the driver
|
||||
type Config struct {
|
||||
// RootCAs defines the set of certificate authorities that the driver trusts. If set
|
||||
// to nil, the driver uses hosts system certificates.
|
||||
//
|
||||
// The trusted certificates are used to validate connections for URI schemes 'bolt+s'
|
||||
// and 'neo4j+s'.
|
||||
RootCAs *x509.CertPool
|
||||
|
||||
// Logging target the driver will send its log outputs
|
||||
//
|
||||
// Possible to use custom logger (implement log.Logger interface) or
|
||||
// use neo4j.ConsoleLogger.
|
||||
//
|
||||
// default: No Op Logger (log.Void)
|
||||
Log log.Logger
|
||||
// Resolver that would be used to resolve initial router address. This may
|
||||
// be useful if you want to provide more than one URL for initial router.
|
||||
// If not specified, the URL provided to NewDriver is used as the initial
|
||||
// router.
|
||||
//
|
||||
// default: nil
|
||||
AddressResolver ServerAddressResolver
|
||||
// Maximum amount of time a retriable operation would continue retrying. It
|
||||
// cannot be specified as a negative value.
|
||||
//
|
||||
// default: 30 * time.Second
|
||||
MaxTransactionRetryTime time.Duration
|
||||
// Maximum number of connections per URL to allow on this driver. It
|
||||
// cannot be specified as 0 and negative values are interpreted as
|
||||
// math.MaxInt32.
|
||||
//
|
||||
// default: 100
|
||||
MaxConnectionPoolSize int
|
||||
// Maximum connection life time on pooled connections. Values less than
|
||||
// or equal to 0 disables the lifetime check.
|
||||
//
|
||||
// default: 1 * time.Hour
|
||||
MaxConnectionLifetime time.Duration
|
||||
// Maximum amount of time to either acquire an idle connection from the pool
|
||||
// or create a new connection (when the pool is not full). Negative values
|
||||
// result in an infinite wait time where 0 value results in no timeout which
|
||||
// results in immediate failure when there are no available connections.
|
||||
//
|
||||
// default: 1 * time.Minute
|
||||
ConnectionAcquisitionTimeout time.Duration
|
||||
// Connect timeout that will be set on underlying sockets. Values less than
|
||||
// or equal to 0 results in no timeout being applied.
|
||||
//
|
||||
// default: 5 * time.Second
|
||||
SocketConnectTimeout time.Duration
|
||||
// Whether to enable TCP keep alive on underlying sockets.
|
||||
//
|
||||
// default: true
|
||||
SocketKeepalive bool
|
||||
// Optionally override the user agent string sent to Neo4j server.
|
||||
//
|
||||
// default: neo4j.UserAgent
|
||||
UserAgent string
|
||||
// FetchSize defines how many records to pull from server in each batch.
|
||||
// From Bolt protocol v4 (Neo4j 4+) records can be fetched in batches as compared to fetching
|
||||
// all in previous versions.
|
||||
//
|
||||
// If FetchSize is set to FetchDefault, the driver decides the appropriate size. If set to a positive value
|
||||
// that size is used if the underlying protocol supports it otherwise it is ignored.
|
||||
//
|
||||
// To turn off fetching in batches and always fetch everything, set FetchSize to FetchAll.
|
||||
// If a single large result is to be retrieved this is the most performant setting.
|
||||
FetchSize int
|
||||
}
|
||||
|
||||
func defaultConfig() *Config {
|
||||
return &Config{
|
||||
AddressResolver: nil,
|
||||
MaxTransactionRetryTime: 30 * time.Second,
|
||||
MaxConnectionPoolSize: 100,
|
||||
MaxConnectionLifetime: 1 * time.Hour,
|
||||
ConnectionAcquisitionTimeout: 1 * time.Minute,
|
||||
SocketConnectTimeout: 5 * time.Second,
|
||||
SocketKeepalive: true,
|
||||
RootCAs: nil,
|
||||
UserAgent: UserAgent,
|
||||
FetchSize: FetchDefault,
|
||||
}
|
||||
}
|
||||
|
||||
func validateAndNormaliseConfig(config *Config) error {
|
||||
// Max Transaction Retry Time
|
||||
if config.MaxTransactionRetryTime < 0 {
|
||||
return &UsageError{Message: "Maximum transaction retry time cannot be smaller than 0"}
|
||||
}
|
||||
|
||||
// Max Connection Pool Size
|
||||
if config.MaxConnectionPoolSize == 0 {
|
||||
return &UsageError{Message: "Maximum connection pool cannot be 0"}
|
||||
}
|
||||
|
||||
if config.MaxConnectionPoolSize < 0 {
|
||||
config.MaxConnectionPoolSize = math.MaxInt32
|
||||
}
|
||||
|
||||
// Max Connection Lifetime
|
||||
if config.MaxConnectionLifetime < 0 {
|
||||
config.MaxConnectionLifetime = 0
|
||||
}
|
||||
|
||||
// Connection Acquisition Timeout
|
||||
if config.ConnectionAcquisitionTimeout < 0 {
|
||||
config.ConnectionAcquisitionTimeout = -1
|
||||
}
|
||||
|
||||
// Socket Connect Timeout
|
||||
if config.SocketConnectTimeout < 0 {
|
||||
config.SocketConnectTimeout = 0
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ServerAddress represents a host and port. Host can either be an IP address or a DNS name.
|
||||
// Both IPv4 and IPv6 hosts are supported.
|
||||
type ServerAddress interface {
|
||||
// Hostname returns the host portion of this ServerAddress.
|
||||
Hostname() string
|
||||
// Port returns the port portion of this ServerAddress.
|
||||
Port() string
|
||||
}
|
||||
|
||||
// ServerAddressResolver is a function type that defines the resolver function used by the routing driver to
|
||||
// resolve the initial address used to create the driver.
|
||||
type ServerAddressResolver func(address ServerAddress) []ServerAddress
|
||||
|
||||
func newServerAddressURL(hostname string, port string) *url.URL {
|
||||
if hostname == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
hostAndPort := hostname
|
||||
if port != "" {
|
||||
hostAndPort = hostAndPort + ":" + port
|
||||
}
|
||||
|
||||
return &url.URL{Host: hostAndPort}
|
||||
}
|
||||
|
||||
// NewServerAddress generates a ServerAddress with provided hostname and port information.
|
||||
func NewServerAddress(hostname string, port string) ServerAddress {
|
||||
return newServerAddressURL(hostname, port)
|
||||
}
|
@ -1,52 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) "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 (
|
||||
"github.com/neo4j/neo4j-go-driver/v4/neo4j/log"
|
||||
)
|
||||
|
||||
// LogLevel is the type that default logging implementations use for available
|
||||
// log levels
|
||||
type LogLevel int
|
||||
|
||||
const (
|
||||
// ERROR is the level that error messages are written
|
||||
ERROR LogLevel = 1
|
||||
// WARNING is the level that warning messages are written
|
||||
WARNING = 2
|
||||
// INFO is the level that info messages are written
|
||||
INFO = 3
|
||||
// DEBUG is the level that debug messages are written
|
||||
DEBUG = 4
|
||||
)
|
||||
|
||||
func ConsoleLogger(level LogLevel) *log.Console {
|
||||
return &log.Console{
|
||||
Errors: level >= ERROR,
|
||||
Warns: level >= WARNING,
|
||||
Infos: level >= INFO,
|
||||
Debugs: level >= DEBUG,
|
||||
}
|
||||
}
|
||||
|
||||
func ConsoleBoltLogger() *log.ConsoleBoltLogger {
|
||||
return &log.ConsoleBoltLogger{}
|
||||
}
|
@ -1,126 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) "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 db defines generic database functionality.
|
||||
package db
|
||||
|
||||
import (
|
||||
"github.com/neo4j/neo4j-go-driver/v4/neo4j/log"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Definitions of these should correspond to public API
|
||||
type AccessMode int
|
||||
|
||||
const (
|
||||
WriteMode AccessMode = 0
|
||||
ReadMode AccessMode = 1
|
||||
)
|
||||
|
||||
type (
|
||||
TxHandle uint64
|
||||
StreamHandle interface{}
|
||||
)
|
||||
|
||||
type Command struct {
|
||||
Cypher string
|
||||
Params map[string]interface{}
|
||||
FetchSize int
|
||||
}
|
||||
|
||||
type TxConfig struct {
|
||||
Mode AccessMode
|
||||
Bookmarks []string
|
||||
Timeout time.Duration
|
||||
ImpersonatedUser string
|
||||
Meta map[string]interface{}
|
||||
}
|
||||
|
||||
// Connection defines an abstract database server connection.
|
||||
type Connection interface {
|
||||
TxBegin(txConfig TxConfig) (TxHandle, error)
|
||||
TxRollback(tx TxHandle) error
|
||||
TxCommit(tx TxHandle) error
|
||||
Run(cmd Command, txConfig TxConfig) (StreamHandle, error)
|
||||
RunTx(tx TxHandle, cmd Command) (StreamHandle, error)
|
||||
// Keys for the specified stream.
|
||||
Keys(streamHandle StreamHandle) ([]string, error)
|
||||
// Moves to next item in the stream.
|
||||
// If error is nil, either Record or Summary has a value, if Record is nil there are no more records.
|
||||
// If error is non nil, neither Record or Summary has a value.
|
||||
Next(streamHandle StreamHandle) (*Record, *Summary, error)
|
||||
// Discards all records on the stream and returns the summary otherwise it will return the error.
|
||||
Consume(streamHandle StreamHandle) (*Summary, error)
|
||||
// Buffers all records on the stream, records, summary and error will be received through call to Next
|
||||
// The Connection implementation should preserve/buffer streams automatically if needed when new
|
||||
// streams are created and the server doesn't support multiple streams. Use Buffer to force
|
||||
// buffering before calling Reset to get all records and the bookmark.
|
||||
Buffer(streamHandle StreamHandle) error
|
||||
// Returns bookmark from last committed transaction or last finished auto-commit transaction.
|
||||
// Note that if there is an ongoing auto-commit transaction (stream active) the bookmark
|
||||
// from that is not included, use Buffer or Consume to end the stream with a bookmark.
|
||||
// Empty string if no bookmark.
|
||||
Bookmark() string
|
||||
// Returns name of the remote server
|
||||
ServerName() string
|
||||
// Returns server version on pattern Neo4j/1.2.3
|
||||
ServerVersion() string
|
||||
// Returns true if the connection is fully functional.
|
||||
// Implementation of this should be passive, no pinging or similair since it might be
|
||||
// called rather frequently.
|
||||
IsAlive() bool
|
||||
// Returns the point in time when this connection was established.
|
||||
Birthdate() time.Time
|
||||
// Resets connection to same state as directly after a connect.
|
||||
// Active streams will be discarded and the bookmark will be lost.
|
||||
Reset()
|
||||
ForceReset() error
|
||||
// Closes the database connection as well as any underlying connection.
|
||||
// The instance should not be used after being closed.
|
||||
Close()
|
||||
// Gets routing table for specified database name or the default database if
|
||||
// database equals DefaultDatabase. If the underlying connection does not support
|
||||
// multiple databases, DefaultDatabase should be used as database.
|
||||
// If user impersonation is used (impersonatedUser != "") and default database is used
|
||||
// the database name in the returned routing table will contain the actual name of the
|
||||
// configured default database for the impersonated user. If no impersonation is used
|
||||
// database name in routing table will be set to the name of the requested database.
|
||||
GetRoutingTable(context map[string]string, bookmarks []string, database, impersonatedUser string) (*RoutingTable, error)
|
||||
// Sets Bolt message logger on already initialized connections
|
||||
SetBoltLogger(boltLogger log.BoltLogger)
|
||||
}
|
||||
|
||||
type RoutingTable struct {
|
||||
TimeToLive int
|
||||
DatabaseName string
|
||||
Routers []string
|
||||
Readers []string
|
||||
Writers []string
|
||||
}
|
||||
|
||||
// Marker for using the default database instance.
|
||||
const DefaultDatabase = ""
|
||||
|
||||
// If database server connection supports selecting which database instance on the server
|
||||
// to connect to. Prior to Neo4j 4 there was only one database per server.
|
||||
type DatabaseSelector interface {
|
||||
// Should be called immediately after Reset. Not allowed to call multiple times with different
|
||||
// databases without a reset inbetween.
|
||||
SelectDatabase(database string)
|
||||
}
|
@ -1,134 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) "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 db
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Database server failed to fulfill request.
|
||||
type Neo4jError struct {
|
||||
Code string
|
||||
Msg string
|
||||
parsed bool
|
||||
classification string
|
||||
category string
|
||||
title string
|
||||
}
|
||||
|
||||
func (e *Neo4jError) Error() string {
|
||||
return fmt.Sprintf("Neo4jError: %s (%s)", e.Code, e.Msg)
|
||||
}
|
||||
|
||||
func (e *Neo4jError) Classification() string {
|
||||
e.parse()
|
||||
return e.classification
|
||||
}
|
||||
|
||||
func (e *Neo4jError) Category() string {
|
||||
e.parse()
|
||||
return e.category
|
||||
}
|
||||
|
||||
func (e *Neo4jError) Title() string {
|
||||
e.parse()
|
||||
return e.title
|
||||
}
|
||||
|
||||
// parse parses code from Neo4j into usable parts.
|
||||
// Code Neo.ClientError.General.ForbiddenReadOnlyDatabase is split into:
|
||||
// Classification: ClientError
|
||||
// Category: General
|
||||
// Title: ForbiddernReadOnlyDatabase
|
||||
func (e *Neo4jError) parse() {
|
||||
if e.parsed {
|
||||
return
|
||||
}
|
||||
e.parsed = true
|
||||
parts := strings.Split(e.Code, ".")
|
||||
if len(parts) != 4 {
|
||||
return
|
||||
}
|
||||
e.classification = parts[1]
|
||||
e.category = parts[2]
|
||||
e.title = parts[3]
|
||||
}
|
||||
|
||||
func (e *Neo4jError) IsAuthenticationFailed() bool {
|
||||
return e.Code == "Neo.ClientError.Security.Unauthorized"
|
||||
}
|
||||
|
||||
func (e *Neo4jError) IsRetriableTransient() bool {
|
||||
e.parse()
|
||||
if e.classification != "TransientError" {
|
||||
return false
|
||||
}
|
||||
switch e.Code {
|
||||
// Happens when client aborts transaction, should not retry
|
||||
case "Neo.TransientError.Transaction.Terminated", "Neo.TransientError.Transaction.LockClientStopped":
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (e *Neo4jError) IsRetriableCluster() bool {
|
||||
switch e.Code {
|
||||
case "Neo.ClientError.Cluster.NotALeader", "Neo.ClientError.General.ForbiddenOnReadOnlyDatabase":
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type FeatureNotSupportedError struct {
|
||||
Server string
|
||||
Feature string
|
||||
Reason string
|
||||
}
|
||||
|
||||
func (e *FeatureNotSupportedError) Error() string {
|
||||
return fmt.Sprintf("Server %s does not support: %s (%s)", e.Server, e.Feature, e.Reason)
|
||||
}
|
||||
|
||||
type UnsupportedTypeError struct {
|
||||
Type reflect.Type
|
||||
}
|
||||
|
||||
func (e *UnsupportedTypeError) Error() string {
|
||||
return fmt.Sprintf("Usage of type '%s' is not supported", e.Type.String())
|
||||
}
|
||||
|
||||
type ProtocolError struct {
|
||||
MessageType string
|
||||
Field string
|
||||
Err string
|
||||
}
|
||||
|
||||
func (e *ProtocolError) Error() string {
|
||||
if e.MessageType == "" {
|
||||
return fmt.Sprintf("ProtocolError: %s", e.Err)
|
||||
}
|
||||
if e.Field == "" {
|
||||
return fmt.Sprintf("ProtocolError: message %s could not be hydrated: %s", e.MessageType, e.Err)
|
||||
}
|
||||
return fmt.Sprintf("ProtocolError: field %s of message %s could not be hydrated: %s",
|
||||
e.Field, e.MessageType, e.Err)
|
||||
}
|
@ -1,51 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) "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 db
|
||||
|
||||
type Record struct {
|
||||
// Values contains all the values in the record.
|
||||
Values []interface{}
|
||||
// Keys contains names of the values in the record.
|
||||
// Should not be modified. Same instance is used for all records within the same result.
|
||||
Keys []string
|
||||
}
|
||||
|
||||
// Get returns the value corresponding to the given key along with a boolean that is true if
|
||||
// a value was found and false if there were no key with the given name.
|
||||
//
|
||||
// If there are a lot of keys in combination with a lot of records to iterate, consider to retrieve
|
||||
// values from Values slice directly or make a key -> index map before iterating. This implementation
|
||||
// does not make or use a key -> index map since the overhead of making the map might not be beneficial
|
||||
// for small and few records.
|
||||
func (r Record) Get(key string) (interface{}, bool) {
|
||||
for i, ckey := range r.Keys {
|
||||
if key == ckey {
|
||||
return r.Values[i], true
|
||||
}
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// GetByIndex returns the value in the record at the specified index.
|
||||
//
|
||||
// Deprecated: Prefer to access Values directly instead.
|
||||
func (r Record) GetByIndex(i int) interface{} {
|
||||
return r.Values[i]
|
||||
}
|
@ -1,142 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) "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 db
|
||||
|
||||
// Definitions of these should correspond to public API
|
||||
type StatementType int
|
||||
|
||||
const (
|
||||
StatementTypeUnknown StatementType = 0
|
||||
StatementTypeRead StatementType = 1
|
||||
StatementTypeReadWrite StatementType = 2
|
||||
StatementTypeWrite StatementType = 3
|
||||
StatementTypeSchemaWrite StatementType = 4
|
||||
)
|
||||
|
||||
// Counter key names
|
||||
const (
|
||||
NodesCreated = "nodes-created"
|
||||
NodesDeleted = "nodes-deleted"
|
||||
RelationshipsCreated = "relationships-created"
|
||||
RelationshipsDeleted = "relationships-deleted"
|
||||
PropertiesSet = "properties-set"
|
||||
LabelsAdded = "labels-added"
|
||||
LabelsRemoved = "labels-removed"
|
||||
IndexesAdded = "indexes-added"
|
||||
IndexesRemoved = "indexes-removed"
|
||||
ConstraintsAdded = "constraints-added"
|
||||
ConstraintsRemoved = "constraints-removed"
|
||||
SystemUpdates = "system-updates"
|
||||
)
|
||||
|
||||
// Plan describes the actual plan that the database planner produced and used (or will use) to execute your statement.
|
||||
// This can be extremely helpful in understanding what a statement is doing, and how to optimize it. For more details,
|
||||
// see the Neo4j Manual. The plan for the statement is a tree of plans - each sub-tree containing zero or more child
|
||||
// plans. The statement starts with the root plan. Each sub-plan is of a specific operator, which describes what
|
||||
// that part of the plan does - for instance, perform an index lookup or filter results.
|
||||
// The Neo4j Manual contains a reference of the available operator types, and these may differ across Neo4j versions.
|
||||
type Plan struct {
|
||||
// Operator is the operation this plan is performing.
|
||||
Operator string
|
||||
// Arguments for the operator.
|
||||
// Many operators have arguments defining their specific behavior. This map contains those arguments.
|
||||
Arguments map[string]interface{}
|
||||
// List of identifiers used by this plan. Identifiers used by this part of the plan.
|
||||
// These can be both identifiers introduced by you, or automatically generated.
|
||||
Identifiers []string
|
||||
// Zero or more child plans. A plan is a tree, where each child is another plan.
|
||||
// The children are where this part of the plan gets its input records - unless this is an operator that
|
||||
// introduces new records on its own.
|
||||
Children []Plan
|
||||
}
|
||||
|
||||
// ProfiledPlan is the same as a regular Plan - except this plan has been executed, meaning it also
|
||||
// contains detailed information about how much work each step of the plan incurred on the database.
|
||||
type ProfiledPlan struct {
|
||||
// Operator contains the operation this plan is performing.
|
||||
Operator string
|
||||
// Arguments contains the arguments for the operator used.
|
||||
// Many operators have arguments defining their specific behavior. This map contains those arguments.
|
||||
Arguments map[string]interface{}
|
||||
// Identifiers contains a list of identifiers used by this plan. Identifiers used by this part of the plan.
|
||||
// These can be both identifiers introduced by you, or automatically generated.
|
||||
Identifiers []string
|
||||
// DbHits contains the number of times this part of the plan touched the underlying data stores/
|
||||
DbHits int64
|
||||
// Records contains the number of records this part of the plan produced.
|
||||
Records int64
|
||||
// Children contains zero or more child plans. A plan is a tree, where each child is another plan.
|
||||
// The children are where this part of the plan gets its input records - unless this is an operator that
|
||||
// introduces new records on its own.
|
||||
Children []ProfiledPlan
|
||||
PageCacheMisses int64
|
||||
PageCacheHits int64
|
||||
PageCacheHitRatio float64
|
||||
Time int64
|
||||
}
|
||||
|
||||
// Notification represents notifications generated when executing a statement.
|
||||
// A notification can be visualized in a client pinpointing problems or other information about the statement.
|
||||
type Notification struct {
|
||||
// Code contains a notification code for the discovered issue of this notification.
|
||||
Code string
|
||||
// Title contains a short summary of this notification.
|
||||
Title string
|
||||
// Description contains a longer description of this notification.
|
||||
Description string
|
||||
// Position contains the position in the statement where this notification points to.
|
||||
// Not all notifications have a unique position to point to and in that case the position would be set to nil.
|
||||
Position *InputPosition
|
||||
// Severity contains the severity level of this notification.
|
||||
Severity string
|
||||
}
|
||||
|
||||
// InputPosition contains information about a specific position in a statement
|
||||
type InputPosition struct {
|
||||
// Offset contains the character offset referred to by this position; offset numbers start at 0.
|
||||
Offset int
|
||||
// Line contains the line number referred to by this position; line numbers start at 1.
|
||||
Line int
|
||||
// Column contains the column number referred to by this position; column numbers start at 1.
|
||||
Column int
|
||||
}
|
||||
|
||||
type ProtocolVersion struct {
|
||||
Major int
|
||||
Minor int
|
||||
}
|
||||
|
||||
type Summary struct {
|
||||
Bookmark string
|
||||
StmntType StatementType
|
||||
ServerName string
|
||||
Agent string
|
||||
Major int
|
||||
Minor int
|
||||
Counters map[string]int
|
||||
TFirst int64
|
||||
TLast int64
|
||||
Plan *Plan
|
||||
ProfiledPlan *ProfiledPlan
|
||||
Notifications []Notification
|
||||
Database string
|
||||
ContainsSystemUpdates *bool
|
||||
ContainsUpdates *bool
|
||||
}
|
@ -1,46 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) "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 dbtype contains definitions of supported database types.
|
||||
package dbtype
|
||||
|
||||
// Node represents a node in the neo4j graph database
|
||||
type Node struct {
|
||||
Id int64 // Id of this node.
|
||||
Labels []string // Labels attached to this Node.
|
||||
Props map[string]interface{} // Properties of this Node.
|
||||
}
|
||||
|
||||
// Relationship represents a relationship in the neo4j graph database
|
||||
type Relationship struct {
|
||||
Id int64 // Identity of this Relationship.
|
||||
StartId int64 // Identity of the start node of this Relationship.
|
||||
EndId int64 // Identity of the end node of this Relationship.
|
||||
Type string // Type of this Relationship.
|
||||
Props map[string]interface{} // Properties of this Relationship.
|
||||
}
|
||||
|
||||
// Path represents a directed sequence of relationships between two nodes.
|
||||
// This generally represents a traversal or walk through a graph and maintains a direction separate from that of any
|
||||
// relationships traversed. It is allowed to be of size 0, meaning there are no relationships in it. In this case,
|
||||
// it contains only a single node which is both the start and the end of the path.
|
||||
type Path struct {
|
||||
Nodes []Node // All the nodes in the path.
|
||||
Relationships []Relationship
|
||||
}
|
@ -1,49 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) "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 dbtype
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Point2D represents a two dimensional point in a particular coordinate reference system.
|
||||
type Point2D struct {
|
||||
X float64
|
||||
Y float64
|
||||
SpatialRefId uint32 // Id of coordinate reference system.
|
||||
}
|
||||
|
||||
// Point3D represents a three dimensional point in a particular coordinate reference system.
|
||||
type Point3D struct {
|
||||
X float64
|
||||
Y float64
|
||||
Z float64
|
||||
SpatialRefId uint32 // Id of coordinate reference system.
|
||||
}
|
||||
|
||||
// String returns string representation of this point.
|
||||
func (p Point2D) String() string {
|
||||
return fmt.Sprintf("Point{srId=%d, x=%f, y=%f}", p.SpatialRefId, p.X, p.Y)
|
||||
}
|
||||
|
||||
// String returns string representation of this point.
|
||||
func (p Point3D) String() string {
|
||||
return fmt.Sprintf("Point{srId=%d, x=%f, y=%f, z=%f}", p.SpatialRefId, p.X, p.Y, p.Z)
|
||||
}
|
@ -1,89 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) "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 dbtype
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Cypher DateTime corresponds to Go time.Time
|
||||
|
||||
type (
|
||||
Time time.Time // Time since start of day with timezone information
|
||||
Date time.Time // Date value, without a time zone and time related components.
|
||||
LocalTime time.Time // Time since start of day in local timezone
|
||||
LocalDateTime time.Time // Date and time in local timezone
|
||||
)
|
||||
|
||||
// Time casts LocalDateTime to time.Time
|
||||
func (t LocalDateTime) Time() time.Time {
|
||||
return time.Time(t)
|
||||
}
|
||||
|
||||
// Time casts LocalTime to time.Time
|
||||
func (t LocalTime) Time() time.Time {
|
||||
return time.Time(t)
|
||||
}
|
||||
|
||||
// Time casts Date to time.Time
|
||||
func (t Date) Time() time.Time {
|
||||
return time.Time(t)
|
||||
}
|
||||
|
||||
// Time casts Time to time.Time
|
||||
func (t Time) Time() time.Time {
|
||||
return time.Time(t)
|
||||
}
|
||||
|
||||
// Duration represents temporal amount containing months, days, seconds and nanoseconds.
|
||||
// Supports longer durations than time.Duration
|
||||
type Duration struct {
|
||||
Months int64
|
||||
Days int64
|
||||
Seconds int64
|
||||
Nanos int
|
||||
}
|
||||
|
||||
// String returns the string representation of this Duration in ISO-8601 compliant form.
|
||||
func (d Duration) String() string {
|
||||
sign := ""
|
||||
if d.Seconds < 0 && d.Nanos > 0 {
|
||||
d.Seconds++
|
||||
d.Nanos = int(time.Second) - d.Nanos
|
||||
|
||||
if d.Seconds == 0 {
|
||||
sign = "-"
|
||||
}
|
||||
}
|
||||
|
||||
timePart := ""
|
||||
if d.Nanos == 0 {
|
||||
timePart = fmt.Sprintf("%s%d", sign, d.Seconds)
|
||||
} else {
|
||||
timePart = fmt.Sprintf("%s%d.%09d", sign, d.Seconds, d.Nanos)
|
||||
}
|
||||
|
||||
return fmt.Sprintf("P%dM%dDT%sS", d.Months, d.Days, timePart)
|
||||
}
|
||||
|
||||
func (d1 Duration) Equal(d2 Duration) bool {
|
||||
return d1.Months == d2.Months && d1.Days == d2.Days && d1.Seconds == d2.Seconds && d1.Nanos == d2.Nanos
|
||||
}
|
@ -1,49 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) "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 (
|
||||
"context"
|
||||
"github.com/neo4j/neo4j-go-driver/v4/neo4j/db"
|
||||
"github.com/neo4j/neo4j-go-driver/v4/neo4j/log"
|
||||
)
|
||||
|
||||
// A router implementation that never routes
|
||||
type directRouter struct {
|
||||
address string
|
||||
}
|
||||
|
||||
func (r *directRouter) Readers(ctx context.Context, bookmarks []string, database string, boltLogger log.BoltLogger) ([]string, error) {
|
||||
return []string{r.address}, nil
|
||||
}
|
||||
|
||||
func (r *directRouter) Writers(ctx context.Context, bookmarks []string, database string, boltLogger log.BoltLogger) ([]string, error) {
|
||||
return []string{r.address}, nil
|
||||
}
|
||||
|
||||
func (r *directRouter) GetNameOfDefaultDatabase(ctx context.Context, bookmarks []string, user string, boltLogger log.BoltLogger) (string, error) {
|
||||
return db.DefaultDatabase, nil
|
||||
}
|
||||
|
||||
func (r *directRouter) Invalidate(database string) {
|
||||
}
|
||||
|
||||
func (r *directRouter) CleanUp() {
|
||||
}
|
@ -1,305 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) "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 provides required functionality to connect and execute statements against a Neo4j Database.
|
||||
package neo4j
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/neo4j/neo4j-go-driver/v4/neo4j/db"
|
||||
"github.com/neo4j/neo4j-go-driver/v4/neo4j/log"
|
||||
|
||||
"github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/connector"
|
||||
"github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/pool"
|
||||
"github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/router"
|
||||
)
|
||||
|
||||
// AccessMode defines modes that routing driver decides to which cluster member
|
||||
// a connection should be opened.
|
||||
type AccessMode int
|
||||
|
||||
const (
|
||||
// AccessModeWrite tells the driver to use a connection to 'Leader'
|
||||
AccessModeWrite AccessMode = 0
|
||||
// AccessModeRead tells the driver to use a connection to one of the 'Follower' or 'Read Replica'.
|
||||
AccessModeRead AccessMode = 1
|
||||
)
|
||||
|
||||
// Driver represents a pool(s) of connections to a neo4j server or cluster. It's
|
||||
// safe for concurrent use.
|
||||
type Driver interface {
|
||||
// The url this driver is bootstrapped
|
||||
Target() url.URL
|
||||
// Creates a new session based on the specified session configuration.
|
||||
NewSession(config SessionConfig) Session
|
||||
// Deprecated: Use NewSession instead
|
||||
Session(accessMode AccessMode, bookmarks ...string) (Session, error)
|
||||
// Verifies that the driver can connect to a remote server or cluster by
|
||||
// establishing a network connection with the remote. Returns nil if succesful
|
||||
// or error describing the problem.
|
||||
VerifyConnectivity() error
|
||||
// Close the driver and all underlying connections
|
||||
Close() error
|
||||
}
|
||||
|
||||
// NewDriver is the entry point to the neo4j driver to create an instance of a Driver. It is the first function to
|
||||
// be called in order to establish a connection to a neo4j database. It requires a Bolt URI and an authentication
|
||||
// token as parameters and can also take optional configuration function(s) as variadic parameters.
|
||||
//
|
||||
// In order to connect to a single instance database, you need to pass a URI with scheme 'bolt', 'bolt+s' or 'bolt+ssc'.
|
||||
// driver, err = NewDriver("bolt://db.server:7687", BasicAuth(username, password))
|
||||
//
|
||||
// In order to connect to a causal cluster database, you need to pass a URI with scheme 'neo4j', 'neo4j+s' or 'neo4j+ssc'
|
||||
// and its host part set to be one of the core cluster members.
|
||||
// driver, err = NewDriver("neo4j://core.db.server:7687", BasicAuth(username, password))
|
||||
//
|
||||
// You can override default configuration options by providing a configuration function(s)
|
||||
// driver, err = NewDriver(uri, BasicAuth(username, password), function (config *Config) {
|
||||
// config.MaxConnectionPoolSize = 10
|
||||
// })
|
||||
func NewDriver(target string, auth AuthToken, configurers ...func(*Config)) (Driver, error) {
|
||||
parsed, err := url.Parse(target)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
d := driver{target: parsed}
|
||||
|
||||
routing := true
|
||||
d.connector.Network = "tcp"
|
||||
address := parsed.Host
|
||||
switch parsed.Scheme {
|
||||
case "bolt":
|
||||
routing = false
|
||||
d.connector.SkipEncryption = true
|
||||
case "bolt+unix":
|
||||
// bolt+unix://<path to socket>
|
||||
routing = false
|
||||
d.connector.SkipEncryption = true
|
||||
d.connector.Network = "unix"
|
||||
if parsed.Host != "" {
|
||||
return nil, &UsageError{
|
||||
Message: fmt.Sprintf("Host part should be empty for scheme %s", parsed.Scheme),
|
||||
}
|
||||
}
|
||||
address = parsed.Path
|
||||
case "bolt+s":
|
||||
routing = false
|
||||
case "bolt+ssc":
|
||||
d.connector.SkipVerify = true
|
||||
routing = false
|
||||
case "neo4j":
|
||||
d.connector.SkipEncryption = true
|
||||
case "neo4j+ssc":
|
||||
d.connector.SkipVerify = true
|
||||
case "neo4j+s":
|
||||
default:
|
||||
return nil, &UsageError{
|
||||
Message: fmt.Sprintf("URI scheme %s is not supported", parsed.Scheme),
|
||||
}
|
||||
}
|
||||
|
||||
if parsed.Host != "" && parsed.Port() == "" {
|
||||
address += ":7687"
|
||||
parsed.Host = address
|
||||
}
|
||||
|
||||
if !routing && len(parsed.RawQuery) > 0 {
|
||||
return nil, &UsageError{
|
||||
Message: fmt.Sprintf("Routing context is not supported for URL scheme %s", parsed.Scheme),
|
||||
}
|
||||
}
|
||||
|
||||
// Apply client hooks for setting up configuration
|
||||
d.config = defaultConfig()
|
||||
for _, configurer := range configurers {
|
||||
configurer(d.config)
|
||||
}
|
||||
if err := validateAndNormaliseConfig(d.config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Setup logging
|
||||
d.log = d.config.Log
|
||||
if d.log == nil {
|
||||
// Default to void logger
|
||||
d.log = &log.Void{}
|
||||
}
|
||||
d.logId = log.NewId()
|
||||
|
||||
routingContext, err := routingContextFromUrl(routing, parsed)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Continue to setup connector
|
||||
d.connector.DialTimeout = d.config.SocketConnectTimeout
|
||||
d.connector.SocketKeepAlive = d.config.SocketKeepalive
|
||||
d.connector.UserAgent = d.config.UserAgent
|
||||
d.connector.RootCAs = d.config.RootCAs
|
||||
d.connector.Log = d.log
|
||||
d.connector.Auth = auth.tokens
|
||||
d.connector.RoutingContext = routingContext
|
||||
|
||||
// Let the pool use the same logid as the driver to simplify log reading.
|
||||
d.pool = pool.New(d.config.MaxConnectionPoolSize, d.config.MaxConnectionLifetime, d.connector.Connect, d.log, d.logId)
|
||||
|
||||
if !routing {
|
||||
d.router = &directRouter{address: address}
|
||||
} else {
|
||||
var routersResolver func() []string
|
||||
addressResolverHook := d.config.AddressResolver
|
||||
if addressResolverHook != nil {
|
||||
routersResolver = func() []string {
|
||||
addresses := addressResolverHook(parsed)
|
||||
servers := make([]string, len(addresses))
|
||||
for i, a := range addresses {
|
||||
servers[i] = fmt.Sprintf("%s:%s", a.Hostname(), a.Port())
|
||||
}
|
||||
return servers
|
||||
}
|
||||
}
|
||||
// Let the router use the same logid as the driver to simplify log reading.
|
||||
d.router = router.New(address, routersResolver, routingContext, d.pool, d.log, d.logId)
|
||||
}
|
||||
|
||||
d.log.Infof(log.Driver, d.logId, "Created { target: %s }", address)
|
||||
return &d, nil
|
||||
}
|
||||
|
||||
const routingcontext_address_key = "address"
|
||||
|
||||
func routingContextFromUrl(useRouting bool, u *url.URL) (map[string]string, error) {
|
||||
if !useRouting {
|
||||
return nil, nil
|
||||
}
|
||||
queryValues := u.Query()
|
||||
routingContext := make(map[string]string, len(queryValues)+1 /*For address*/)
|
||||
for k, vs := range queryValues {
|
||||
if len(vs) > 1 {
|
||||
return nil, &UsageError{
|
||||
Message: fmt.Sprintf("Duplicated routing context key '%s'", k),
|
||||
}
|
||||
}
|
||||
if len(vs) == 0 {
|
||||
return nil, &UsageError{
|
||||
Message: fmt.Sprintf("Empty routing context key '%s'", k),
|
||||
}
|
||||
}
|
||||
v := vs[0]
|
||||
v = strings.TrimSpace(v)
|
||||
if len(v) == 0 {
|
||||
return nil, &UsageError{
|
||||
Message: fmt.Sprintf("Empty routing context value for key '%s'", k),
|
||||
}
|
||||
}
|
||||
if k == routingcontext_address_key {
|
||||
return nil, &UsageError{Message: fmt.Sprintf("Illegal key '%s' for routing context", k)}
|
||||
}
|
||||
routingContext[k] = v
|
||||
}
|
||||
routingContext[routingcontext_address_key] = u.Host
|
||||
return routingContext, nil
|
||||
}
|
||||
|
||||
type sessionRouter interface {
|
||||
// Returns list of servers that can serve reads on the requested database.
|
||||
Readers(ctx context.Context, bookmarks []string, database string, boltLogger log.BoltLogger) ([]string, error)
|
||||
// Returns list of servers that can serve writes on the requested database.
|
||||
Writers(ctx context.Context, bookmarks []string, database string, boltLogger log.BoltLogger) ([]string, error)
|
||||
// Returns name of default database for specified user. The correct database name is needed when
|
||||
// requesting readers or writers.
|
||||
GetNameOfDefaultDatabase(ctx context.Context, bookmarks []string, user string, boltLogger log.BoltLogger) (string, error)
|
||||
Invalidate(database string)
|
||||
CleanUp()
|
||||
}
|
||||
|
||||
type driver struct {
|
||||
target *url.URL
|
||||
config *Config
|
||||
pool *pool.Pool
|
||||
mut sync.Mutex
|
||||
connector connector.Connector
|
||||
router sessionRouter
|
||||
logId string
|
||||
log log.Logger
|
||||
}
|
||||
|
||||
func (d *driver) Target() url.URL {
|
||||
return *d.target
|
||||
}
|
||||
|
||||
func (d *driver) Session(accessMode AccessMode, bookmarks ...string) (Session, error) {
|
||||
d.mut.Lock()
|
||||
defer d.mut.Unlock()
|
||||
if d.pool == nil {
|
||||
return nil, &UsageError{
|
||||
Message: "Trying to create session on closed driver",
|
||||
}
|
||||
}
|
||||
sessConfig := SessionConfig{
|
||||
AccessMode: accessMode,
|
||||
Bookmarks: bookmarks,
|
||||
DatabaseName: db.DefaultDatabase,
|
||||
}
|
||||
return newSession(
|
||||
d.config, sessConfig, d.router, d.pool, d.log), nil
|
||||
}
|
||||
|
||||
func (d *driver) NewSession(config SessionConfig) Session {
|
||||
if config.DatabaseName == "" {
|
||||
config.DatabaseName = db.DefaultDatabase
|
||||
}
|
||||
|
||||
d.mut.Lock()
|
||||
defer d.mut.Unlock()
|
||||
if d.pool == nil {
|
||||
return &sessionWithError{
|
||||
err: &UsageError{Message: "Trying to create session on closed driver"}}
|
||||
}
|
||||
return newSession(d.config, config, d.router, d.pool, d.log)
|
||||
}
|
||||
|
||||
func (d *driver) VerifyConnectivity() error {
|
||||
session := d.NewSession(SessionConfig{AccessMode: AccessModeRead})
|
||||
defer session.Close()
|
||||
result, err := session.Run("RETURN 1 AS n", nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = result.Consume()
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *driver) Close() error {
|
||||
d.mut.Lock()
|
||||
defer d.mut.Unlock()
|
||||
// Safeguard against closing more than once
|
||||
if d.pool != nil {
|
||||
d.pool.Close()
|
||||
}
|
||||
d.pool = nil
|
||||
d.log.Infof(log.Driver, d.logId, "Closed")
|
||||
return nil
|
||||
}
|
@ -1,156 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) "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 (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
|
||||
"github.com/neo4j/neo4j-go-driver/v4/neo4j/db"
|
||||
"github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/connector"
|
||||
"github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/pool"
|
||||
"github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/retry"
|
||||
"github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/router"
|
||||
)
|
||||
|
||||
// Neo4jError represents errors originating from Neo4j service.
|
||||
// Alias for convenience. This error is defined in db package and
|
||||
// used internally.
|
||||
type Neo4jError = db.Neo4jError
|
||||
|
||||
// UsageError represents errors caused by incorrect usage of the driver API.
|
||||
// This does not include Cypher syntax (those errors will be Neo4jError).
|
||||
type UsageError struct {
|
||||
Message string
|
||||
}
|
||||
|
||||
func (e *UsageError) Error() string {
|
||||
return e.Message
|
||||
}
|
||||
|
||||
// TransactionExecutionLimit error indicates that a retryable transaction has
|
||||
// failed due to reaching a limit like a timeout or maximum number of attempts.
|
||||
type TransactionExecutionLimit struct {
|
||||
Errors []error
|
||||
Causes []string
|
||||
}
|
||||
|
||||
func newTransactionExecutionLimit(errors []error, causes []string) *TransactionExecutionLimit {
|
||||
tel := &TransactionExecutionLimit{Errors: make([]error, len(errors)), Causes: causes}
|
||||
for i, err := range errors {
|
||||
tel.Errors[i] = wrapError(err)
|
||||
}
|
||||
|
||||
return tel
|
||||
}
|
||||
|
||||
func (e *TransactionExecutionLimit) Error() string {
|
||||
cause := "Unknown cause"
|
||||
l := len(e.Causes)
|
||||
if l > 0 {
|
||||
cause = e.Causes[l-1]
|
||||
}
|
||||
var err error
|
||||
l = len(e.Errors)
|
||||
if l > 0 {
|
||||
err = e.Errors[l-1]
|
||||
}
|
||||
return fmt.Sprintf("TransactionExecutionLimit: %s after %d attempts, last error: %s", cause, len(e.Errors), err)
|
||||
}
|
||||
|
||||
// ConnectivityError represent errors caused by the driver not being able to connect to Neo4j services,
|
||||
// or lost connections.
|
||||
type ConnectivityError struct {
|
||||
inner error
|
||||
}
|
||||
|
||||
func (e *ConnectivityError) Error() string {
|
||||
return fmt.Sprintf("ConnectivityError: %s", e.inner.Error())
|
||||
}
|
||||
|
||||
// IsNeo4jError returns true if the provided error is an instance of Neo4jError.
|
||||
func IsNeo4jError(err error) bool {
|
||||
_, is := err.(*Neo4jError)
|
||||
return is
|
||||
}
|
||||
|
||||
// IsUsageError returns true if the provided error is an instance of UsageError.
|
||||
func IsUsageError(err error) bool {
|
||||
_, is := err.(*UsageError)
|
||||
return is
|
||||
}
|
||||
|
||||
// IsConnectivityError returns true if the provided error is an instance of ConnectivityError.
|
||||
func IsConnectivityError(err error) bool {
|
||||
_, is := err.(*ConnectivityError)
|
||||
return is
|
||||
}
|
||||
|
||||
// IsTransactionExecutionLimit returns true if the provided error is an instance of TransactionExecutionLimit.
|
||||
func IsTransactionExecutionLimit(err error) bool {
|
||||
_, is := err.(*TransactionExecutionLimit)
|
||||
return is
|
||||
}
|
||||
|
||||
// TokenExpiredError represent errors caused by the driver not being able to connect to Neo4j services,
|
||||
// or lost connections.
|
||||
type TokenExpiredError struct {
|
||||
Code string
|
||||
Message string
|
||||
}
|
||||
|
||||
func (e *TokenExpiredError) Error() string {
|
||||
return fmt.Sprintf("TokenExpiredError: %s (%s)", e.Code, e.Message)
|
||||
}
|
||||
|
||||
func wrapError(err error) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
if err == io.EOF ||
|
||||
errors.Is(err, context.DeadlineExceeded) ||
|
||||
errors.Is(err, context.Canceled) {
|
||||
return &ConnectivityError{inner: err}
|
||||
}
|
||||
switch e := err.(type) {
|
||||
case *db.UnsupportedTypeError, *db.FeatureNotSupportedError:
|
||||
// Usage of a type not supported by database network protocol or feature
|
||||
// not supported by current version or edition.
|
||||
return &UsageError{Message: err.Error()}
|
||||
case *connector.TlsError, *connector.ConnectError:
|
||||
return &ConnectivityError{inner: err}
|
||||
case *pool.PoolTimeout, *pool.PoolFull:
|
||||
return &ConnectivityError{inner: err}
|
||||
case *router.ReadRoutingTableError:
|
||||
return &ConnectivityError{inner: err}
|
||||
case *retry.CommitFailedDeadError:
|
||||
return &ConnectivityError{inner: err}
|
||||
case net.Error:
|
||||
return &ConnectivityError{inner: err}
|
||||
case *db.Neo4jError:
|
||||
if e.Code == "Neo.ClientError.Security.TokenExpired" {
|
||||
return &TokenExpiredError{Code: e.Code, Message: e.Msg}
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
@ -1,780 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) "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 bolt
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/neo4j/neo4j-go-driver/v4/neo4j/db"
|
||||
"github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/packstream"
|
||||
"github.com/neo4j/neo4j-go-driver/v4/neo4j/log"
|
||||
)
|
||||
|
||||
const (
|
||||
bolt3_ready = iota // Ready for use
|
||||
bolt3_streaming // Receiving result from auto commit query
|
||||
bolt3_pendingtx // Transaction has been requested but not applied
|
||||
bolt3_tx // Transaction pending
|
||||
bolt3_streamingtx // Receiving result from a query within a transaction
|
||||
bolt3_failed // Recoverable error, needs reset
|
||||
bolt3_dead // Non recoverable protocol or connection error
|
||||
bolt3_unauthorized // Initial state, not sent hello message with authentication
|
||||
)
|
||||
|
||||
type internalTx3 struct {
|
||||
mode db.AccessMode
|
||||
bookmarks []string
|
||||
timeout time.Duration
|
||||
txMeta map[string]interface{}
|
||||
}
|
||||
|
||||
func (i *internalTx3) toMeta() map[string]interface{} {
|
||||
meta := map[string]interface{}{}
|
||||
if i.mode == db.ReadMode {
|
||||
meta["mode"] = "r"
|
||||
}
|
||||
if len(i.bookmarks) > 0 {
|
||||
meta["bookmarks"] = i.bookmarks
|
||||
}
|
||||
ms := int(i.timeout.Nanoseconds() / 1e6)
|
||||
if ms > 0 {
|
||||
meta["tx_timeout"] = ms
|
||||
}
|
||||
if len(i.txMeta) > 0 {
|
||||
meta["tx_metadata"] = i.txMeta
|
||||
}
|
||||
return meta
|
||||
}
|
||||
|
||||
type bolt3 struct {
|
||||
state int
|
||||
txId db.TxHandle
|
||||
currStream *stream
|
||||
conn net.Conn
|
||||
serverName string
|
||||
out *outgoing
|
||||
in *incoming
|
||||
connId string
|
||||
logId string
|
||||
serverVersion string
|
||||
tfirst int64 // Time that server started streaming
|
||||
pendingTx *internalTx3 // Stashed away when tx started explcitly
|
||||
bookmark string // Last bookmark
|
||||
birthDate time.Time
|
||||
log log.Logger
|
||||
err error // Last fatal error
|
||||
minor int
|
||||
}
|
||||
|
||||
func NewBolt3(serverName string, conn net.Conn, logger log.Logger, boltLog log.BoltLogger) *bolt3 {
|
||||
b := &bolt3{
|
||||
state: bolt3_unauthorized,
|
||||
conn: conn,
|
||||
serverName: serverName,
|
||||
in: &incoming{
|
||||
buf: make([]byte, 4096),
|
||||
hyd: hydrator{
|
||||
boltLogger: boltLog,
|
||||
},
|
||||
connReadTimeout: -1,
|
||||
logger: logger,
|
||||
logName: log.Bolt3,
|
||||
},
|
||||
birthDate: time.Now(),
|
||||
log: logger,
|
||||
}
|
||||
b.out = &outgoing{
|
||||
chunker: newChunker(),
|
||||
packer: packstream.Packer{},
|
||||
onErr: func(err error) {
|
||||
if b.err == nil {
|
||||
b.err = err
|
||||
}
|
||||
b.state = bolt3_dead
|
||||
},
|
||||
boltLogger: boltLog,
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func (b *bolt3) ServerName() string {
|
||||
return b.serverName
|
||||
}
|
||||
|
||||
func (b *bolt3) ServerVersion() string {
|
||||
return b.serverVersion
|
||||
}
|
||||
|
||||
// Sets b.err and b.state on failure
|
||||
func (b *bolt3) receiveMsg() interface{} {
|
||||
msg, err := b.in.next(b.conn)
|
||||
if err != nil {
|
||||
b.err = err
|
||||
b.log.Error(log.Bolt3, b.logId, b.err)
|
||||
b.state = bolt3_dead
|
||||
return nil
|
||||
}
|
||||
return msg
|
||||
}
|
||||
|
||||
// Receives a message that is assumed to be a success response or a failure in response
|
||||
// to a sent command.
|
||||
// Sets b.err and b.state on failure
|
||||
func (b *bolt3) receiveSuccess() *success {
|
||||
switch v := b.receiveMsg().(type) {
|
||||
case *success:
|
||||
return v
|
||||
case *db.Neo4jError:
|
||||
b.state = bolt3_failed
|
||||
b.err = v
|
||||
if v.Classification() == "ClientError" {
|
||||
// These could include potentially large cypher statement, only log to debug
|
||||
b.log.Debugf(log.Bolt3, b.logId, "%s", v)
|
||||
} else {
|
||||
b.log.Error(log.Bolt3, b.logId, v)
|
||||
}
|
||||
return nil
|
||||
default:
|
||||
// Receive failed, state has been set
|
||||
if b.err != nil {
|
||||
return nil
|
||||
}
|
||||
// Unexpected message received
|
||||
b.state = bolt3_dead
|
||||
b.err = errors.New("Expected success or database error")
|
||||
b.log.Error(log.Bolt3, b.logId, b.err)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (b *bolt3) connect(minor int, auth map[string]interface{}, userAgent string) error {
|
||||
if err := b.assertState(bolt3_unauthorized); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
hello := map[string]interface{}{
|
||||
"user_agent": userAgent,
|
||||
}
|
||||
// Merge authentication info into hello message
|
||||
for k, v := range auth {
|
||||
_, exists := hello[k]
|
||||
if exists {
|
||||
continue
|
||||
}
|
||||
hello[k] = v
|
||||
}
|
||||
|
||||
// Send hello message and wait for confirmation
|
||||
b.out.appendHello(hello)
|
||||
if b.out.send(b.conn); b.err != nil {
|
||||
return b.err
|
||||
}
|
||||
|
||||
succ := b.receiveSuccess()
|
||||
if b.err != nil {
|
||||
return b.err
|
||||
}
|
||||
|
||||
b.connId = succ.connectionId
|
||||
connectionLogId := fmt.Sprintf("%s@%s", b.connId, b.serverName)
|
||||
b.logId = connectionLogId
|
||||
b.in.logId = connectionLogId
|
||||
b.in.hyd.logId = connectionLogId
|
||||
b.out.logId = connectionLogId
|
||||
b.serverVersion = succ.server
|
||||
|
||||
// Transition into ready state
|
||||
b.state = bolt3_ready
|
||||
b.minor = minor
|
||||
b.log.Infof(log.Bolt3, b.logId, "Connected")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *bolt3) TxBegin(txConfig db.TxConfig) (db.TxHandle, error) {
|
||||
// Ok, to begin transaction while streaming auto-commit, just empty the stream and continue.
|
||||
if b.state == bolt3_streaming {
|
||||
if err := b.bufferStream(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
if err := b.assertState(bolt3_ready); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if err := b.checkImpersonation(txConfig.ImpersonatedUser); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
tx := &internalTx3{
|
||||
mode: txConfig.Mode,
|
||||
bookmarks: txConfig.Bookmarks,
|
||||
timeout: txConfig.Timeout,
|
||||
txMeta: txConfig.Meta,
|
||||
}
|
||||
|
||||
// If there are bookmarks, begin the transaction immediately to get the error from the
|
||||
// server early on. Requires a network roundtrip.
|
||||
if len(tx.bookmarks) > 0 {
|
||||
b.out.appendBegin(tx.toMeta())
|
||||
if b.out.send(b.conn); b.err != nil {
|
||||
return 0, b.err
|
||||
}
|
||||
if b.receiveSuccess(); b.err != nil {
|
||||
return 0, b.err
|
||||
}
|
||||
b.state = bolt3_tx
|
||||
} else {
|
||||
// Stash this into pending internal tx
|
||||
b.pendingTx = tx
|
||||
b.state = bolt3_pendingtx
|
||||
}
|
||||
b.txId = db.TxHandle(time.Now().Unix())
|
||||
return b.txId, nil
|
||||
}
|
||||
|
||||
// Should NOT set b.err or change b.state as this is used to guard from
|
||||
// misuse from clients that stick to their connections when they shouldn't.
|
||||
func (b *bolt3) assertTxHandle(h1, h2 db.TxHandle) error {
|
||||
if h1 != h2 {
|
||||
err := errors.New("Invalid transaction handle")
|
||||
b.log.Error(log.Bolt3, b.logId, err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Should NOT set b.err or b.state since the connection is still valid
|
||||
func (b *bolt3) assertState(allowed ...int) error {
|
||||
// Forward prior error instead, this former error is probably the
|
||||
// root cause of any state error. Like a call to Run with malformed
|
||||
// cypher causes an error and another call to Commit would cause the
|
||||
// state to be wrong. Do not log this.
|
||||
if b.err != nil {
|
||||
return b.err
|
||||
}
|
||||
for _, a := range allowed {
|
||||
if b.state == a {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
err := errors.New(fmt.Sprintf("Invalid state %d, expected: %+v", b.state, allowed))
|
||||
b.log.Error(log.Bolt3, b.logId, err)
|
||||
return err
|
||||
}
|
||||
|
||||
func (b *bolt3) TxCommit(txh db.TxHandle) error {
|
||||
if err := b.assertTxHandle(b.txId, txh); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Nothing to do, a transaction started but no commands were issued on it, server is unaware
|
||||
if b.state == bolt3_pendingtx {
|
||||
b.state = bolt3_ready
|
||||
return nil
|
||||
}
|
||||
|
||||
// Consume pending stream if any to turn state from streamingtx to tx
|
||||
// Access to streams outside of tx boundary is not allowed, therefore we should discard
|
||||
// the stream (not buffer).
|
||||
if b.state == bolt3_streamingtx {
|
||||
if err := b.discardStream(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Should be in vanilla tx state now
|
||||
if err := b.assertState(bolt3_tx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Send request to server to commit
|
||||
b.out.appendCommit()
|
||||
if b.out.send(b.conn); b.err != nil {
|
||||
return b.err
|
||||
}
|
||||
|
||||
// Evaluate server response
|
||||
succ := b.receiveSuccess()
|
||||
if b.err != nil {
|
||||
return b.err
|
||||
}
|
||||
// Keep track of bookmark
|
||||
if len(succ.bookmark) > 0 {
|
||||
b.bookmark = succ.bookmark
|
||||
}
|
||||
|
||||
// Transition into ready state
|
||||
b.state = bolt3_ready
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *bolt3) TxRollback(txh db.TxHandle) error {
|
||||
if err := b.assertTxHandle(b.txId, txh); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Nothing to do, a transaction started but no commands were issued on it
|
||||
if b.state == bolt3_pendingtx {
|
||||
b.state = bolt3_ready
|
||||
return nil
|
||||
}
|
||||
|
||||
// Can not send rollback while still streaming, consume to turn state into tx
|
||||
// Access to streams outside of tx boundary is not allowed, therefore we should discard
|
||||
// the stream (not buffer).
|
||||
if b.state == bolt3_streamingtx {
|
||||
if err := b.discardStream(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Should be in vanilla tx state now
|
||||
if err := b.assertState(bolt3_tx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Send rollback request to server
|
||||
b.out.appendRollback()
|
||||
if b.out.send(b.conn); b.err != nil {
|
||||
return b.err
|
||||
}
|
||||
|
||||
// Receive rollback confirmation
|
||||
if b.receiveSuccess(); b.err != nil {
|
||||
return b.err
|
||||
}
|
||||
|
||||
b.state = bolt3_ready
|
||||
return nil
|
||||
}
|
||||
|
||||
// Discards all records in current stream
|
||||
func (b *bolt3) discardStream() error {
|
||||
if b.state != bolt3_streaming && b.state != bolt3_streamingtx {
|
||||
// Nothing to do
|
||||
return nil
|
||||
}
|
||||
|
||||
var (
|
||||
sum *db.Summary
|
||||
err error
|
||||
)
|
||||
for sum == nil && err == nil {
|
||||
_, sum, err = b.receiveNext()
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Collects all records in current stream
|
||||
func (b *bolt3) bufferStream() error {
|
||||
if b.state != bolt3_streaming && b.state != bolt3_streamingtx {
|
||||
// Nothing to do
|
||||
return nil
|
||||
}
|
||||
|
||||
n := 0
|
||||
var (
|
||||
sum *db.Summary
|
||||
err error
|
||||
rec *db.Record
|
||||
)
|
||||
for sum == nil && err == nil {
|
||||
rec, sum, err = b.receiveNext()
|
||||
if rec != nil {
|
||||
b.currStream.push(rec)
|
||||
n++
|
||||
}
|
||||
}
|
||||
|
||||
if n > 0 {
|
||||
b.log.Warnf(log.Bolt3, b.logId, "Buffered %d records", n)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (b *bolt3) run(cypher string, params map[string]interface{}, tx *internalTx3) (*stream, error) {
|
||||
// If already streaming, finish current stream first
|
||||
if err := b.bufferStream(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := b.assertState(bolt3_tx, bolt3_ready, bolt3_pendingtx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var meta map[string]interface{}
|
||||
if tx != nil {
|
||||
meta = tx.toMeta()
|
||||
}
|
||||
|
||||
// Append lazy begin transaction message
|
||||
if b.state == bolt3_pendingtx {
|
||||
b.out.appendBegin(meta)
|
||||
meta = nil
|
||||
}
|
||||
|
||||
// Append run message
|
||||
b.out.appendRun(cypher, params, meta)
|
||||
|
||||
// Append pull all message and send it along with other pending messages
|
||||
b.out.appendPullAll()
|
||||
if b.out.send(b.conn); b.err != nil {
|
||||
return nil, b.err
|
||||
}
|
||||
|
||||
// Process server responses
|
||||
// Receive confirmation of transaction begin if it was started above
|
||||
if b.state == bolt3_pendingtx {
|
||||
if b.receiveSuccess(); b.err != nil {
|
||||
return nil, b.err
|
||||
}
|
||||
b.state = bolt3_tx
|
||||
}
|
||||
|
||||
// Receive confirmation of run message
|
||||
succ := b.receiveSuccess()
|
||||
if b.err != nil {
|
||||
return nil, b.err
|
||||
}
|
||||
b.tfirst = succ.tfirst
|
||||
// Change state to streaming
|
||||
if b.state == bolt3_ready {
|
||||
b.state = bolt3_streaming
|
||||
} else {
|
||||
b.state = bolt3_streamingtx
|
||||
}
|
||||
|
||||
b.currStream = &stream{keys: succ.fields}
|
||||
return b.currStream, nil
|
||||
}
|
||||
|
||||
func (b *bolt3) Run(runCommand db.Command, txConfig db.TxConfig) (db.StreamHandle, error) {
|
||||
if err := b.assertState(bolt3_streaming, bolt3_ready); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := b.checkImpersonation(txConfig.ImpersonatedUser); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tx := internalTx3{
|
||||
mode: txConfig.Mode,
|
||||
bookmarks: txConfig.Bookmarks,
|
||||
timeout: txConfig.Timeout,
|
||||
txMeta: txConfig.Meta,
|
||||
}
|
||||
stream, err := b.run(runCommand.Cypher, runCommand.Params, &tx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return stream, nil
|
||||
}
|
||||
|
||||
func (b *bolt3) RunTx(txh db.TxHandle, runCommand db.Command) (db.StreamHandle, error) {
|
||||
if err := b.assertTxHandle(b.txId, txh); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
stream, err := b.run(runCommand.Cypher, runCommand.Params, b.pendingTx)
|
||||
b.pendingTx = nil
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return stream, nil
|
||||
}
|
||||
|
||||
func (b *bolt3) Keys(streamHandle db.StreamHandle) ([]string, error) {
|
||||
stream, ok := streamHandle.(*stream)
|
||||
if !ok {
|
||||
return nil, errors.New("Invalid stream handle")
|
||||
}
|
||||
// Don't care about if the stream is the current or even if it belongs to this connection.
|
||||
return stream.keys, nil
|
||||
}
|
||||
|
||||
// Reads one record from the stream.
|
||||
func (b *bolt3) Next(streamHandle db.StreamHandle) (*db.Record, *db.Summary, error) {
|
||||
stream, ok := streamHandle.(*stream)
|
||||
if !ok {
|
||||
return nil, nil, errors.New("Invalid stream handle")
|
||||
}
|
||||
|
||||
// Buffered stream or someone elses stream, doesn't matter...
|
||||
buf, rec, sum, err := stream.bufferedNext()
|
||||
if buf {
|
||||
return rec, sum, err
|
||||
}
|
||||
|
||||
// Nothing in the stream buffer, the stream must be the current
|
||||
// one to fetch on it otherwise something is wrong.
|
||||
if stream != b.currStream {
|
||||
return nil, nil, errors.New("Invalid stream handle")
|
||||
}
|
||||
|
||||
return b.receiveNext()
|
||||
}
|
||||
|
||||
func (b *bolt3) Consume(streamHandle db.StreamHandle) (*db.Summary, error) {
|
||||
stream, ok := streamHandle.(*stream)
|
||||
if !ok {
|
||||
return nil, errors.New("Invalid stream handle")
|
||||
}
|
||||
|
||||
// If the stream isn't current, it should either already be complete
|
||||
// or have an error.
|
||||
if stream != b.currStream {
|
||||
return stream.sum, stream.err
|
||||
}
|
||||
|
||||
// It is the current stream, it should not be complete but...
|
||||
if stream.err != nil || stream.sum != nil {
|
||||
return stream.sum, stream.err
|
||||
}
|
||||
|
||||
b.discardStream()
|
||||
return stream.sum, stream.err
|
||||
}
|
||||
|
||||
func (b *bolt3) Buffer(streamHandle db.StreamHandle) error {
|
||||
stream, ok := streamHandle.(*stream)
|
||||
if !ok {
|
||||
return errors.New("Invalid stream handle")
|
||||
}
|
||||
|
||||
// If the stream isn't current, it should either already be complete
|
||||
// or have an error.
|
||||
if stream != b.currStream {
|
||||
return stream.Err()
|
||||
}
|
||||
|
||||
// It is the current stream, it should not be complete but...
|
||||
if stream.err != nil || stream.sum != nil {
|
||||
return stream.Err()
|
||||
}
|
||||
|
||||
b.bufferStream()
|
||||
return stream.Err()
|
||||
}
|
||||
|
||||
// Reads one record from the network.
|
||||
func (b *bolt3) receiveNext() (*db.Record, *db.Summary, error) {
|
||||
if err := b.assertState(bolt3_streaming, bolt3_streamingtx); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
res := b.receiveMsg()
|
||||
if b.err != nil {
|
||||
return nil, nil, b.err
|
||||
}
|
||||
|
||||
switch x := res.(type) {
|
||||
case *db.Record:
|
||||
x.Keys = b.currStream.keys
|
||||
return x, nil, nil
|
||||
case *success:
|
||||
// End of stream, parse summary
|
||||
sum := x.summary()
|
||||
if sum == nil {
|
||||
b.state = bolt3_dead
|
||||
b.err = errors.New("Failed to parse summary")
|
||||
b.currStream.err = b.err
|
||||
b.currStream = nil
|
||||
b.log.Error(log.Bolt3, b.logId, b.err)
|
||||
return nil, nil, b.err
|
||||
}
|
||||
if b.state == bolt3_streamingtx {
|
||||
b.state = bolt3_tx
|
||||
} else {
|
||||
b.state = bolt3_ready
|
||||
// Keep bookmark for auto-commit tx
|
||||
if len(sum.Bookmark) > 0 {
|
||||
b.bookmark = sum.Bookmark
|
||||
}
|
||||
}
|
||||
b.currStream.sum = sum
|
||||
b.currStream = nil
|
||||
// Add some extras to the summary
|
||||
sum.Agent = b.serverVersion
|
||||
sum.Major = 3
|
||||
sum.Minor = b.minor
|
||||
sum.ServerName = b.serverName
|
||||
sum.TFirst = b.tfirst
|
||||
return nil, sum, nil
|
||||
case *db.Neo4jError:
|
||||
b.err = x
|
||||
b.currStream.err = b.err
|
||||
b.currStream = nil
|
||||
b.state = bolt3_failed
|
||||
if x.Classification() == "ClientError" {
|
||||
// These could include potentially large cypher statement, only log to debug
|
||||
b.log.Debugf(log.Bolt3, b.logId, "%s", x)
|
||||
} else {
|
||||
b.log.Error(log.Bolt3, b.logId, x)
|
||||
}
|
||||
return nil, nil, x
|
||||
default:
|
||||
b.state = bolt3_dead
|
||||
b.err = errors.New("Unknown response")
|
||||
b.currStream.err = b.err
|
||||
b.currStream = nil
|
||||
b.log.Error(log.Bolt3, b.logId, b.err)
|
||||
return nil, nil, b.err
|
||||
}
|
||||
}
|
||||
|
||||
func (b *bolt3) Bookmark() string {
|
||||
return b.bookmark
|
||||
}
|
||||
|
||||
func (b *bolt3) IsAlive() bool {
|
||||
return b.state != bolt3_dead
|
||||
}
|
||||
|
||||
func (b *bolt3) Birthdate() time.Time {
|
||||
return b.birthDate
|
||||
}
|
||||
|
||||
func (b *bolt3) Reset() {
|
||||
defer func() {
|
||||
// Reset internal state
|
||||
b.txId = 0
|
||||
b.currStream = nil
|
||||
b.bookmark = ""
|
||||
b.pendingTx = nil
|
||||
b.err = nil
|
||||
}()
|
||||
|
||||
if b.state == bolt3_ready || b.state == bolt3_dead {
|
||||
// No need for reset
|
||||
return
|
||||
}
|
||||
|
||||
// Discard any pending stream
|
||||
b.discardStream()
|
||||
|
||||
if b.state == bolt3_ready || b.state == bolt3_dead {
|
||||
// No need for reset
|
||||
return
|
||||
}
|
||||
|
||||
// Send the reset message to the server
|
||||
// Need to clear any pending error
|
||||
b.err = nil
|
||||
b.out.appendReset()
|
||||
if b.out.send(b.conn); b.err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Should receive x number of ignores until we get a success
|
||||
for {
|
||||
msg := b.receiveMsg()
|
||||
if b.err != nil {
|
||||
return
|
||||
}
|
||||
switch msg.(type) {
|
||||
case *ignored:
|
||||
// Command ignored
|
||||
case *success:
|
||||
// Reset confirmed
|
||||
b.state = bolt3_ready
|
||||
return
|
||||
default:
|
||||
b.state = bolt3_dead
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (b *bolt3) checkImpersonation(impersonatedUser string) error {
|
||||
if impersonatedUser != "" {
|
||||
return &db.FeatureNotSupportedError{Server: b.serverName, Feature: "user impersonation", Reason: "requires least server v4.4"}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *bolt3) GetRoutingTable(context map[string]string, bookmarks []string, database, impersonatedUser string) (*db.RoutingTable, error) {
|
||||
if err := b.assertState(bolt3_ready); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if database != db.DefaultDatabase {
|
||||
return nil, &db.FeatureNotSupportedError{Server: b.serverName, Feature: "route to database", Reason: "requires at least server v4"}
|
||||
}
|
||||
if err := b.checkImpersonation(impersonatedUser); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Only available when Neo4j is setup with clustering
|
||||
runCommand := db.Command{
|
||||
Cypher: "CALL dbms.cluster.routing.getRoutingTable($context)",
|
||||
Params: map[string]interface{}{"context": context},
|
||||
}
|
||||
txConfig := db.TxConfig{Mode: db.ReadMode}
|
||||
streamHandle, err := b.Run(runCommand, txConfig)
|
||||
if err != nil {
|
||||
// Give a better error
|
||||
dbError, isDbError := err.(*db.Neo4jError)
|
||||
if isDbError && dbError.Code == "Neo.ClientError.Procedure.ProcedureNotFound" {
|
||||
return nil, &db.FeatureNotSupportedError{Server: b.serverName, Feature: "routing", Reason: "requires cluster setup"}
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rec, _, err := b.Next(streamHandle)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if rec == nil {
|
||||
return nil, errors.New("No routing table record")
|
||||
}
|
||||
// Just empty the stream, ignore the summary should leave the connecion in ready state
|
||||
b.Next(streamHandle)
|
||||
|
||||
table := parseRoutingTableRecord(rec)
|
||||
if table == nil {
|
||||
return nil, errors.New("Unable to parse routing table")
|
||||
}
|
||||
// Just because
|
||||
table.DatabaseName = db.DefaultDatabase
|
||||
|
||||
return table, nil
|
||||
}
|
||||
|
||||
// Beware, could be called on another thread when driver is closed.
|
||||
func (b *bolt3) Close() {
|
||||
b.log.Infof(log.Bolt3, b.logId, "Close")
|
||||
if b.state != bolt3_dead {
|
||||
b.out.appendGoodbye()
|
||||
b.out.send(b.conn)
|
||||
}
|
||||
b.conn.Close()
|
||||
b.state = bolt3_dead
|
||||
}
|
||||
|
||||
func (b *bolt3) ForceReset() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *bolt3) SetBoltLogger(boltLogger log.BoltLogger) {
|
||||
b.in.hyd.boltLogger = boltLogger
|
||||
b.out.boltLogger = boltLogger
|
||||
}
|
File diff suppressed because it is too large
Load Diff
119
vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/bolt/bolt_logging.go
generated
vendored
119
vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/bolt/bolt_logging.go
generated
vendored
@ -1,119 +0,0 @@
|
||||
package bolt
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/neo4j/neo4j-go-driver/v4/neo4j/db"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type loggableDictionary map[string]interface{}
|
||||
|
||||
func (d loggableDictionary) String() string {
|
||||
if credentials, ok := d["credentials"]; ok {
|
||||
d["credentials"] = "<redacted>"
|
||||
defer func() {
|
||||
d["credentials"] = credentials
|
||||
}()
|
||||
}
|
||||
return serializeTrace(d)
|
||||
}
|
||||
|
||||
type loggableStringDictionary map[string]string
|
||||
|
||||
func (sd loggableStringDictionary) String() string {
|
||||
if credentials, ok := sd["credentials"]; ok {
|
||||
sd["credentials"] = "<redacted>"
|
||||
defer func() {
|
||||
sd["credentials"] = credentials
|
||||
}()
|
||||
}
|
||||
return serializeTrace(sd)
|
||||
}
|
||||
|
||||
type loggableList []interface{}
|
||||
|
||||
func (l loggableList) String() string {
|
||||
return serializeTrace(l)
|
||||
}
|
||||
|
||||
type loggableStringList []string
|
||||
|
||||
func (s loggableStringList) String() string {
|
||||
return serializeTrace(s)
|
||||
}
|
||||
|
||||
type loggableSuccess success
|
||||
type loggedSuccess struct {
|
||||
Server string `json:"server,omitempty"`
|
||||
ConnectionId string `json:"connection_id,omitempty"`
|
||||
Fields []string `json:"fields,omitempty"`
|
||||
TFirst string `json:"t_first,omitempty"`
|
||||
Bookmark string `json:"bookmark,omitempty"`
|
||||
TLast string `json:"t_last,omitempty"`
|
||||
HasMore bool `json:"has_more,omitempy"`
|
||||
Db string `json:"db,omitempty"`
|
||||
Qid int64 `json:"qid,omitempty"`
|
||||
RoutingTable *loggedRoutingTable `json:"routing_table,omitempty"`
|
||||
ConfigHints loggableDictionary `json:"hints,omitempty"`
|
||||
}
|
||||
|
||||
func (s loggableSuccess) String() string {
|
||||
success := loggedSuccess{
|
||||
Server: s.server,
|
||||
ConnectionId: s.connectionId,
|
||||
Fields: s.fields,
|
||||
TFirst: formatOmittingZero(s.tfirst),
|
||||
Bookmark: s.bookmark,
|
||||
TLast: formatOmittingZero(s.tlast),
|
||||
HasMore: s.hasMore,
|
||||
Db: s.db,
|
||||
ConfigHints: s.configurationHints,
|
||||
}
|
||||
if s.qid > -1 {
|
||||
success.Qid = s.qid
|
||||
}
|
||||
routingTable := s.routingTable
|
||||
if routingTable != nil {
|
||||
success.RoutingTable = &loggedRoutingTable{
|
||||
TimeToLive: routingTable.TimeToLive,
|
||||
DatabaseName: routingTable.DatabaseName,
|
||||
Routers: routingTable.Routers,
|
||||
Readers: routingTable.Readers,
|
||||
Writers: routingTable.Writers,
|
||||
}
|
||||
}
|
||||
return serializeTrace(success)
|
||||
}
|
||||
|
||||
type loggedRoutingTable struct {
|
||||
TimeToLive int `json:"ttl,omitempty"`
|
||||
DatabaseName string `json:"db,omitempty"`
|
||||
Routers []string `json:"routers,omitempty"`
|
||||
Readers []string `json:"readers,omitempty"`
|
||||
Writers []string `json:"writers,omitempty"`
|
||||
}
|
||||
|
||||
func formatOmittingZero(i int64) string {
|
||||
if i == 0 {
|
||||
return ""
|
||||
}
|
||||
return strconv.FormatInt(i, 10)
|
||||
}
|
||||
|
||||
type loggableFailure db.Neo4jError
|
||||
|
||||
func (f loggableFailure) String() string {
|
||||
return serializeTrace(map[string]interface{}{
|
||||
"code": f.Code,
|
||||
"message": f.Msg,
|
||||
})
|
||||
}
|
||||
|
||||
func serializeTrace(v interface{}) string {
|
||||
builder := strings.Builder{}
|
||||
encoder := json.NewEncoder(&builder)
|
||||
encoder.SetEscapeHTML(false)
|
||||
_ = encoder.Encode(v)
|
||||
return strings.TrimSpace(builder.String())
|
||||
}
|
@ -1,107 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) "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 bolt
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"io"
|
||||
)
|
||||
|
||||
type chunker struct {
|
||||
buf []byte
|
||||
sizes []int
|
||||
offset int
|
||||
}
|
||||
|
||||
func newChunker() chunker {
|
||||
return chunker{
|
||||
buf: make([]byte, 0, 1024),
|
||||
sizes: make([]int, 0, 3),
|
||||
offset: 0,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *chunker) beginMessage() {
|
||||
// Space for length of next message
|
||||
c.buf = append(c.buf, 0, 0)
|
||||
c.offset += 2
|
||||
}
|
||||
|
||||
func (c *chunker) endMessage() {
|
||||
// Calculate size and stash it
|
||||
size := len(c.buf) - c.offset
|
||||
c.offset += size
|
||||
c.sizes = append(c.sizes, size)
|
||||
|
||||
// Add zero chunk to mark end of message
|
||||
c.buf = append(c.buf, 0, 0)
|
||||
c.offset += 2
|
||||
}
|
||||
|
||||
func (c *chunker) send(wr io.Writer) error {
|
||||
// Try to make as few writes as possible to reduce network overhead
|
||||
// Whenever we encounter a message that is bigger than max chunk size we need
|
||||
// to write and make a new chunk
|
||||
start := 0
|
||||
end := 0
|
||||
|
||||
for _, size := range c.sizes {
|
||||
if size <= 0xffff {
|
||||
binary.BigEndian.PutUint16(c.buf[end:], uint16(size))
|
||||
// Size + messge + end of message marker
|
||||
end += 2 + size + 2
|
||||
} else {
|
||||
// Could be a message that ranges over multiple chunks
|
||||
for size > 0xffff {
|
||||
c.buf[end] = 0xff
|
||||
c.buf[end+1] = 0xff
|
||||
// Size + messge
|
||||
end += 2 + 0xffff
|
||||
|
||||
_, err := wr.Write(c.buf[start:end])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Reuse part of buffer that has already been written to specify size
|
||||
// of the chunk
|
||||
end -= 2
|
||||
start = end
|
||||
size -= 0xffff
|
||||
}
|
||||
binary.BigEndian.PutUint16(c.buf[end:], uint16(size))
|
||||
// Size + messge + end of message marker
|
||||
end += 2 + size + 2
|
||||
}
|
||||
}
|
||||
|
||||
if end > start {
|
||||
_, err := wr.Write(c.buf[start:end])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Prepare for reuse
|
||||
c.offset = 0
|
||||
c.buf = c.buf[:0]
|
||||
c.sizes = c.sizes[:0]
|
||||
|
||||
return nil
|
||||
}
|
@ -1,105 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) "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 bolt contains implementations of the database functionality.
|
||||
package bolt
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
|
||||
"github.com/neo4j/neo4j-go-driver/v4/neo4j/db"
|
||||
"github.com/neo4j/neo4j-go-driver/v4/neo4j/log"
|
||||
)
|
||||
|
||||
type protocolVersion struct {
|
||||
major byte
|
||||
minor byte
|
||||
back byte // Number of minor versions back
|
||||
}
|
||||
|
||||
// Supported versions in priority order
|
||||
var versions = [4]protocolVersion{
|
||||
{major: 4, minor: 4, back: 2},
|
||||
{major: 4, minor: 1},
|
||||
{major: 4, minor: 0},
|
||||
{major: 3, minor: 0},
|
||||
}
|
||||
|
||||
// Connect initiates the negotiation of the Bolt protocol version.
|
||||
// Returns the instance of bolt protocol implementing the low-level Connection interface.
|
||||
func Connect(serverName string, conn net.Conn, auth map[string]interface{}, userAgent string, routingContext map[string]string, logger log.Logger, boltLog log.BoltLogger) (db.Connection, error) {
|
||||
// Perform Bolt handshake to negotiate version
|
||||
// Send handshake to server
|
||||
handshake := []byte{
|
||||
0x60, 0x60, 0xb0, 0x17, // Magic: GoGoBolt
|
||||
0x00, versions[0].back, versions[0].minor, versions[0].major,
|
||||
0x00, versions[1].back, versions[1].minor, versions[1].major,
|
||||
0x00, versions[2].back, versions[2].minor, versions[2].major,
|
||||
0x00, versions[3].back, versions[3].minor, versions[3].major,
|
||||
}
|
||||
if boltLog != nil {
|
||||
boltLog.LogClientMessage("", "<MAGIC> %#010X", handshake[0:4])
|
||||
boltLog.LogClientMessage("", "<HANDSHAKE> %#010X %#010X %#010X %#010X", handshake[4:8], handshake[8:12], handshake[12:16], handshake[16:20])
|
||||
}
|
||||
_, err := conn.Write(handshake)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Receive accepted server version
|
||||
buf := make([]byte, 4)
|
||||
_, err = io.ReadFull(conn, buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if boltLog != nil {
|
||||
boltLog.LogServerMessage("", "<HANDSHAKE> %#010X", buf)
|
||||
}
|
||||
// Parse received version and construct the correct instance
|
||||
major := buf[3]
|
||||
minor := buf[2]
|
||||
switch major {
|
||||
case 3:
|
||||
// Handover rest of connection handshaking
|
||||
boltConn := NewBolt3(serverName, conn, logger, boltLog)
|
||||
err = boltConn.connect(int(minor), auth, userAgent)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return boltConn, nil
|
||||
case 4:
|
||||
// Handover rest of connection handshaking
|
||||
boltConn := NewBolt4(serverName, conn, logger, boltLog)
|
||||
err = boltConn.connect(int(minor), auth, userAgent, routingContext)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return boltConn, nil
|
||||
case 0:
|
||||
err = errors.New(fmt.Sprintf("Server did not accept any of the requested Bolt versions (%#v)", versions))
|
||||
default:
|
||||
err = errors.New(fmt.Sprintf("Server responded with unsupported version %d.%d", major, minor))
|
||||
}
|
||||
|
||||
return nil, err
|
||||
}
|
@ -1,113 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) "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 bolt
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/binary"
|
||||
rio "github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/racingio"
|
||||
"github.com/neo4j/neo4j-go-driver/v4/neo4j/log"
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
// dechunkMessage takes a buffer to be reused and returns the reusable buffer
|
||||
// (might have been reallocated to handle growth), the message buffer and
|
||||
// error.
|
||||
// Reads will race against the provided context ctx
|
||||
// If the server provides the connection read timeout hint readTimeout, a new context will be created from that timeout
|
||||
// and the user-provided context ctx before every read
|
||||
func dechunkMessage(
|
||||
conn net.Conn,
|
||||
msgBuf []byte,
|
||||
readTimeout time.Duration,
|
||||
logger log.Logger,
|
||||
logName string,
|
||||
logId string) ([]byte, []byte, error) {
|
||||
|
||||
sizeBuf := []byte{0x00, 0x00}
|
||||
off := 0
|
||||
|
||||
reader := rio.NewRacingReader(conn)
|
||||
|
||||
for {
|
||||
updatedCtx, cancelFunc := newContext(readTimeout, logger, logName, logId)
|
||||
_, err := reader.ReadFull(updatedCtx, sizeBuf)
|
||||
if err != nil {
|
||||
return msgBuf, nil, err
|
||||
}
|
||||
if cancelFunc != nil { // reading has been completed, time to release the context
|
||||
cancelFunc()
|
||||
}
|
||||
chunkSize := int(binary.BigEndian.Uint16(sizeBuf))
|
||||
if chunkSize == 0 {
|
||||
if off > 0 {
|
||||
return msgBuf, msgBuf[:off], nil
|
||||
}
|
||||
// Got a nop chunk
|
||||
continue
|
||||
}
|
||||
|
||||
// Need to expand buffer
|
||||
if (off + chunkSize) > cap(msgBuf) {
|
||||
newMsgBuf := make([]byte, (off+chunkSize)+4096)
|
||||
copy(newMsgBuf, msgBuf)
|
||||
msgBuf = newMsgBuf
|
||||
}
|
||||
// Read the chunk into buffer
|
||||
updatedCtx, cancelFunc = newContext(readTimeout, logger, logName, logId)
|
||||
_, err = reader.ReadFull(updatedCtx, msgBuf[off:(off+chunkSize)])
|
||||
if err != nil {
|
||||
return msgBuf, nil, err
|
||||
}
|
||||
if cancelFunc != nil { // reading has been completed, time to release the context
|
||||
cancelFunc()
|
||||
}
|
||||
off += chunkSize
|
||||
}
|
||||
}
|
||||
|
||||
// newContext computes a new context and cancel function if a readTimeout is set
|
||||
func newContext(
|
||||
readTimeout time.Duration,
|
||||
logger log.Logger,
|
||||
logName string,
|
||||
logId string) (context.Context, context.CancelFunc) {
|
||||
|
||||
ctx := context.Background()
|
||||
if readTimeout >= 0 {
|
||||
newCtx, cancelFunc := context.WithTimeout(ctx, readTimeout)
|
||||
logger.Debugf(logName, logId,
|
||||
"read timeout of %s applied, chunk read deadline is now: %s",
|
||||
readTimeout.String(),
|
||||
deadlineOf(newCtx),
|
||||
)
|
||||
return newCtx, cancelFunc
|
||||
}
|
||||
return ctx, nil
|
||||
}
|
||||
|
||||
func deadlineOf(ctx context.Context) string {
|
||||
deadline, hasDeadline := ctx.Deadline()
|
||||
if !hasDeadline {
|
||||
return "N/A (no deadline set)"
|
||||
}
|
||||
return deadline.String()
|
||||
}
|
@ -1,818 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) "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 bolt
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/neo4j/neo4j-go-driver/v4/neo4j/log"
|
||||
"time"
|
||||
|
||||
"github.com/neo4j/neo4j-go-driver/v4/neo4j/db"
|
||||
"github.com/neo4j/neo4j-go-driver/v4/neo4j/dbtype"
|
||||
"github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/packstream"
|
||||
)
|
||||
|
||||
const containsSystemUpdatesKey = "contains-system-updates"
|
||||
|
||||
type ignored struct{}
|
||||
type success struct {
|
||||
fields []string
|
||||
tfirst int64
|
||||
qid int64
|
||||
bookmark string
|
||||
connectionId string
|
||||
server string
|
||||
db string
|
||||
hasMore bool
|
||||
tlast int64
|
||||
qtype db.StatementType
|
||||
counters map[string]interface{}
|
||||
plan *db.Plan
|
||||
profile *db.ProfiledPlan
|
||||
notifications []db.Notification
|
||||
routingTable *db.RoutingTable
|
||||
num uint32
|
||||
configurationHints map[string]interface{}
|
||||
}
|
||||
|
||||
func (s *success) String() string {
|
||||
str := fmt.Sprintf("%#v", s)
|
||||
if s.plan != nil {
|
||||
str += fmt.Sprintf(" \nplan: %#v", s.plan)
|
||||
}
|
||||
if s.profile != nil {
|
||||
str += fmt.Sprintf(" \nprofile: %#v", s.profile)
|
||||
}
|
||||
if s.routingTable != nil {
|
||||
str += fmt.Sprintf(" \nrouting table: %#v", s.routingTable)
|
||||
}
|
||||
return str
|
||||
}
|
||||
|
||||
func (s *success) summary() *db.Summary {
|
||||
return &db.Summary{
|
||||
Bookmark: s.bookmark,
|
||||
StmntType: s.qtype,
|
||||
Counters: extractIntCounters(s.counters),
|
||||
TLast: s.tlast,
|
||||
Plan: s.plan,
|
||||
ProfiledPlan: s.profile,
|
||||
Notifications: s.notifications,
|
||||
Database: s.db,
|
||||
ContainsSystemUpdates: extractBoolPointer(s.counters, containsSystemUpdatesKey),
|
||||
}
|
||||
}
|
||||
|
||||
func extractIntCounters(counters map[string]interface{}) map[string]int {
|
||||
result := make(map[string]int, len(counters))
|
||||
for k, v := range counters {
|
||||
if k != containsSystemUpdatesKey {
|
||||
result[k] = v.(int)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func extractBoolPointer(counters map[string]interface{}, key string) *bool {
|
||||
result, ok := counters[key]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return result.(*bool)
|
||||
}
|
||||
|
||||
func (s *success) isResetResponse() bool {
|
||||
return s.num == 0
|
||||
}
|
||||
|
||||
type hydrator struct {
|
||||
unpacker packstream.Unpacker
|
||||
unp *packstream.Unpacker
|
||||
err error
|
||||
cachedIgnored ignored
|
||||
cachedSuccess success
|
||||
boltLogger log.BoltLogger
|
||||
logId string
|
||||
}
|
||||
|
||||
func (h *hydrator) setErr(err error) {
|
||||
if h.err == nil {
|
||||
h.err = err
|
||||
}
|
||||
}
|
||||
|
||||
func (h *hydrator) getErr() error {
|
||||
if h.unp.Err != nil {
|
||||
return h.unp.Err
|
||||
}
|
||||
return h.err
|
||||
}
|
||||
|
||||
func (h *hydrator) assertLength(structType string, expected, actual uint32) {
|
||||
if expected != actual {
|
||||
h.setErr(&db.ProtocolError{
|
||||
MessageType: structType,
|
||||
Err: fmt.Sprintf("Invalid length of struct, expected %d but was %d",
|
||||
expected, actual),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// hydrate hydrates a top-level struct message
|
||||
func (h *hydrator) hydrate(buf []byte) (x interface{}, err error) {
|
||||
h.unp = &h.unpacker
|
||||
h.unp.Reset(buf)
|
||||
h.unp.Next()
|
||||
|
||||
if h.unp.Curr != packstream.PackedStruct {
|
||||
return nil, errors.New(fmt.Sprintf("Expected struct"))
|
||||
}
|
||||
|
||||
n := h.unp.Len()
|
||||
t := h.unp.StructTag()
|
||||
switch t {
|
||||
case msgSuccess:
|
||||
x = h.success(n)
|
||||
case msgIgnored:
|
||||
x = h.ignored(n)
|
||||
case msgFailure:
|
||||
x = h.failure(n)
|
||||
case msgRecord:
|
||||
x = h.record(n)
|
||||
default:
|
||||
return nil, errors.New(fmt.Sprintf("Unexpected tag at top level: %d", t))
|
||||
}
|
||||
err = h.getErr()
|
||||
return
|
||||
}
|
||||
|
||||
func (h *hydrator) ignored(n uint32) *ignored {
|
||||
h.assertLength("ignored", 0, n)
|
||||
if h.getErr() != nil {
|
||||
return nil
|
||||
}
|
||||
if h.boltLogger != nil {
|
||||
h.boltLogger.LogServerMessage(h.logId, "IGNORED")
|
||||
}
|
||||
return &h.cachedIgnored
|
||||
}
|
||||
|
||||
func (h *hydrator) failure(n uint32) *db.Neo4jError {
|
||||
h.assertLength("failure", 1, n)
|
||||
if h.getErr() != nil {
|
||||
return nil
|
||||
}
|
||||
dberr := db.Neo4jError{}
|
||||
h.unp.Next() // Detect map
|
||||
for maplen := h.unp.Len(); maplen > 0; maplen-- {
|
||||
h.unp.Next()
|
||||
key := h.unp.String()
|
||||
h.unp.Next()
|
||||
switch key {
|
||||
case "code":
|
||||
dberr.Code = h.unp.String()
|
||||
case "message":
|
||||
dberr.Msg = h.unp.String()
|
||||
default:
|
||||
// Do not fail on unknown value in map
|
||||
h.trash()
|
||||
}
|
||||
}
|
||||
if h.boltLogger != nil {
|
||||
h.boltLogger.LogServerMessage(h.logId, "FAILURE %s", loggableFailure(dberr))
|
||||
}
|
||||
return &dberr
|
||||
}
|
||||
|
||||
func (h *hydrator) success(n uint32) *success {
|
||||
h.assertLength("success", 1, n)
|
||||
if h.getErr() != nil {
|
||||
return nil
|
||||
}
|
||||
// Use cached success but clear it first
|
||||
h.cachedSuccess = success{}
|
||||
h.cachedSuccess.qid = -1
|
||||
succ := &h.cachedSuccess
|
||||
|
||||
h.unp.Next() // Detect map
|
||||
n = h.unp.Len()
|
||||
succ.num = n
|
||||
for ; n > 0; n-- {
|
||||
// Key
|
||||
h.unp.Next()
|
||||
key := h.unp.String()
|
||||
// Value
|
||||
h.unp.Next()
|
||||
switch key {
|
||||
case "fields":
|
||||
succ.fields = h.strings()
|
||||
case "t_first":
|
||||
succ.tfirst = h.unp.Int()
|
||||
case "qid":
|
||||
succ.qid = h.unp.Int()
|
||||
case "bookmark":
|
||||
succ.bookmark = h.unp.String()
|
||||
case "connection_id":
|
||||
succ.connectionId = h.unp.String()
|
||||
case "server":
|
||||
succ.server = h.unp.String()
|
||||
case "has_more":
|
||||
succ.hasMore = h.unp.Bool()
|
||||
case "t_last":
|
||||
succ.tlast = h.unp.Int()
|
||||
case "type":
|
||||
statementType := h.unp.String()
|
||||
switch statementType {
|
||||
case "r":
|
||||
succ.qtype = db.StatementTypeRead
|
||||
case "w":
|
||||
succ.qtype = db.StatementTypeWrite
|
||||
case "rw":
|
||||
succ.qtype = db.StatementTypeReadWrite
|
||||
case "s":
|
||||
succ.qtype = db.StatementTypeSchemaWrite
|
||||
default:
|
||||
h.setErr(&db.ProtocolError{
|
||||
MessageType: "success",
|
||||
Field: "type",
|
||||
Err: fmt.Sprintf("unrecognized success statement type %s", statementType),
|
||||
})
|
||||
}
|
||||
case "db":
|
||||
succ.db = h.unp.String()
|
||||
case "stats":
|
||||
succ.counters = h.successStats()
|
||||
case "plan":
|
||||
m := h.amap()
|
||||
succ.plan = parsePlan(m)
|
||||
case "profile":
|
||||
m := h.amap()
|
||||
succ.profile = parseProfile(m)
|
||||
case "notifications":
|
||||
l := h.array()
|
||||
succ.notifications = parseNotifications(l)
|
||||
case "rt":
|
||||
succ.routingTable = h.routingTable()
|
||||
case "hints":
|
||||
hints := h.amap()
|
||||
succ.configurationHints = hints
|
||||
default:
|
||||
// Unknown key, waste it
|
||||
h.trash()
|
||||
}
|
||||
}
|
||||
if h.boltLogger != nil {
|
||||
h.boltLogger.LogServerMessage(h.logId, "SUCCESS %s", loggableSuccess(*succ))
|
||||
}
|
||||
return succ
|
||||
}
|
||||
|
||||
func (h *hydrator) successStats() map[string]interface{} {
|
||||
n := h.unp.Len()
|
||||
if n == 0 {
|
||||
return nil
|
||||
}
|
||||
counts := make(map[string]interface{}, n)
|
||||
for ; n > 0; n-- {
|
||||
h.unp.Next()
|
||||
key := h.unp.String()
|
||||
h.unp.Next()
|
||||
val := h.parseStatValue(key)
|
||||
counts[key] = val
|
||||
}
|
||||
return counts
|
||||
}
|
||||
|
||||
func (h *hydrator) parseStatValue(key string) interface{} {
|
||||
var val interface{}
|
||||
switch key {
|
||||
case containsSystemUpdatesKey:
|
||||
boolValue := h.unp.Bool()
|
||||
val = &boolValue
|
||||
default:
|
||||
val = int(h.unp.Int())
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
// routingTable parses a routing table sent from the server. This is done
|
||||
// the 'hard' way to reduce number of allocations (would be easier to go via
|
||||
// a map) since it is called in normal flow (not that frequent...).
|
||||
func (h *hydrator) routingTable() *db.RoutingTable {
|
||||
rt := db.RoutingTable{}
|
||||
// Length of map
|
||||
nkeys := h.unp.Len()
|
||||
for ; nkeys > 0; nkeys-- {
|
||||
h.unp.Next()
|
||||
key := h.unp.String()
|
||||
h.unp.Next()
|
||||
switch key {
|
||||
case "ttl":
|
||||
rt.TimeToLive = int(h.unp.Int())
|
||||
case "servers":
|
||||
nservers := h.unp.Len()
|
||||
for ; nservers > 0; nservers-- {
|
||||
h.routingTableRole(&rt)
|
||||
}
|
||||
case "db":
|
||||
rt.DatabaseName = h.unp.String()
|
||||
default:
|
||||
// Unknown key, waste the value
|
||||
h.trash()
|
||||
}
|
||||
}
|
||||
return &rt
|
||||
}
|
||||
|
||||
func (h *hydrator) routingTableRole(rt *db.RoutingTable) {
|
||||
h.unp.Next()
|
||||
nkeys := h.unp.Len()
|
||||
var role string
|
||||
var addresses []string
|
||||
for ; nkeys > 0; nkeys-- {
|
||||
h.unp.Next()
|
||||
key := h.unp.String()
|
||||
h.unp.Next()
|
||||
switch key {
|
||||
case "role":
|
||||
role = h.unp.String()
|
||||
case "addresses":
|
||||
addresses = h.strings()
|
||||
default:
|
||||
// Unknown key, waste the value
|
||||
h.trash()
|
||||
}
|
||||
}
|
||||
switch role {
|
||||
case "READ":
|
||||
rt.Readers = addresses
|
||||
case "WRITE":
|
||||
rt.Writers = addresses
|
||||
case "ROUTE":
|
||||
rt.Routers = addresses
|
||||
}
|
||||
}
|
||||
|
||||
func (h *hydrator) strings() []string {
|
||||
n := h.unp.Len()
|
||||
slice := make([]string, n)
|
||||
for i := range slice {
|
||||
h.unp.Next()
|
||||
slice[i] = h.unp.String()
|
||||
}
|
||||
return slice
|
||||
}
|
||||
|
||||
func (h *hydrator) amap() map[string]interface{} {
|
||||
n := h.unp.Len()
|
||||
m := make(map[string]interface{}, n)
|
||||
for ; n > 0; n-- {
|
||||
h.unp.Next()
|
||||
key := h.unp.String()
|
||||
h.unp.Next()
|
||||
m[key] = h.value()
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func (h *hydrator) array() []interface{} {
|
||||
n := h.unp.Len()
|
||||
a := make([]interface{}, n)
|
||||
for i := range a {
|
||||
h.unp.Next()
|
||||
a[i] = h.value()
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
func (h *hydrator) record(n uint32) *db.Record {
|
||||
h.assertLength("record", 1, n)
|
||||
if h.getErr() != nil {
|
||||
return nil
|
||||
}
|
||||
rec := db.Record{}
|
||||
h.unp.Next() // Detect array
|
||||
n = h.unp.Len()
|
||||
rec.Values = make([]interface{}, n)
|
||||
for i := range rec.Values {
|
||||
h.unp.Next()
|
||||
rec.Values[i] = h.value()
|
||||
}
|
||||
if h.boltLogger != nil {
|
||||
h.boltLogger.LogServerMessage(h.logId, "RECORD %s", loggableList(rec.Values))
|
||||
}
|
||||
return &rec
|
||||
}
|
||||
|
||||
func (h *hydrator) value() interface{} {
|
||||
valueType := h.unp.Curr
|
||||
switch valueType {
|
||||
case packstream.PackedInt:
|
||||
return h.unp.Int()
|
||||
case packstream.PackedFloat:
|
||||
return h.unp.Float()
|
||||
case packstream.PackedStr:
|
||||
return h.unp.String()
|
||||
case packstream.PackedStruct:
|
||||
t := h.unp.StructTag()
|
||||
n := h.unp.Len()
|
||||
switch t {
|
||||
case 'N':
|
||||
return h.node(n)
|
||||
case 'R':
|
||||
return h.relationship(n)
|
||||
case 'r':
|
||||
return h.relationnode(n)
|
||||
case 'P':
|
||||
return h.path(n)
|
||||
case 'X':
|
||||
return h.point2d(n)
|
||||
case 'Y':
|
||||
return h.point3d(n)
|
||||
case 'F':
|
||||
return h.dateTimeOffset(n)
|
||||
case 'f':
|
||||
return h.dateTimeNamedZone(n)
|
||||
case 'd':
|
||||
return h.localDateTime(n)
|
||||
case 'D':
|
||||
return h.date(n)
|
||||
case 'T':
|
||||
return h.time(n)
|
||||
case 't':
|
||||
return h.localTime(n)
|
||||
case 'E':
|
||||
return h.duration(n)
|
||||
default:
|
||||
h.setErr(&db.ProtocolError{
|
||||
Err: fmt.Sprintf("Received unknown struct tag: %d", t),
|
||||
})
|
||||
return nil
|
||||
}
|
||||
case packstream.PackedByteArray:
|
||||
return h.unp.ByteArray()
|
||||
case packstream.PackedArray:
|
||||
return h.array()
|
||||
case packstream.PackedMap:
|
||||
return h.amap()
|
||||
case packstream.PackedNil:
|
||||
return nil
|
||||
case packstream.PackedTrue:
|
||||
return true
|
||||
case packstream.PackedFalse:
|
||||
return false
|
||||
default:
|
||||
h.setErr(&db.ProtocolError{
|
||||
Err: fmt.Sprintf("Received unknown packstream value type: %d", valueType),
|
||||
})
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Trashes current value
|
||||
func (h *hydrator) trash() {
|
||||
// TODO Less consuming implementation
|
||||
h.value()
|
||||
}
|
||||
|
||||
func (h *hydrator) node(num uint32) interface{} {
|
||||
h.assertLength("node", 3, num)
|
||||
if h.getErr() != nil {
|
||||
return nil
|
||||
}
|
||||
n := dbtype.Node{}
|
||||
h.unp.Next()
|
||||
n.Id = h.unp.Int()
|
||||
h.unp.Next()
|
||||
n.Labels = h.strings()
|
||||
h.unp.Next()
|
||||
n.Props = h.amap()
|
||||
return n
|
||||
}
|
||||
|
||||
func (h *hydrator) relationship(n uint32) interface{} {
|
||||
h.assertLength("relationship", 5, n)
|
||||
if h.getErr() != nil {
|
||||
return nil
|
||||
}
|
||||
r := dbtype.Relationship{}
|
||||
h.unp.Next()
|
||||
r.Id = h.unp.Int()
|
||||
h.unp.Next()
|
||||
r.StartId = h.unp.Int()
|
||||
h.unp.Next()
|
||||
r.EndId = h.unp.Int()
|
||||
h.unp.Next()
|
||||
r.Type = h.unp.String()
|
||||
h.unp.Next()
|
||||
r.Props = h.amap()
|
||||
return r
|
||||
}
|
||||
|
||||
func (h *hydrator) relationnode(n uint32) interface{} {
|
||||
h.assertLength("relationnode", 3, n)
|
||||
if h.getErr() != nil {
|
||||
return nil
|
||||
}
|
||||
r := relNode{}
|
||||
h.unp.Next()
|
||||
r.id = h.unp.Int()
|
||||
h.unp.Next()
|
||||
r.name = h.unp.String()
|
||||
h.unp.Next()
|
||||
r.props = h.amap()
|
||||
return &r
|
||||
}
|
||||
|
||||
func (h *hydrator) path(n uint32) interface{} {
|
||||
h.assertLength("path", 3, n)
|
||||
if h.getErr() != nil {
|
||||
return nil
|
||||
}
|
||||
// Array of nodes
|
||||
h.unp.Next()
|
||||
num := h.unp.Int()
|
||||
nodes := make([]dbtype.Node, num)
|
||||
for i := range nodes {
|
||||
h.unp.Next()
|
||||
node, ok := h.value().(dbtype.Node)
|
||||
if !ok {
|
||||
h.setErr(&db.ProtocolError{
|
||||
MessageType: "path",
|
||||
Field: "nodes",
|
||||
Err: "value could not be cast to Node",
|
||||
})
|
||||
return nil
|
||||
}
|
||||
nodes[i] = node
|
||||
}
|
||||
// Array of relnodes
|
||||
h.unp.Next()
|
||||
num = h.unp.Int()
|
||||
rnodes := make([]*relNode, num)
|
||||
for i := range rnodes {
|
||||
h.unp.Next()
|
||||
rnode, ok := h.value().(*relNode)
|
||||
if !ok {
|
||||
h.setErr(&db.ProtocolError{
|
||||
MessageType: "path",
|
||||
Field: "rnodes",
|
||||
Err: "value could be not cast to *relNode",
|
||||
})
|
||||
return nil
|
||||
}
|
||||
rnodes[i] = rnode
|
||||
}
|
||||
// Array of indexes
|
||||
h.unp.Next()
|
||||
num = h.unp.Int()
|
||||
indexes := make([]int, num)
|
||||
for i := range indexes {
|
||||
h.unp.Next()
|
||||
indexes[i] = int(h.unp.Int())
|
||||
}
|
||||
|
||||
if (len(indexes) & 0x01) == 1 {
|
||||
h.setErr(&db.ProtocolError{
|
||||
MessageType: "path",
|
||||
Field: "indices",
|
||||
Err: fmt.Sprintf("there should be an even number of indices, found %d", len(indexes)),
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
return buildPath(nodes, rnodes, indexes)
|
||||
}
|
||||
|
||||
func (h *hydrator) point2d(n uint32) interface{} {
|
||||
p := dbtype.Point2D{}
|
||||
h.unp.Next()
|
||||
p.SpatialRefId = uint32(h.unp.Int())
|
||||
h.unp.Next()
|
||||
p.X = h.unp.Float()
|
||||
h.unp.Next()
|
||||
p.Y = h.unp.Float()
|
||||
return p
|
||||
}
|
||||
|
||||
func (h *hydrator) point3d(n uint32) interface{} {
|
||||
p := dbtype.Point3D{}
|
||||
h.unp.Next()
|
||||
p.SpatialRefId = uint32(h.unp.Int())
|
||||
h.unp.Next()
|
||||
p.X = h.unp.Float()
|
||||
h.unp.Next()
|
||||
p.Y = h.unp.Float()
|
||||
h.unp.Next()
|
||||
p.Z = h.unp.Float()
|
||||
return p
|
||||
}
|
||||
|
||||
func (h *hydrator) dateTimeOffset(n uint32) interface{} {
|
||||
h.unp.Next()
|
||||
secs := h.unp.Int()
|
||||
h.unp.Next()
|
||||
nans := h.unp.Int()
|
||||
h.unp.Next()
|
||||
offs := h.unp.Int()
|
||||
t := time.Unix(secs, nans).UTC()
|
||||
l := time.FixedZone("Offset", int(offs))
|
||||
return time.Date(t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second(), t.Nanosecond(), l)
|
||||
}
|
||||
|
||||
func (h *hydrator) dateTimeNamedZone(n uint32) interface{} {
|
||||
h.unp.Next()
|
||||
secs := h.unp.Int()
|
||||
h.unp.Next()
|
||||
nans := h.unp.Int()
|
||||
h.unp.Next()
|
||||
zone := h.unp.String()
|
||||
t := time.Unix(secs, nans).UTC()
|
||||
l, err := time.LoadLocation(zone)
|
||||
if err != nil {
|
||||
h.setErr(&db.ProtocolError{
|
||||
MessageType: "dateTimeNamedZone",
|
||||
Field: "location",
|
||||
Err: err.Error(),
|
||||
})
|
||||
return nil
|
||||
}
|
||||
return time.Date(t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second(), t.Nanosecond(), l)
|
||||
}
|
||||
|
||||
func (h *hydrator) localDateTime(n uint32) interface{} {
|
||||
h.unp.Next()
|
||||
secs := h.unp.Int()
|
||||
h.unp.Next()
|
||||
nans := h.unp.Int()
|
||||
t := time.Unix(secs, nans).UTC()
|
||||
t = time.Date(t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second(), t.Nanosecond(), time.Local)
|
||||
return dbtype.LocalDateTime(t)
|
||||
}
|
||||
|
||||
func (h *hydrator) date(n uint32) interface{} {
|
||||
h.unp.Next()
|
||||
days := h.unp.Int()
|
||||
secs := days * 86400
|
||||
return dbtype.Date(time.Unix(secs, 0).UTC())
|
||||
}
|
||||
|
||||
func (h *hydrator) time(n uint32) interface{} {
|
||||
h.unp.Next()
|
||||
nans := h.unp.Int()
|
||||
h.unp.Next()
|
||||
offs := h.unp.Int()
|
||||
secs := nans / int64(time.Second)
|
||||
nans -= secs * int64(time.Second)
|
||||
l := time.FixedZone("Offset", int(offs))
|
||||
t := time.Date(0, 0, 0, 0, 0, int(secs), int(nans), l)
|
||||
return dbtype.Time(t)
|
||||
}
|
||||
|
||||
func (h *hydrator) localTime(n uint32) interface{} {
|
||||
h.unp.Next()
|
||||
nans := h.unp.Int()
|
||||
secs := nans / int64(time.Second)
|
||||
nans -= secs * int64(time.Second)
|
||||
t := time.Date(0, 0, 0, 0, 0, int(secs), int(nans), time.Local)
|
||||
return dbtype.LocalTime(t)
|
||||
}
|
||||
|
||||
func (h *hydrator) duration(n uint32) interface{} {
|
||||
h.unp.Next()
|
||||
mon := h.unp.Int()
|
||||
h.unp.Next()
|
||||
day := h.unp.Int()
|
||||
h.unp.Next()
|
||||
sec := h.unp.Int()
|
||||
h.unp.Next()
|
||||
nan := h.unp.Int()
|
||||
return dbtype.Duration{Months: mon, Days: day, Seconds: sec, Nanos: int(nan)}
|
||||
}
|
||||
|
||||
func parseNotifications(notificationsx []interface{}) []db.Notification {
|
||||
var notifications []db.Notification
|
||||
if notificationsx != nil {
|
||||
notifications = make([]db.Notification, 0, len(notificationsx))
|
||||
for _, x := range notificationsx {
|
||||
notificationx, ok := x.(map[string]interface{})
|
||||
if ok {
|
||||
notifications = append(notifications, parseNotification(notificationx))
|
||||
}
|
||||
}
|
||||
}
|
||||
return notifications
|
||||
}
|
||||
|
||||
func parsePlanOpIdArgsChildren(planx map[string]interface{}) (string, []string, map[string]interface{}, []interface{}) {
|
||||
operator, _ := planx["operatorType"].(string)
|
||||
identifiersx, _ := planx["identifiers"].([]interface{})
|
||||
arguments, _ := planx["args"].(map[string]interface{})
|
||||
|
||||
identifiers := make([]string, len(identifiersx))
|
||||
for i, id := range identifiersx {
|
||||
identifiers[i], _ = id.(string)
|
||||
}
|
||||
|
||||
childrenx, _ := planx["children"].([]interface{})
|
||||
|
||||
return operator, identifiers, arguments, childrenx
|
||||
}
|
||||
|
||||
func parsePlan(planx map[string]interface{}) *db.Plan {
|
||||
op, ids, args, childrenx := parsePlanOpIdArgsChildren(planx)
|
||||
plan := &db.Plan{
|
||||
Operator: op,
|
||||
Arguments: args,
|
||||
Identifiers: ids,
|
||||
}
|
||||
|
||||
plan.Children = make([]db.Plan, 0, len(childrenx))
|
||||
for _, c := range childrenx {
|
||||
childPlanx, _ := c.(map[string]interface{})
|
||||
if len(childPlanx) > 0 {
|
||||
childPlan := parsePlan(childPlanx)
|
||||
if childPlan != nil {
|
||||
plan.Children = append(plan.Children, *childPlan)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return plan
|
||||
}
|
||||
|
||||
func parseProfile(profilex map[string]interface{}) *db.ProfiledPlan {
|
||||
op, ids, args, childrenx := parsePlanOpIdArgsChildren(profilex)
|
||||
plan := &db.ProfiledPlan{
|
||||
Operator: op,
|
||||
Arguments: args,
|
||||
Identifiers: ids,
|
||||
}
|
||||
|
||||
plan.DbHits, _ = profilex["dbHits"].(int64)
|
||||
plan.Records, _ = profilex["rows"].(int64)
|
||||
|
||||
plan.Children = make([]db.ProfiledPlan, 0, len(childrenx))
|
||||
for _, c := range childrenx {
|
||||
childPlanx, _ := c.(map[string]interface{})
|
||||
if len(childPlanx) > 0 {
|
||||
childPlan := parseProfile(childPlanx)
|
||||
if childPlan != nil {
|
||||
if pageCacheMisses, ok := childPlanx["pageCacheMisses"]; ok {
|
||||
childPlan.PageCacheMisses = pageCacheMisses.(int64)
|
||||
}
|
||||
if pageCacheHits, ok := childPlanx["pageCacheHits"]; ok {
|
||||
childPlan.PageCacheHits = pageCacheHits.(int64)
|
||||
}
|
||||
if pageCacheHitRatio, ok := childPlanx["pageCacheHitRatio"]; ok {
|
||||
childPlan.PageCacheHitRatio = pageCacheHitRatio.(float64)
|
||||
}
|
||||
if planTime, ok := childPlanx["time"]; ok {
|
||||
childPlan.Time = planTime.(int64)
|
||||
}
|
||||
plan.Children = append(plan.Children, *childPlan)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return plan
|
||||
}
|
||||
|
||||
func parseNotification(m map[string]interface{}) db.Notification {
|
||||
n := db.Notification{}
|
||||
n.Code, _ = m["code"].(string)
|
||||
n.Description = m["description"].(string)
|
||||
n.Severity, _ = m["severity"].(string)
|
||||
n.Title, _ = m["title"].(string)
|
||||
posx, exists := m["position"].(map[string]interface{})
|
||||
if exists {
|
||||
pos := &db.InputPosition{}
|
||||
i, _ := posx["column"].(int64)
|
||||
pos.Column = int(i)
|
||||
i, _ = posx["line"].(int64)
|
||||
pos.Line = int(i)
|
||||
i, _ = posx["offset"].(int64)
|
||||
pos.Offset = int(i)
|
||||
n.Position = pos
|
||||
}
|
||||
|
||||
return n
|
||||
}
|
@ -1,47 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) "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 bolt
|
||||
|
||||
import (
|
||||
"github.com/neo4j/neo4j-go-driver/v4/neo4j/log"
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
type incoming struct {
|
||||
buf []byte // Reused buffer
|
||||
hyd hydrator
|
||||
connReadTimeout time.Duration
|
||||
logger log.Logger
|
||||
logName string
|
||||
logId string
|
||||
}
|
||||
|
||||
func (i *incoming) next(rd net.Conn) (interface{}, error) {
|
||||
// Get next message from transport layer
|
||||
var err error
|
||||
var msg []byte
|
||||
i.buf, msg, err = dechunkMessage(rd, i.buf, i.connReadTimeout, i.logger,
|
||||
i.logName, i.logId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return i.hyd.hydrate(msg)
|
||||
}
|
@ -1,41 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) "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 bolt
|
||||
|
||||
// Message struct tags
|
||||
// Shared between bolt versions
|
||||
const (
|
||||
msgReset byte = 0x0f
|
||||
msgRun byte = 0x10
|
||||
msgDiscardAll byte = 0x2f
|
||||
msgDiscardN = msgDiscardAll // Different name >= 4.0
|
||||
msgPullAll byte = 0x3f
|
||||
msgPullN = msgPullAll // Different name >= 4.0
|
||||
msgRecord byte = 0x71
|
||||
msgSuccess byte = 0x70
|
||||
msgIgnored byte = 0x7e
|
||||
msgFailure byte = 0x7f
|
||||
msgHello byte = 0x01
|
||||
msgGoodbye byte = 0x02
|
||||
msgBegin byte = 0x11
|
||||
msgCommit byte = 0x12
|
||||
msgRollback byte = 0x13
|
||||
msgRoute byte = 0x66 // > 4.2
|
||||
)
|
@ -1,410 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) "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 bolt
|
||||
|
||||
import (
|
||||
"github.com/neo4j/neo4j-go-driver/v4/neo4j/log"
|
||||
"io"
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
"github.com/neo4j/neo4j-go-driver/v4/neo4j/db"
|
||||
"github.com/neo4j/neo4j-go-driver/v4/neo4j/dbtype"
|
||||
"github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/packstream"
|
||||
)
|
||||
|
||||
type outgoing struct {
|
||||
chunker chunker
|
||||
packer packstream.Packer
|
||||
onErr func(err error)
|
||||
boltLogger log.BoltLogger
|
||||
logId string
|
||||
}
|
||||
|
||||
func (o *outgoing) begin() {
|
||||
o.chunker.beginMessage()
|
||||
o.packer.Begin(o.chunker.buf)
|
||||
}
|
||||
|
||||
func (o *outgoing) end() {
|
||||
buf, err := o.packer.End()
|
||||
o.chunker.buf = buf
|
||||
o.chunker.endMessage()
|
||||
if err != nil {
|
||||
o.onErr(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (o *outgoing) appendHello(hello map[string]interface{}) {
|
||||
if o.boltLogger != nil {
|
||||
o.boltLogger.LogClientMessage(o.logId, "HELLO %s", loggableDictionary(hello))
|
||||
}
|
||||
o.begin()
|
||||
o.packer.StructHeader(byte(msgHello), 1)
|
||||
o.packMap(hello)
|
||||
o.end()
|
||||
}
|
||||
|
||||
func (o *outgoing) appendBegin(meta map[string]interface{}) {
|
||||
if o.boltLogger != nil {
|
||||
o.boltLogger.LogClientMessage(o.logId, "BEGIN %s", loggableDictionary(meta))
|
||||
}
|
||||
o.begin()
|
||||
o.packer.StructHeader(byte(msgBegin), 1)
|
||||
o.packMap(meta)
|
||||
o.end()
|
||||
}
|
||||
|
||||
func (o *outgoing) appendCommit() {
|
||||
if o.boltLogger != nil {
|
||||
o.boltLogger.LogClientMessage(o.logId, "COMMIT")
|
||||
}
|
||||
o.begin()
|
||||
o.packer.StructHeader(byte(msgCommit), 0)
|
||||
o.end()
|
||||
}
|
||||
|
||||
func (o *outgoing) appendRollback() {
|
||||
if o.boltLogger != nil {
|
||||
o.boltLogger.LogClientMessage(o.logId, "ROLLBACK")
|
||||
}
|
||||
o.begin()
|
||||
o.packer.StructHeader(byte(msgRollback), 0)
|
||||
o.end()
|
||||
}
|
||||
|
||||
func (o *outgoing) appendRun(cypher string, params, meta map[string]interface{}) {
|
||||
if o.boltLogger != nil {
|
||||
o.boltLogger.LogClientMessage(o.logId, "RUN %q %s %s", cypher, loggableDictionary(params), loggableDictionary(meta))
|
||||
}
|
||||
o.begin()
|
||||
o.packer.StructHeader(byte(msgRun), 3)
|
||||
o.packer.String(cypher)
|
||||
o.packMap(params)
|
||||
o.packMap(meta)
|
||||
o.end()
|
||||
}
|
||||
|
||||
func (o *outgoing) appendPullN(n int) {
|
||||
if o.boltLogger != nil {
|
||||
o.boltLogger.LogClientMessage(o.logId, "PULL %s", loggableDictionary{"n": n})
|
||||
}
|
||||
o.begin()
|
||||
o.packer.StructHeader(byte(msgPullN), 1)
|
||||
o.packer.MapHeader(1)
|
||||
o.packer.String("n")
|
||||
o.packer.Int(n)
|
||||
o.end()
|
||||
}
|
||||
|
||||
func (o *outgoing) appendPullNQid(n int, qid int64) {
|
||||
if o.boltLogger != nil {
|
||||
o.boltLogger.LogClientMessage(o.logId, "PULL %s", loggableDictionary{"n": n, "qid": qid})
|
||||
}
|
||||
o.begin()
|
||||
o.packer.StructHeader(byte(msgPullN), 1)
|
||||
o.packer.MapHeader(2)
|
||||
o.packer.String("n")
|
||||
o.packer.Int(n)
|
||||
o.packer.String("qid")
|
||||
o.packer.Int64(qid)
|
||||
o.end()
|
||||
}
|
||||
|
||||
func (o *outgoing) appendDiscardN(n int) {
|
||||
if o.boltLogger != nil {
|
||||
o.boltLogger.LogClientMessage(o.logId, "DISCARD %s", loggableDictionary{"n": n})
|
||||
}
|
||||
o.begin()
|
||||
o.packer.StructHeader(byte(msgDiscardN), 1)
|
||||
o.packer.MapHeader(1)
|
||||
o.packer.String("n")
|
||||
o.packer.Int(n)
|
||||
o.end()
|
||||
}
|
||||
|
||||
func (o *outgoing) appendDiscardNQid(n int, qid int64) {
|
||||
if o.boltLogger != nil {
|
||||
o.boltLogger.LogClientMessage(o.logId, "DISCARD %s", loggableDictionary{"n": n, "qid": qid})
|
||||
}
|
||||
o.begin()
|
||||
o.packer.StructHeader(byte(msgDiscardN), 1)
|
||||
o.packer.MapHeader(2)
|
||||
o.packer.String("n")
|
||||
o.packer.Int(n)
|
||||
o.packer.String("qid")
|
||||
o.packer.Int64(qid)
|
||||
o.end()
|
||||
}
|
||||
|
||||
func (o *outgoing) appendPullAll() {
|
||||
if o.boltLogger != nil {
|
||||
o.boltLogger.LogClientMessage(o.logId, "PULL ALL")
|
||||
}
|
||||
o.begin()
|
||||
o.packer.StructHeader(byte(msgPullAll), 0)
|
||||
o.end()
|
||||
}
|
||||
|
||||
// Only valid for V4.3
|
||||
func (o *outgoing) appendRouteToV43(context map[string]string, bookmarks []string, database string) {
|
||||
if o.boltLogger != nil {
|
||||
o.boltLogger.LogClientMessage(o.logId, "ROUTE %s %s %q", loggableStringDictionary(context), loggableStringList(bookmarks), database)
|
||||
}
|
||||
o.begin()
|
||||
o.packer.StructHeader(byte(msgRoute), 3)
|
||||
o.packer.MapHeader(len(context))
|
||||
for k, v := range context {
|
||||
o.packer.String(k)
|
||||
o.packer.String(v)
|
||||
}
|
||||
o.packer.ArrayHeader(len(bookmarks))
|
||||
for _, bookmark := range bookmarks {
|
||||
o.packer.String(bookmark)
|
||||
}
|
||||
if database == db.DefaultDatabase {
|
||||
o.packer.Nil()
|
||||
} else {
|
||||
o.packer.String(database)
|
||||
}
|
||||
o.end()
|
||||
}
|
||||
|
||||
func (o *outgoing) appendRoute(context map[string]string, bookmarks []string, what map[string]interface{}) {
|
||||
if o.boltLogger != nil {
|
||||
o.boltLogger.LogClientMessage(o.logId, "ROUTE %s %s %s", loggableStringDictionary(context), loggableStringList(bookmarks), loggableDictionary(what))
|
||||
}
|
||||
o.begin()
|
||||
o.packer.StructHeader(byte(msgRoute), 3)
|
||||
o.packer.MapHeader(len(context))
|
||||
for k, v := range context {
|
||||
o.packer.String(k)
|
||||
o.packer.String(v)
|
||||
}
|
||||
o.packer.ArrayHeader(len(bookmarks))
|
||||
for _, bookmark := range bookmarks {
|
||||
o.packer.String(bookmark)
|
||||
}
|
||||
o.packMap(what)
|
||||
o.end()
|
||||
}
|
||||
|
||||
func (o *outgoing) appendReset() {
|
||||
if o.boltLogger != nil {
|
||||
o.boltLogger.LogClientMessage(o.logId, "RESET")
|
||||
}
|
||||
o.begin()
|
||||
o.packer.StructHeader(byte(msgReset), 0)
|
||||
o.end()
|
||||
}
|
||||
|
||||
func (o *outgoing) appendGoodbye() {
|
||||
if o.boltLogger != nil {
|
||||
o.boltLogger.LogClientMessage(o.logId, "GOODBYE")
|
||||
}
|
||||
o.begin()
|
||||
o.packer.StructHeader(byte(msgGoodbye), 0)
|
||||
o.end()
|
||||
}
|
||||
|
||||
// For tests
|
||||
func (o *outgoing) appendX(tag byte, fields ...interface{}) {
|
||||
o.begin()
|
||||
o.packer.StructHeader(tag, len(fields))
|
||||
for _, f := range fields {
|
||||
o.packX(f)
|
||||
}
|
||||
o.end()
|
||||
}
|
||||
|
||||
func (o *outgoing) send(wr io.Writer) {
|
||||
err := o.chunker.send(wr)
|
||||
if err != nil {
|
||||
o.onErr(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (o *outgoing) packMap(m map[string]interface{}) {
|
||||
o.packer.MapHeader(len(m))
|
||||
for k, v := range m {
|
||||
o.packer.String(k)
|
||||
o.packX(v)
|
||||
}
|
||||
}
|
||||
|
||||
func (o *outgoing) packStruct(x interface{}) {
|
||||
switch v := x.(type) {
|
||||
case *dbtype.Point2D:
|
||||
o.packer.StructHeader('X', 3)
|
||||
o.packer.Uint32(v.SpatialRefId)
|
||||
o.packer.Float64(v.X)
|
||||
o.packer.Float64(v.Y)
|
||||
case dbtype.Point2D:
|
||||
o.packer.StructHeader('X', 3)
|
||||
o.packer.Uint32(v.SpatialRefId)
|
||||
o.packer.Float64(v.X)
|
||||
o.packer.Float64(v.Y)
|
||||
case *dbtype.Point3D:
|
||||
o.packer.StructHeader('Y', 4)
|
||||
o.packer.Uint32(v.SpatialRefId)
|
||||
o.packer.Float64(v.X)
|
||||
o.packer.Float64(v.Y)
|
||||
o.packer.Float64(v.Z)
|
||||
case dbtype.Point3D:
|
||||
o.packer.StructHeader('Y', 4)
|
||||
o.packer.Uint32(v.SpatialRefId)
|
||||
o.packer.Float64(v.X)
|
||||
o.packer.Float64(v.Y)
|
||||
o.packer.Float64(v.Z)
|
||||
case time.Time:
|
||||
zone, offset := v.Zone()
|
||||
secs := v.Unix() + int64(offset)
|
||||
nanos := v.Nanosecond()
|
||||
if zone == "Offset" {
|
||||
o.packer.StructHeader('F', 3)
|
||||
o.packer.Int64(secs)
|
||||
o.packer.Int(nanos)
|
||||
o.packer.Int(offset)
|
||||
} else {
|
||||
o.packer.StructHeader('f', 3)
|
||||
o.packer.Int64(secs)
|
||||
o.packer.Int(nanos)
|
||||
o.packer.String(v.Location().String())
|
||||
}
|
||||
case dbtype.LocalDateTime:
|
||||
t := time.Time(v)
|
||||
_, offset := t.Zone()
|
||||
secs := t.Unix() + int64(offset)
|
||||
o.packer.StructHeader('d', 2)
|
||||
o.packer.Int64(secs)
|
||||
o.packer.Int(t.Nanosecond())
|
||||
case dbtype.Date:
|
||||
t := time.Time(v)
|
||||
secs := t.Unix()
|
||||
_, offset := t.Zone()
|
||||
secs += int64(offset)
|
||||
days := secs / (60 * 60 * 24)
|
||||
o.packer.StructHeader('D', 1)
|
||||
o.packer.Int64(days)
|
||||
case dbtype.Time:
|
||||
t := time.Time(v)
|
||||
_, tzOffsetSecs := t.Zone()
|
||||
d := t.Sub(
|
||||
time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, t.Location()))
|
||||
o.packer.StructHeader('T', 2)
|
||||
o.packer.Int64(d.Nanoseconds())
|
||||
o.packer.Int(tzOffsetSecs)
|
||||
case dbtype.LocalTime:
|
||||
t := time.Time(v)
|
||||
nanos := int64(time.Hour)*int64(t.Hour()) +
|
||||
int64(time.Minute)*int64(t.Minute()) +
|
||||
int64(time.Second)*int64(t.Second()) +
|
||||
int64(t.Nanosecond())
|
||||
o.packer.StructHeader('t', 1)
|
||||
o.packer.Int64(nanos)
|
||||
case dbtype.Duration:
|
||||
o.packer.StructHeader('E', 4)
|
||||
o.packer.Int64(v.Months)
|
||||
o.packer.Int64(v.Days)
|
||||
o.packer.Int64(v.Seconds)
|
||||
o.packer.Int(v.Nanos)
|
||||
default:
|
||||
o.onErr(&db.UnsupportedTypeError{Type: reflect.TypeOf(x)})
|
||||
}
|
||||
}
|
||||
|
||||
func (o *outgoing) packX(x interface{}) {
|
||||
if x == nil {
|
||||
o.packer.Nil()
|
||||
return
|
||||
}
|
||||
|
||||
v := reflect.ValueOf(x)
|
||||
switch v.Kind() {
|
||||
case reflect.Bool:
|
||||
o.packer.Bool(v.Bool())
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
o.packer.Int64(v.Int())
|
||||
case reflect.Uint8, reflect.Uint16, reflect.Uint32:
|
||||
o.packer.Uint32(uint32(v.Uint()))
|
||||
case reflect.Uint64, reflect.Uint:
|
||||
o.packer.Uint64(v.Uint())
|
||||
case reflect.Float32, reflect.Float64:
|
||||
o.packer.Float64(v.Float())
|
||||
case reflect.String:
|
||||
o.packer.String(v.String())
|
||||
case reflect.Ptr:
|
||||
if v.IsNil() {
|
||||
o.packer.Nil()
|
||||
return
|
||||
}
|
||||
// Inspect what the pointer points to
|
||||
i := reflect.Indirect(v)
|
||||
switch i.Kind() {
|
||||
case reflect.Struct:
|
||||
o.packStruct(x)
|
||||
default:
|
||||
o.packX(i.Interface())
|
||||
}
|
||||
case reflect.Struct:
|
||||
o.packStruct(x)
|
||||
case reflect.Slice:
|
||||
// Optimizations
|
||||
switch s := x.(type) {
|
||||
case []byte:
|
||||
o.packer.Bytes(s) // Not just optimization
|
||||
case []int:
|
||||
o.packer.Ints(s)
|
||||
case []int64:
|
||||
o.packer.Int64s(s)
|
||||
case []string:
|
||||
o.packer.Strings(s)
|
||||
case []float64:
|
||||
o.packer.Float64s(s)
|
||||
default:
|
||||
num := v.Len()
|
||||
o.packer.ArrayHeader(num)
|
||||
for i := 0; i < num; i++ {
|
||||
o.packX(v.Index(i).Interface())
|
||||
}
|
||||
}
|
||||
case reflect.Map:
|
||||
// Optimizations
|
||||
switch m := x.(type) {
|
||||
case map[string]int:
|
||||
o.packer.IntMap(m)
|
||||
case map[string]string:
|
||||
o.packer.StringMap(m)
|
||||
default:
|
||||
t := reflect.TypeOf(x)
|
||||
if t.Key().Kind() != reflect.String {
|
||||
o.onErr(&db.UnsupportedTypeError{Type: reflect.TypeOf(x)})
|
||||
return
|
||||
}
|
||||
o.packer.MapHeader(v.Len())
|
||||
// TODO Use MapRange when min Go version is >= 1.12
|
||||
for _, ki := range v.MapKeys() {
|
||||
o.packer.String(ki.String())
|
||||
o.packX(v.MapIndex(ki).Interface())
|
||||
}
|
||||
}
|
||||
default:
|
||||
o.onErr(&db.UnsupportedTypeError{Type: reflect.TypeOf(x)})
|
||||
}
|
||||
}
|
74
vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/bolt/parseroutingtable.go
generated
vendored
74
vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/bolt/parseroutingtable.go
generated
vendored
@ -1,74 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) "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 bolt
|
||||
|
||||
import (
|
||||
"github.com/neo4j/neo4j-go-driver/v4/neo4j/db"
|
||||
)
|
||||
|
||||
// Parses a record assumed to contain a routing table into common db API routing table struct
|
||||
// Returns nil if error while parsing
|
||||
func parseRoutingTableRecord(rec *db.Record) *db.RoutingTable {
|
||||
ttl, ok := rec.Values[0].(int64)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
listOfX, ok := rec.Values[1].([]interface{})
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
table := &db.RoutingTable{
|
||||
TimeToLive: int(ttl),
|
||||
}
|
||||
|
||||
for _, x := range listOfX {
|
||||
// Each x should be a map consisting of addresses and the role
|
||||
m, ok := x.(map[string]interface{})
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
addressesX, ok := m["addresses"].([]interface{})
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
addresses := make([]string, len(addressesX))
|
||||
for i, addrX := range addressesX {
|
||||
addr, ok := addrX.(string)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
addresses[i] = addr
|
||||
}
|
||||
role, ok := m["role"].(string)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
switch role {
|
||||
case "READ":
|
||||
table.Readers = addresses
|
||||
case "WRITE":
|
||||
table.Writers = addresses
|
||||
case "ROUTE":
|
||||
table.Routers = addresses
|
||||
}
|
||||
}
|
||||
return table
|
||||
}
|
@ -1,81 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) "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 bolt
|
||||
|
||||
import (
|
||||
"github.com/neo4j/neo4j-go-driver/v4/neo4j/dbtype"
|
||||
)
|
||||
|
||||
// Intermediate representation of part of path
|
||||
type relNode struct {
|
||||
id int64
|
||||
name string
|
||||
props map[string]interface{}
|
||||
}
|
||||
|
||||
// buildPath builds a path from Bolt representation
|
||||
func buildPath(nodes []dbtype.Node, relNodes []*relNode, indexes []int) dbtype.Path {
|
||||
num := len(indexes) / 2
|
||||
if num == 0 {
|
||||
var path dbtype.Path
|
||||
if len(nodes) > 0 {
|
||||
// there could be a single disconnected node
|
||||
path.Nodes = nodes
|
||||
}
|
||||
return path
|
||||
}
|
||||
rels := make([]dbtype.Relationship, 0, num)
|
||||
|
||||
i := 0
|
||||
n1 := nodes[0]
|
||||
for num > 0 {
|
||||
relni := indexes[i]
|
||||
i++
|
||||
n2i := indexes[i]
|
||||
i++
|
||||
num--
|
||||
var reln *relNode
|
||||
var n1start bool
|
||||
if relni < 0 {
|
||||
reln = relNodes[(relni*-1)-1]
|
||||
} else {
|
||||
reln = relNodes[relni-1]
|
||||
n1start = true
|
||||
}
|
||||
n2 := nodes[n2i]
|
||||
|
||||
rel := dbtype.Relationship{
|
||||
Id: reln.id,
|
||||
Type: reln.name,
|
||||
Props: reln.props,
|
||||
}
|
||||
if n1start {
|
||||
rel.StartId = n1.Id
|
||||
rel.EndId = n2.Id
|
||||
} else {
|
||||
rel.StartId = n2.Id
|
||||
rel.EndId = n1.Id
|
||||
}
|
||||
rels = append(rels, rel)
|
||||
n1 = n2
|
||||
}
|
||||
|
||||
return dbtype.Path{Nodes: nodes, Relationships: rels}
|
||||
}
|
@ -1,152 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) "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 bolt
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/neo4j/neo4j-go-driver/v4/neo4j/db"
|
||||
)
|
||||
|
||||
type stream struct {
|
||||
keys []string
|
||||
fifo list.List
|
||||
sum *db.Summary
|
||||
err error
|
||||
qid int64
|
||||
fetchSize int
|
||||
key int64
|
||||
}
|
||||
|
||||
// Acts on buffered data, first return value indicates if buffering
|
||||
// is active or not.
|
||||
func (s *stream) bufferedNext() (bool, *db.Record, *db.Summary, error) {
|
||||
e := s.fifo.Front()
|
||||
if e != nil {
|
||||
s.fifo.Remove(e)
|
||||
return true, e.Value.(*db.Record), nil, nil
|
||||
}
|
||||
if s.err != nil {
|
||||
return true, nil, nil, s.err
|
||||
}
|
||||
if s.sum != nil {
|
||||
return true, nil, s.sum, nil
|
||||
}
|
||||
|
||||
return false, nil, nil, nil
|
||||
}
|
||||
|
||||
// Delayed error until fifo emptied
|
||||
func (s *stream) Err() error {
|
||||
if s.fifo.Len() > 0 {
|
||||
return nil
|
||||
}
|
||||
return s.err
|
||||
}
|
||||
|
||||
func (s *stream) push(rec *db.Record) {
|
||||
s.fifo.PushBack(rec)
|
||||
}
|
||||
|
||||
// Only need to keep track of current stream. Client keeps track of other
|
||||
// open streams and a key in each stream is used to validate if it belongs to
|
||||
// current bolt connection or not.
|
||||
type openstreams struct {
|
||||
curr *stream
|
||||
num int
|
||||
key int64
|
||||
}
|
||||
|
||||
var (
|
||||
invalidStream = errors.New("Invalid stream handle")
|
||||
)
|
||||
|
||||
// Adds a new open stream and sets it as current.
|
||||
// There should NOT be a current stream .
|
||||
func (o *openstreams) attach(s *stream) {
|
||||
if o.curr != nil {
|
||||
return
|
||||
}
|
||||
// Track number of open streams and set the stream as current
|
||||
o.num++
|
||||
o.curr = s
|
||||
s.key = o.key
|
||||
}
|
||||
|
||||
// Detaches the current stream from being current and
|
||||
// removes it from set of open streams it is no longer open.
|
||||
// The stream should be either in failed state or completed.
|
||||
func (o *openstreams) detach(sum *db.Summary, err error) {
|
||||
if o.curr == nil {
|
||||
return
|
||||
}
|
||||
|
||||
o.curr.sum = sum
|
||||
o.curr.err = err
|
||||
o.remove(o.curr)
|
||||
}
|
||||
|
||||
// Streams can be paused when they have received a "has_more" response from server
|
||||
// Pauses the current stream
|
||||
func (o *openstreams) pause() {
|
||||
o.curr = nil
|
||||
}
|
||||
|
||||
// When resuming a stream a new PULL message needs to be sent.
|
||||
func (o *openstreams) resume(s *stream) {
|
||||
if o.curr != nil {
|
||||
return
|
||||
}
|
||||
o.curr = s
|
||||
}
|
||||
|
||||
// Removes the stream by disabling its key and removing it from the count of streams.
|
||||
// If the stream is current the current is set to nil.
|
||||
func (o *openstreams) remove(s *stream) {
|
||||
o.num--
|
||||
s.key = 0
|
||||
if o.curr == s {
|
||||
o.curr = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (o *openstreams) reset() {
|
||||
o.num = 0
|
||||
o.curr = nil
|
||||
o.key = time.Now().UnixNano()
|
||||
}
|
||||
|
||||
// Checks that the handle represents a stream but not necessarily a stream belonging
|
||||
// to this set of open streams.
|
||||
func (o openstreams) getUnsafe(h db.StreamHandle) (*stream, error) {
|
||||
stream, ok := h.(*stream)
|
||||
if !ok || stream == nil {
|
||||
return nil, invalidStream
|
||||
}
|
||||
return stream, nil
|
||||
}
|
||||
|
||||
func (o openstreams) isSafe(s *stream) error {
|
||||
if s.key == o.key {
|
||||
return nil
|
||||
}
|
||||
return invalidStream
|
||||
}
|
100
vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/connector/connector.go
generated
vendored
100
vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/connector/connector.go
generated
vendored
@ -1,100 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) "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 connector is responsible for connecting to a database server.
|
||||
package connector
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/neo4j/neo4j-go-driver/v4/neo4j/db"
|
||||
"github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/bolt"
|
||||
"github.com/neo4j/neo4j-go-driver/v4/neo4j/log"
|
||||
)
|
||||
|
||||
type Connector struct {
|
||||
SkipEncryption bool
|
||||
SkipVerify bool
|
||||
RootCAs *x509.CertPool
|
||||
DialTimeout time.Duration
|
||||
SocketKeepAlive bool
|
||||
Auth map[string]interface{}
|
||||
Log log.Logger
|
||||
UserAgent string
|
||||
RoutingContext map[string]string
|
||||
Network string
|
||||
}
|
||||
|
||||
type ConnectError struct {
|
||||
inner error
|
||||
}
|
||||
|
||||
func (e *ConnectError) Error() string {
|
||||
return e.inner.Error()
|
||||
}
|
||||
|
||||
type TlsError struct {
|
||||
inner error
|
||||
}
|
||||
|
||||
func (e *TlsError) Error() string {
|
||||
return e.inner.Error()
|
||||
}
|
||||
|
||||
func (c Connector) Connect(address string, boltLogger log.BoltLogger) (db.Connection, error) {
|
||||
dialer := net.Dialer{Timeout: c.DialTimeout}
|
||||
if !c.SocketKeepAlive {
|
||||
dialer.KeepAlive = -1 * time.Second // Turns keep-alive off
|
||||
}
|
||||
|
||||
conn, err := dialer.Dial(c.Network, address)
|
||||
if err != nil {
|
||||
return nil, &ConnectError{inner: err}
|
||||
}
|
||||
|
||||
// TLS not requested, perform Bolt handshake
|
||||
if c.SkipEncryption {
|
||||
return bolt.Connect(address, conn, c.Auth, c.UserAgent, c.RoutingContext, c.Log, boltLogger)
|
||||
}
|
||||
|
||||
// TLS requested, continue with handshake
|
||||
serverName, _, err := net.SplitHostPort(address)
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
return nil, err
|
||||
}
|
||||
config := tls.Config{InsecureSkipVerify: c.SkipVerify, RootCAs: c.RootCAs, ServerName: serverName}
|
||||
tlsconn := tls.Client(conn, &config)
|
||||
err = tlsconn.Handshake()
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
// Give a bit nicer error message
|
||||
err = errors.New("Remote end closed the connection, check that TLS is enabled on the server")
|
||||
}
|
||||
conn.Close()
|
||||
return nil, &TlsError{inner: err}
|
||||
}
|
||||
// Perform Bolt handshake
|
||||
return bolt.Connect(address, tlsconn, c.Auth, c.UserAgent, c.RoutingContext, c.Log, boltLogger)
|
||||
}
|
44
vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/packstream/errors.go
generated
vendored
44
vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/packstream/errors.go
generated
vendored
@ -1,44 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) "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 packstream handles serialization of data sent to database server and
|
||||
// deserialization of data received from database server.
|
||||
package packstream
|
||||
|
||||
type OverflowError struct {
|
||||
msg string
|
||||
}
|
||||
|
||||
func (e *OverflowError) Error() string {
|
||||
return e.msg
|
||||
}
|
||||
|
||||
type IoError struct{}
|
||||
|
||||
func (e *IoError) Error() string {
|
||||
return "IO error"
|
||||
}
|
||||
|
||||
type UnpackError struct {
|
||||
msg string
|
||||
}
|
||||
|
||||
func (e *UnpackError) Error() string {
|
||||
return e.msg
|
||||
}
|
242
vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/packstream/packer.go
generated
vendored
242
vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/packstream/packer.go
generated
vendored
@ -1,242 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) "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 packstream
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"math"
|
||||
)
|
||||
|
||||
type Packer struct {
|
||||
buf []byte
|
||||
err error
|
||||
}
|
||||
|
||||
func (p *Packer) Begin(buf []byte) {
|
||||
p.buf = buf
|
||||
p.err = nil
|
||||
}
|
||||
|
||||
func (p *Packer) End() ([]byte, error) {
|
||||
return p.buf, p.err
|
||||
|
||||
}
|
||||
|
||||
func (p *Packer) setErr(err error) {
|
||||
if p.err == nil {
|
||||
p.err = err
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Packer) StructHeader(tag byte, num int) {
|
||||
if num > 0x0f {
|
||||
p.setErr(&OverflowError{msg: "Trying to pack struct with too many fields"})
|
||||
return
|
||||
}
|
||||
|
||||
p.buf = append(p.buf, 0xb0+byte(num), byte(tag))
|
||||
}
|
||||
|
||||
func (p *Packer) Int64(i int64) {
|
||||
switch {
|
||||
case int64(-0x10) <= i && i < int64(0x80):
|
||||
p.buf = append(p.buf, byte(i))
|
||||
case int64(-0x80) <= i && i < int64(-0x10):
|
||||
p.buf = append(p.buf, 0xc8, byte(i))
|
||||
case int64(-0x8000) <= i && i < int64(0x8000):
|
||||
buf := [3]byte{0xc9}
|
||||
binary.BigEndian.PutUint16(buf[1:], uint16(i))
|
||||
p.buf = append(p.buf, buf[:]...)
|
||||
case int64(-0x80000000) <= i && i < int64(0x80000000):
|
||||
buf := [5]byte{0xca}
|
||||
binary.BigEndian.PutUint32(buf[1:], uint32(i))
|
||||
p.buf = append(p.buf, buf[:]...)
|
||||
default:
|
||||
buf := [9]byte{0xcb}
|
||||
binary.BigEndian.PutUint64(buf[1:], uint64(i))
|
||||
p.buf = append(p.buf, buf[:]...)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Packer) Int32(i int32) {
|
||||
p.Int64(int64(i))
|
||||
}
|
||||
|
||||
func (p *Packer) Int16(i int16) {
|
||||
p.Int64(int64(i))
|
||||
}
|
||||
|
||||
func (p *Packer) Int8(i int8) {
|
||||
p.Int64(int64(i))
|
||||
}
|
||||
|
||||
func (p *Packer) Int(i int) {
|
||||
p.Int64(int64(i))
|
||||
}
|
||||
|
||||
func (p *Packer) Uint64(i uint64) {
|
||||
p.checkOverflowInt(i)
|
||||
p.Int64(int64(i))
|
||||
}
|
||||
|
||||
func (p *Packer) Uint32(i uint32) {
|
||||
p.Int64(int64(i))
|
||||
}
|
||||
|
||||
func (p *Packer) Uint16(i uint16) {
|
||||
p.Int64(int64(i))
|
||||
}
|
||||
|
||||
func (p *Packer) Uint8(i uint8) {
|
||||
p.Int64(int64(i))
|
||||
}
|
||||
|
||||
func (p *Packer) Float64(f float64) {
|
||||
buf := [9]byte{0xc1}
|
||||
binary.BigEndian.PutUint64(buf[1:], math.Float64bits(f))
|
||||
p.buf = append(p.buf, buf[:]...)
|
||||
}
|
||||
|
||||
func (p *Packer) Float32(f float32) {
|
||||
p.Float64(float64(f))
|
||||
}
|
||||
|
||||
func (p *Packer) listHeader(ll int, shortOffset, longOffset byte) {
|
||||
l := int64(ll)
|
||||
hdr := make([]byte, 0, 1+4)
|
||||
if l < 0x10 {
|
||||
hdr = append(hdr, shortOffset+byte(l))
|
||||
} else {
|
||||
switch {
|
||||
case l < 0x100:
|
||||
hdr = append(hdr, []byte{longOffset, byte(l)}...)
|
||||
case l < 0x10000:
|
||||
hdr = hdr[:1+2]
|
||||
hdr[0] = longOffset + 1
|
||||
binary.BigEndian.PutUint16(hdr[1:], uint16(l))
|
||||
case l < math.MaxUint32:
|
||||
hdr = hdr[:1+4]
|
||||
hdr[0] = longOffset + 2
|
||||
binary.BigEndian.PutUint32(hdr[1:], uint32(l))
|
||||
default:
|
||||
p.err = &OverflowError{msg: fmt.Sprintf("Trying to pack too large list of size %d ", l)}
|
||||
return
|
||||
}
|
||||
}
|
||||
p.buf = append(p.buf, hdr...)
|
||||
}
|
||||
|
||||
func (p *Packer) String(s string) {
|
||||
p.listHeader(len(s), 0x80, 0xd0)
|
||||
p.buf = append(p.buf, []byte(s)...)
|
||||
}
|
||||
|
||||
func (p *Packer) Strings(ss []string) {
|
||||
p.listHeader(len(ss), 0x90, 0xd4)
|
||||
for _, s := range ss {
|
||||
p.String(s)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Packer) Ints(ii []int) {
|
||||
p.listHeader(len(ii), 0x90, 0xd4)
|
||||
for _, i := range ii {
|
||||
p.Int(i)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Packer) Int64s(ii []int64) {
|
||||
p.listHeader(len(ii), 0x90, 0xd4)
|
||||
for _, i := range ii {
|
||||
p.Int64(i)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Packer) Float64s(ii []float64) {
|
||||
p.listHeader(len(ii), 0x90, 0xd4)
|
||||
for _, i := range ii {
|
||||
p.Float64(i)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Packer) ArrayHeader(l int) {
|
||||
p.listHeader(l, 0x90, 0xd4)
|
||||
}
|
||||
|
||||
func (p *Packer) MapHeader(l int) {
|
||||
p.listHeader(l, 0xa0, 0xd8)
|
||||
}
|
||||
|
||||
func (p *Packer) IntMap(m map[string]int) {
|
||||
p.listHeader(len(m), 0xa0, 0xd8)
|
||||
for k, v := range m {
|
||||
p.String(k)
|
||||
p.Int(v)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Packer) StringMap(m map[string]string) {
|
||||
p.listHeader(len(m), 0xa0, 0xd8)
|
||||
for k, v := range m {
|
||||
p.String(k)
|
||||
p.String(v)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Packer) Bytes(b []byte) {
|
||||
hdr := make([]byte, 0, 1+4)
|
||||
l := int64(len(b))
|
||||
switch {
|
||||
case l < 0x100:
|
||||
hdr = append(hdr, 0xcc, byte(l))
|
||||
case l < 0x10000:
|
||||
hdr = hdr[:1+2]
|
||||
hdr[0] = 0xcd
|
||||
binary.BigEndian.PutUint16(hdr[1:], uint16(l))
|
||||
case l < 0x100000000:
|
||||
hdr = hdr[:1+4]
|
||||
hdr[0] = 0xce
|
||||
binary.BigEndian.PutUint32(hdr[1:], uint32(l))
|
||||
default:
|
||||
p.err = &OverflowError{msg: fmt.Sprintf("Trying to pack too large byte array of size %d", l)}
|
||||
return
|
||||
}
|
||||
p.buf = append(p.buf, hdr...)
|
||||
p.buf = append(p.buf, b...)
|
||||
}
|
||||
|
||||
func (p *Packer) Bool(b bool) {
|
||||
if b {
|
||||
p.buf = append(p.buf, 0xc3)
|
||||
return
|
||||
}
|
||||
p.buf = append(p.buf, 0xc2)
|
||||
}
|
||||
|
||||
func (p *Packer) Nil() {
|
||||
p.buf = append(p.buf, 0xc0)
|
||||
}
|
||||
|
||||
func (p *Packer) checkOverflowInt(i uint64) {
|
||||
if i > math.MaxInt64 {
|
||||
p.err = &OverflowError{msg: "Trying to pack uint64 that doesn't fit into int64"}
|
||||
}
|
||||
}
|
254
vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/packstream/unpacker.go
generated
vendored
254
vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/packstream/unpacker.go
generated
vendored
@ -1,254 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) "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 packstream
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"math"
|
||||
)
|
||||
|
||||
const (
|
||||
PackedUndef = iota // Undefined must be zero!
|
||||
PackedInt
|
||||
PackedFloat
|
||||
PackedStr
|
||||
PackedStruct
|
||||
PackedByteArray
|
||||
PackedArray
|
||||
PackedMap
|
||||
PackedNil
|
||||
PackedTrue
|
||||
PackedFalse
|
||||
)
|
||||
|
||||
type Unpacker struct {
|
||||
buf []byte
|
||||
off uint32
|
||||
len uint32
|
||||
mrk marker
|
||||
Err error
|
||||
Curr int // Packed type
|
||||
}
|
||||
|
||||
func (u *Unpacker) Reset(buf []byte) {
|
||||
u.buf = buf
|
||||
u.off = 0
|
||||
u.len = uint32(len(buf))
|
||||
u.Err = nil
|
||||
u.mrk.typ = PackedUndef
|
||||
u.Curr = PackedUndef
|
||||
}
|
||||
|
||||
func (u *Unpacker) setErr(err error) {
|
||||
if u.Err == nil {
|
||||
u.Err = err
|
||||
}
|
||||
}
|
||||
|
||||
func (u *Unpacker) Next() {
|
||||
i := u.pop()
|
||||
u.mrk = markers[i]
|
||||
u.Curr = u.mrk.typ
|
||||
}
|
||||
|
||||
func (u *Unpacker) Len() uint32 {
|
||||
if u.mrk.numlenbytes == 0 {
|
||||
return uint32(u.mrk.shortlen)
|
||||
}
|
||||
return u.readlen(uint32(u.mrk.numlenbytes))
|
||||
}
|
||||
|
||||
func (u *Unpacker) Int() int64 {
|
||||
n := u.mrk.numlenbytes
|
||||
if n == 0 {
|
||||
return int64(u.mrk.shortlen)
|
||||
}
|
||||
|
||||
end := u.off + uint32(n)
|
||||
if end > u.len {
|
||||
u.setErr(&IoError{})
|
||||
return 0
|
||||
}
|
||||
i := int64(0)
|
||||
switch n {
|
||||
case 1:
|
||||
i = int64(int8(u.buf[u.off]))
|
||||
case 2:
|
||||
i = int64(int16(binary.BigEndian.Uint16(u.buf[u.off:])))
|
||||
case 4:
|
||||
i = int64(int32(binary.BigEndian.Uint32(u.buf[u.off:])))
|
||||
case 8:
|
||||
i = int64(binary.BigEndian.Uint64(u.buf[u.off:]))
|
||||
default:
|
||||
u.setErr(&UnpackError{msg: fmt.Sprintf("Illegal int length: %d", n)})
|
||||
return 0
|
||||
}
|
||||
u.off = end
|
||||
return i
|
||||
}
|
||||
|
||||
func (u *Unpacker) Float() float64 {
|
||||
buf := u.read(8)
|
||||
if u.Err != nil {
|
||||
return math.NaN()
|
||||
}
|
||||
return math.Float64frombits(binary.BigEndian.Uint64(buf))
|
||||
}
|
||||
|
||||
func (u *Unpacker) StructTag() byte {
|
||||
return u.pop()
|
||||
}
|
||||
|
||||
func (u *Unpacker) String() string {
|
||||
n := uint32(u.mrk.numlenbytes)
|
||||
if n == 0 {
|
||||
n = uint32(u.mrk.shortlen)
|
||||
} else {
|
||||
n = u.readlen(n)
|
||||
}
|
||||
return string(u.read(n))
|
||||
}
|
||||
|
||||
func (u *Unpacker) Bool() bool {
|
||||
switch u.Curr {
|
||||
case PackedTrue:
|
||||
return true
|
||||
case PackedFalse:
|
||||
return false
|
||||
default:
|
||||
u.setErr(&UnpackError{msg: "Illegal value for bool"})
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (u *Unpacker) ByteArray() []byte {
|
||||
n := u.Len()
|
||||
buf := u.read(n)
|
||||
if u.Err != nil || n == 0 {
|
||||
}
|
||||
out := make([]byte, n)
|
||||
copy(out, buf)
|
||||
return out
|
||||
}
|
||||
|
||||
func (u *Unpacker) pop() byte {
|
||||
if u.off < u.len {
|
||||
x := u.buf[u.off]
|
||||
u.off += 1
|
||||
return x
|
||||
}
|
||||
u.setErr(&IoError{})
|
||||
return 0
|
||||
}
|
||||
|
||||
func (u *Unpacker) read(n uint32) []byte {
|
||||
start := u.off
|
||||
end := u.off + n
|
||||
if end > u.len {
|
||||
u.setErr(&IoError{})
|
||||
return []byte{}
|
||||
}
|
||||
u.off = end
|
||||
return u.buf[start:end]
|
||||
}
|
||||
|
||||
func (u *Unpacker) readlen(n uint32) uint32 {
|
||||
end := u.off + n
|
||||
if end > u.len {
|
||||
u.setErr(&IoError{})
|
||||
return 0
|
||||
}
|
||||
l := uint32(0)
|
||||
switch n {
|
||||
case 1:
|
||||
l = uint32(u.buf[u.off])
|
||||
case 2:
|
||||
l = uint32(binary.BigEndian.Uint16(u.buf[u.off:]))
|
||||
case 4:
|
||||
l = uint32(binary.BigEndian.Uint32(u.buf[u.off:]))
|
||||
default:
|
||||
u.setErr(&UnpackError{msg: fmt.Sprintf("Illegal length: %d (%d)", n, u.Curr)})
|
||||
}
|
||||
u.off = end
|
||||
return l
|
||||
}
|
||||
|
||||
type marker struct {
|
||||
typ int
|
||||
shortlen int8
|
||||
numlenbytes byte
|
||||
}
|
||||
|
||||
var markers [0x100]marker
|
||||
|
||||
func init() {
|
||||
i := 0
|
||||
// Tiny int
|
||||
for ; i < 0x80; i++ {
|
||||
markers[i] = marker{typ: PackedInt, shortlen: int8(i)}
|
||||
}
|
||||
// Tiny string
|
||||
for ; i < 0x90; i++ {
|
||||
markers[i] = marker{typ: PackedStr, shortlen: int8(i - 0x80)}
|
||||
}
|
||||
// Tiny array
|
||||
for ; i < 0xa0; i++ {
|
||||
markers[i] = marker{typ: PackedArray, shortlen: int8(i - 0x90)}
|
||||
}
|
||||
// Tiny map
|
||||
for ; i < 0xb0; i++ {
|
||||
markers[i] = marker{typ: PackedMap, shortlen: int8(i - 0xa0)}
|
||||
}
|
||||
// Struct
|
||||
for ; i < 0xc0; i++ {
|
||||
markers[i] = marker{typ: PackedStruct, shortlen: int8(i - 0xb0)}
|
||||
}
|
||||
|
||||
markers[0xc0] = marker{typ: PackedNil}
|
||||
markers[0xc1] = marker{typ: PackedFloat, numlenbytes: 8}
|
||||
markers[0xc2] = marker{typ: PackedFalse}
|
||||
markers[0xc3] = marker{typ: PackedTrue}
|
||||
|
||||
markers[0xc8] = marker{typ: PackedInt, numlenbytes: 1}
|
||||
markers[0xc9] = marker{typ: PackedInt, numlenbytes: 2}
|
||||
markers[0xca] = marker{typ: PackedInt, numlenbytes: 4}
|
||||
markers[0xcb] = marker{typ: PackedInt, numlenbytes: 8}
|
||||
|
||||
markers[0xcc] = marker{typ: PackedByteArray, numlenbytes: 1}
|
||||
markers[0xcd] = marker{typ: PackedByteArray, numlenbytes: 2}
|
||||
markers[0xce] = marker{typ: PackedByteArray, numlenbytes: 4}
|
||||
|
||||
markers[0xd0] = marker{typ: PackedStr, numlenbytes: 1}
|
||||
markers[0xd1] = marker{typ: PackedStr, numlenbytes: 2}
|
||||
markers[0xd2] = marker{typ: PackedStr, numlenbytes: 4}
|
||||
|
||||
markers[0xd4] = marker{typ: PackedArray, numlenbytes: 1}
|
||||
markers[0xd5] = marker{typ: PackedArray, numlenbytes: 2}
|
||||
markers[0xd6] = marker{typ: PackedArray, numlenbytes: 4}
|
||||
|
||||
markers[0xd8] = marker{typ: PackedMap, numlenbytes: 1}
|
||||
markers[0xd9] = marker{typ: PackedMap, numlenbytes: 2}
|
||||
markers[0xda] = marker{typ: PackedMap, numlenbytes: 4}
|
||||
|
||||
for i = 0xf0; i < 0x100; i++ {
|
||||
markers[i] = marker{typ: PackedInt, shortlen: int8(i - 0x100)}
|
||||
}
|
||||
}
|
@ -1,48 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) "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 pool
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type PoolTimeout struct {
|
||||
err error
|
||||
servers []string
|
||||
}
|
||||
|
||||
func (e *PoolTimeout) Error() string {
|
||||
return fmt.Sprintf("Timeout while waiting for connection to any of [%s]: %s", e.servers, e.err)
|
||||
}
|
||||
|
||||
type PoolFull struct {
|
||||
servers []string
|
||||
}
|
||||
|
||||
func (e *PoolFull) Error() string {
|
||||
return fmt.Sprintf("No idle connections on any of [%s]", e.servers)
|
||||
}
|
||||
|
||||
type PoolClosed struct {
|
||||
}
|
||||
|
||||
func (e *PoolClosed) Error() string {
|
||||
return "Pool closed"
|
||||
}
|
@ -1,419 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) "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 pool handles the database connection pool.
|
||||
package pool
|
||||
|
||||
// Thread safe
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/neo4j/neo4j-go-driver/v4/neo4j/db"
|
||||
"github.com/neo4j/neo4j-go-driver/v4/neo4j/log"
|
||||
)
|
||||
|
||||
type Connect func(string, log.BoltLogger) (db.Connection, error)
|
||||
|
||||
type qitem struct {
|
||||
servers []string
|
||||
wakeup chan bool
|
||||
conn db.Connection
|
||||
}
|
||||
|
||||
type Pool struct {
|
||||
maxSize int
|
||||
maxAge time.Duration
|
||||
connect Connect
|
||||
servers map[string]*server
|
||||
serversMut sync.Mutex
|
||||
queueMut sync.Mutex
|
||||
queue list.List
|
||||
now func() time.Time
|
||||
closed bool
|
||||
log log.Logger
|
||||
logId string
|
||||
}
|
||||
|
||||
type serverPenalty struct {
|
||||
name string
|
||||
penalty uint32
|
||||
}
|
||||
|
||||
func New(maxSize int, maxAge time.Duration, connect Connect, logger log.Logger, logId string) *Pool {
|
||||
// Means infinite life, simplifies checking later on
|
||||
if maxAge <= 0 {
|
||||
maxAge = 1<<63 - 1
|
||||
}
|
||||
|
||||
p := &Pool{
|
||||
maxSize: maxSize,
|
||||
maxAge: maxAge,
|
||||
connect: connect,
|
||||
servers: make(map[string]*server),
|
||||
now: time.Now,
|
||||
logId: logId,
|
||||
log: logger,
|
||||
}
|
||||
p.log.Infof(log.Pool, p.logId, "Created")
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *Pool) Close() {
|
||||
p.closed = true
|
||||
// Cancel everything in the queue by just emptying at and let all callers timeout
|
||||
p.queueMut.Lock()
|
||||
p.queue.Init()
|
||||
p.queueMut.Unlock()
|
||||
// Go through each server and close all connections to it
|
||||
p.serversMut.Lock()
|
||||
for n, s := range p.servers {
|
||||
s.closeAll()
|
||||
delete(p.servers, n)
|
||||
}
|
||||
p.serversMut.Unlock()
|
||||
p.log.Infof(log.Pool, p.logId, "Closed")
|
||||
}
|
||||
|
||||
func (p *Pool) anyExistingConnectionsOnServers(serverNames []string) bool {
|
||||
p.serversMut.Lock()
|
||||
defer p.serversMut.Unlock()
|
||||
for _, s := range serverNames {
|
||||
b := p.servers[s]
|
||||
if b != nil {
|
||||
if b.size() > 0 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// For testing
|
||||
func (p *Pool) queueSize() int {
|
||||
p.queueMut.Lock()
|
||||
defer p.queueMut.Unlock()
|
||||
return p.queue.Len()
|
||||
}
|
||||
|
||||
// For testing
|
||||
func (p *Pool) getServers() map[string]*server {
|
||||
p.serversMut.Lock()
|
||||
defer p.serversMut.Unlock()
|
||||
servers := make(map[string]*server)
|
||||
for k, v := range p.servers {
|
||||
servers[k] = v
|
||||
}
|
||||
return servers
|
||||
}
|
||||
|
||||
// Prune all old connection on all the servers, this makes sure that servers
|
||||
// gets removed from the map at some point in time. If there is a noticed
|
||||
// failed connect still active we should wait a while with removal to get
|
||||
// prioritization right.
|
||||
func (p *Pool) CleanUp() {
|
||||
p.serversMut.Lock()
|
||||
defer p.serversMut.Unlock()
|
||||
now := p.now()
|
||||
for n, s := range p.servers {
|
||||
s.removeIdleOlderThan(now, p.maxAge)
|
||||
if s.size() == 0 && !s.hasFailedConnect(now) {
|
||||
delete(p.servers, n)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Pool) tryBorrow(serverName string, boltLogger log.BoltLogger) (db.Connection, error) {
|
||||
// For now, lock complete servers map to avoid over connecting but with the downside
|
||||
// that long connect times will block connects to other servers as well. To fix this
|
||||
// we would need to add a pending connect to the server and lock per server.
|
||||
p.serversMut.Lock()
|
||||
defer p.serversMut.Unlock()
|
||||
|
||||
srv := p.servers[serverName]
|
||||
if srv != nil {
|
||||
// Try to get an existing idle connection
|
||||
if c := srv.getIdle(); c != nil {
|
||||
c.SetBoltLogger(boltLogger)
|
||||
return c, nil
|
||||
}
|
||||
if srv.size() >= p.maxSize {
|
||||
return nil, &PoolFull{servers: []string{serverName}}
|
||||
}
|
||||
} else {
|
||||
// Make sure that there is a server in the map
|
||||
srv = &server{}
|
||||
p.servers[serverName] = srv
|
||||
}
|
||||
|
||||
// No idle connection, try to connect
|
||||
p.log.Infof(log.Pool, p.logId, "Connecting to %s", serverName)
|
||||
c, err := p.connect(serverName, boltLogger)
|
||||
if err != nil {
|
||||
// Failed to connect, keep track that it was bad for a while
|
||||
srv.notifyFailedConnect(p.now())
|
||||
p.log.Warnf(log.Pool, p.logId, "Failed to connect to %s: %s", serverName, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Ok, got a connection, register the connection
|
||||
srv.registerBusy(c)
|
||||
srv.notifySuccesfulConnect()
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (p *Pool) getPenaltiesForServers(serverNames []string) []serverPenalty {
|
||||
p.serversMut.Lock()
|
||||
defer p.serversMut.Unlock()
|
||||
|
||||
// Retrieve penalty for each server
|
||||
penalties := make([]serverPenalty, len(serverNames))
|
||||
now := p.now()
|
||||
for i, n := range serverNames {
|
||||
s := p.servers[n]
|
||||
penalties[i].name = n
|
||||
if s != nil {
|
||||
// Make sure that we don't get a too old connection
|
||||
s.removeIdleOlderThan(now, p.maxAge)
|
||||
penalties[i].penalty = s.calculatePenalty(now)
|
||||
} else {
|
||||
penalties[i].penalty = newConnectionPenalty
|
||||
}
|
||||
}
|
||||
return penalties
|
||||
}
|
||||
|
||||
func (p *Pool) tryAnyIdle(serverNames []string) db.Connection {
|
||||
p.serversMut.Lock()
|
||||
defer p.serversMut.Unlock()
|
||||
for _, serverName := range serverNames {
|
||||
srv := p.servers[serverName]
|
||||
if srv != nil {
|
||||
// Try to get an existing idle connection
|
||||
conn := srv.getIdle()
|
||||
if conn != nil {
|
||||
return conn
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Borrow tries to borrow an existing database connection or tries to create a new one
|
||||
// if none exists. The wait flag indicates if the caller wants to wait for a connection
|
||||
// to be returned if there aren't any idle connection available.
|
||||
func (p *Pool) Borrow(ctx context.Context, serverNames []string, wait bool, boltLogger log.BoltLogger) (db.Connection, error) {
|
||||
timeOut := func() bool {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
p.log.Warnf(log.Pool, p.logId, "Borrow time-out")
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
if p.closed {
|
||||
return nil, &PoolClosed{}
|
||||
}
|
||||
p.log.Debugf(log.Pool, p.logId, "Trying to borrow connection from %s", serverNames)
|
||||
|
||||
// Retrieve penalty for each server
|
||||
penalties := p.getPenaltiesForServers(serverNames)
|
||||
// Sort server penalties by lowest penalty
|
||||
sort.Slice(penalties, func(i, j int) bool {
|
||||
return penalties[i].penalty < penalties[j].penalty
|
||||
})
|
||||
|
||||
var err error
|
||||
var conn db.Connection
|
||||
for _, s := range penalties {
|
||||
conn, err = p.tryBorrow(s.name, boltLogger)
|
||||
if err == nil {
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
// Check if we have timed out after failed borrow
|
||||
if timeOut() {
|
||||
return nil, &PoolTimeout{servers: serverNames}
|
||||
}
|
||||
}
|
||||
|
||||
// If there are no connections for any of the servers, there is no point in waiting for anything
|
||||
// to be returned.
|
||||
if !p.anyExistingConnectionsOnServers(serverNames) {
|
||||
p.log.Warnf(log.Pool, p.logId, "No server connection available to any of %v", serverNames)
|
||||
if err == nil {
|
||||
err = errors.New(fmt.Sprintf("No server connection available to any of %v", serverNames))
|
||||
}
|
||||
// Intentionally return last error from last connection attempt to make it easier to
|
||||
// see connection errors for users.
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !wait {
|
||||
return nil, &PoolFull{servers: serverNames}
|
||||
}
|
||||
|
||||
// Wait for a matching connection to be returned from another thread.
|
||||
p.queueMut.Lock()
|
||||
// Ok, now that we own the queue we can add the item there but between getting the lock
|
||||
// and above check for an existing connection another thread might have returned a connection
|
||||
// so check again to avoid potentially starving this thread.
|
||||
conn = p.tryAnyIdle(serverNames)
|
||||
if conn != nil {
|
||||
p.queueMut.Unlock()
|
||||
return conn, nil
|
||||
}
|
||||
// Add a waiting request to the queue and unlock the queue to let other threads that returns
|
||||
// their connections access the queue.
|
||||
q := &qitem{
|
||||
servers: serverNames,
|
||||
wakeup: make(chan bool),
|
||||
}
|
||||
e := p.queue.PushBack(q)
|
||||
p.queueMut.Unlock()
|
||||
|
||||
p.log.Warnf(log.Pool, p.logId, "Borrow queued")
|
||||
// Wait for either a wake up signal that indicates that we got a connection or a timeout.
|
||||
select {
|
||||
case <-q.wakeup:
|
||||
return q.conn, nil
|
||||
case <-ctx.Done():
|
||||
p.queueMut.Lock()
|
||||
p.queue.Remove(e)
|
||||
p.queueMut.Unlock()
|
||||
if q.conn != nil {
|
||||
return q.conn, nil
|
||||
}
|
||||
p.log.Warnf(log.Pool, p.logId, "Borrow time-out")
|
||||
return nil, &PoolTimeout{err: ctx.Err(), servers: serverNames}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Pool) unreg(serverName string, c db.Connection, now time.Time) {
|
||||
p.serversMut.Lock()
|
||||
defer p.serversMut.Unlock()
|
||||
|
||||
defer func() {
|
||||
// Close connection in another thread to avoid potential long blocking operation during close.
|
||||
go c.Close()
|
||||
}()
|
||||
|
||||
server := p.servers[serverName]
|
||||
// Check for strange condition of not finding the server.
|
||||
if server == nil {
|
||||
p.log.Warnf(log.Pool, p.logId, "Server %s not found", serverName)
|
||||
return
|
||||
}
|
||||
|
||||
server.unregisterBusy(c)
|
||||
if server.size() == 0 && !server.hasFailedConnect(now) {
|
||||
delete(p.servers, serverName)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Pool) removeIdleOlderThanOnServer(serverName string, now time.Time, maxAge time.Duration) {
|
||||
p.serversMut.Lock()
|
||||
defer p.serversMut.Unlock()
|
||||
server := p.servers[serverName]
|
||||
if server == nil {
|
||||
return
|
||||
}
|
||||
server.removeIdleOlderThan(now, maxAge)
|
||||
}
|
||||
|
||||
func (p *Pool) Return(c db.Connection) {
|
||||
if p.closed {
|
||||
p.log.Warnf(log.Pool, p.logId, "Trying to return connection to closed pool")
|
||||
return
|
||||
}
|
||||
|
||||
c.SetBoltLogger(nil)
|
||||
|
||||
// Get the name of the server that the connection belongs to.
|
||||
serverName := c.ServerName()
|
||||
isAlive := c.IsAlive()
|
||||
p.log.Debugf(log.Pool, p.logId, "Returning connection to %s {alive:%t}", serverName, isAlive)
|
||||
|
||||
// If the connection is dead, remove all other idle connections on the same server that older
|
||||
// or of the same age as the dead connection, otherwise perform normal cleanup of old connections
|
||||
maxAge := p.maxAge
|
||||
now := p.now()
|
||||
age := now.Sub(c.Birthdate())
|
||||
if !isAlive {
|
||||
// Since this connection has died all other connections that connected before this one
|
||||
// might also be bad, remove the idle ones.
|
||||
if age < maxAge {
|
||||
maxAge = age
|
||||
}
|
||||
}
|
||||
p.removeIdleOlderThanOnServer(serverName, now, maxAge)
|
||||
|
||||
// Prepare connection for being used by someone else if is alive.
|
||||
// Since reset could find the connection to be in a bad state or non-recoverable state,
|
||||
// make sure again that it really is alive.
|
||||
if isAlive {
|
||||
c.Reset()
|
||||
isAlive = c.IsAlive()
|
||||
}
|
||||
|
||||
// Shouldn't return a too old or dead connection back to the pool
|
||||
if !isAlive || age >= p.maxAge {
|
||||
p.unreg(serverName, c, now)
|
||||
p.log.Infof(log.Pool, p.logId, "Unregistering dead or too old connection to %s", serverName)
|
||||
// Returning here could cause a waiting thread to wait until it times out, to do it
|
||||
// properly we could wake up threads that waits on the server and wake them up if there
|
||||
// are no more connections to wait for.
|
||||
return
|
||||
}
|
||||
|
||||
// Check if there is anyone in the queue waiting for a connection to this server.
|
||||
p.queueMut.Lock()
|
||||
for e := p.queue.Front(); e != nil; e = e.Next() {
|
||||
qitem := e.Value.(*qitem)
|
||||
// Check requested servers
|
||||
for _, rserver := range qitem.servers {
|
||||
if rserver == serverName {
|
||||
qitem.conn = c
|
||||
p.queue.Remove(e)
|
||||
p.queueMut.Unlock()
|
||||
qitem.wakeup <- true
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
p.queueMut.Unlock()
|
||||
|
||||
// Just put it back in the list of idle connections for this server
|
||||
p.serversMut.Lock()
|
||||
defer p.serversMut.Unlock()
|
||||
server := p.servers[serverName]
|
||||
if server != nil { // Strange when server not found
|
||||
server.returnBusy(c)
|
||||
} else {
|
||||
p.log.Warnf(log.Pool, p.logId, "Server %s not found", serverName)
|
||||
}
|
||||
}
|
@ -1,166 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) "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 pool
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/neo4j/neo4j-go-driver/v4/neo4j/db"
|
||||
)
|
||||
|
||||
// Represents a server with a number of connections that either is in use (borrowed) or
|
||||
// is ready for use.
|
||||
// Not thread safe
|
||||
type server struct {
|
||||
idle list.List
|
||||
busy list.List
|
||||
failedConnectAt time.Time
|
||||
roundRobin uint32
|
||||
}
|
||||
|
||||
var sharedRoundRobin uint32
|
||||
|
||||
const rememberFailedConnectDuration = 3 * time.Minute
|
||||
|
||||
// Returns a idle connection if any
|
||||
func (s *server) getIdle() db.Connection {
|
||||
// Remove from idle list and add to busy list
|
||||
e := s.idle.Front()
|
||||
if e != nil {
|
||||
c := s.idle.Remove(e)
|
||||
s.busy.PushFront(c)
|
||||
// Update round-robin counter every time we give away a connection and keep track
|
||||
// of our own round-robin index
|
||||
s.roundRobin = atomic.AddUint32(&sharedRoundRobin, 1)
|
||||
return c.(db.Connection)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *server) notifyFailedConnect(now time.Time) {
|
||||
s.failedConnectAt = now
|
||||
}
|
||||
|
||||
func (s *server) notifySuccesfulConnect() {
|
||||
s.failedConnectAt = time.Time{}
|
||||
}
|
||||
|
||||
func (s *server) hasFailedConnect(now time.Time) bool {
|
||||
if s.failedConnectAt.IsZero() {
|
||||
return false
|
||||
}
|
||||
return now.Sub(s.failedConnectAt) < rememberFailedConnectDuration
|
||||
}
|
||||
|
||||
const newConnectionPenalty = uint32(1 << 8)
|
||||
|
||||
// Calculates a penalty value for how this server compares to other servers
|
||||
// when there is more than one server to choose from. The lower penalty the better choice.
|
||||
func (s *server) calculatePenalty(now time.Time) uint32 {
|
||||
penalty := uint32(0)
|
||||
|
||||
// If a connect to the server has failed recently, add a penalty
|
||||
if s.hasFailedConnect(now) {
|
||||
penalty = 1 << 31
|
||||
}
|
||||
// The more busy connections, the higher penalty
|
||||
numBusy := uint32(s.busy.Len())
|
||||
if numBusy > 0xff {
|
||||
numBusy = 0xff
|
||||
}
|
||||
penalty |= numBusy << 16
|
||||
// If there are no idle connections, add a penalty as the cost of connect would
|
||||
// add to the transaction time
|
||||
if s.idle.Len() == 0 {
|
||||
penalty |= newConnectionPenalty
|
||||
}
|
||||
// Use last round-robin value as lowest priority penalty, so when all other is equal we will
|
||||
// make sure to spread usage among the servers. And yes it will wrap around once in a while
|
||||
// but since number of busy servers weights higher it will even out pretty fast.
|
||||
penalty |= (s.roundRobin & 0xff)
|
||||
|
||||
return penalty
|
||||
}
|
||||
|
||||
// Returns a busy connection, makes it idle
|
||||
func (s *server) returnBusy(c db.Connection) {
|
||||
s.unregisterBusy(c)
|
||||
s.idle.PushFront(c)
|
||||
}
|
||||
|
||||
// Number of idle connections
|
||||
func (s server) numIdle() int {
|
||||
return s.idle.Len()
|
||||
}
|
||||
|
||||
// Adds a connection to busy list
|
||||
func (s *server) registerBusy(c db.Connection) {
|
||||
// Update round-robin to indicate when this server was last used.
|
||||
s.roundRobin = atomic.AddUint32(&sharedRoundRobin, 1)
|
||||
s.busy.PushFront(c)
|
||||
}
|
||||
|
||||
func (s *server) unregisterBusy(c db.Connection) {
|
||||
found := false
|
||||
for e := s.busy.Front(); e != nil && !found; e = e.Next() {
|
||||
x := e.Value.(db.Connection)
|
||||
found = x == c
|
||||
if found {
|
||||
s.busy.Remove(e)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *server) size() int {
|
||||
return s.busy.Len() + s.idle.Len()
|
||||
}
|
||||
|
||||
func (s *server) removeIdleOlderThan(now time.Time, maxAge time.Duration) {
|
||||
e := s.idle.Front()
|
||||
for e != nil {
|
||||
n := e.Next()
|
||||
c := e.Value.(db.Connection)
|
||||
|
||||
age := now.Sub(c.Birthdate())
|
||||
if age >= maxAge {
|
||||
s.idle.Remove(e)
|
||||
go c.Close()
|
||||
}
|
||||
|
||||
e = n
|
||||
}
|
||||
}
|
||||
|
||||
func closeAndEmptyConnections(l list.List) {
|
||||
for e := l.Front(); e != nil; e = e.Next() {
|
||||
c := e.Value.(db.Connection)
|
||||
c.Close()
|
||||
}
|
||||
l.Init()
|
||||
}
|
||||
|
||||
func (s *server) closeAll() {
|
||||
closeAndEmptyConnections(s.idle)
|
||||
// Closing the busy connections could mean here that we do close from another thread.
|
||||
closeAndEmptyConnections(s.busy)
|
||||
}
|
@ -1,93 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) "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 racingio
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
type RacingReader interface {
|
||||
Read(ctx context.Context, bytes []byte) (int, error)
|
||||
ReadFull(ctx context.Context, bytes []byte) (int, error)
|
||||
}
|
||||
|
||||
func NewRacingReader(reader io.Reader) RacingReader {
|
||||
return &racingReader{reader: reader}
|
||||
}
|
||||
|
||||
type racingReader struct {
|
||||
reader io.Reader
|
||||
}
|
||||
|
||||
func (rr *racingReader) Read(ctx context.Context, bytes []byte) (int, error) {
|
||||
return rr.race(ctx, bytes, read)
|
||||
}
|
||||
|
||||
func (rr *racingReader) ReadFull(ctx context.Context, bytes []byte) (int, error) {
|
||||
return rr.race(ctx, bytes, readFull)
|
||||
}
|
||||
|
||||
func (rr *racingReader) race(ctx context.Context, bytes []byte, readFn func(io.Reader, []byte) (int, error)) (int, error) {
|
||||
if err := ctx.Err(); err != nil {
|
||||
return 0, wrapRaceError(err)
|
||||
}
|
||||
resultChan := make(chan *ioResult, 1)
|
||||
go func() {
|
||||
defer close(resultChan)
|
||||
n, err := readFn(rr.reader, bytes)
|
||||
resultChan <- &ioResult{
|
||||
n: n,
|
||||
err: err,
|
||||
}
|
||||
}()
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return 0, wrapRaceError(ctx.Err())
|
||||
case result := <-resultChan:
|
||||
return result.n, wrapRaceError(result.err)
|
||||
}
|
||||
}
|
||||
|
||||
func wrapRaceError(err error) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
|
||||
// temporary adjustment for 4.x
|
||||
return fmt.Errorf("i/o timeout: %w", err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
type ioResult struct {
|
||||
n int
|
||||
err error
|
||||
}
|
||||
|
||||
func read(reader io.Reader, bytes []byte) (int, error) {
|
||||
return reader.Read(bytes)
|
||||
}
|
||||
|
||||
func readFull(reader io.Reader, bytes []byte) (int, error) {
|
||||
return io.ReadFull(reader, bytes)
|
||||
}
|
@ -1,155 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) "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 retry handles retry operations.
|
||||
package retry
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/neo4j/neo4j-go-driver/v4/neo4j/db"
|
||||
"github.com/neo4j/neo4j-go-driver/v4/neo4j/log"
|
||||
)
|
||||
|
||||
type Router interface {
|
||||
Invalidate(database string)
|
||||
}
|
||||
|
||||
type CommitFailedDeadError struct {
|
||||
inner error
|
||||
}
|
||||
|
||||
func (e *CommitFailedDeadError) Error() string {
|
||||
return fmt.Sprintf("Connection lost during commit: %s", e.inner)
|
||||
}
|
||||
|
||||
type State struct {
|
||||
LastErrWasRetryable bool
|
||||
LastErr error
|
||||
stop bool
|
||||
Errs []error
|
||||
Causes []string
|
||||
MaxTransactionRetryTime time.Duration
|
||||
Log log.Logger
|
||||
LogName string
|
||||
LogId string
|
||||
Now func() time.Time
|
||||
Sleep func(time.Duration)
|
||||
Throttle Throttler
|
||||
MaxDeadConnections int
|
||||
Router Router
|
||||
DatabaseName string
|
||||
|
||||
start time.Time
|
||||
cause string
|
||||
deadErrors int
|
||||
skipSleep bool
|
||||
}
|
||||
|
||||
func (s *State) OnFailure(conn db.Connection, err error, isCommitting bool) {
|
||||
s.LastErr = err
|
||||
s.cause = ""
|
||||
s.skipSleep = false
|
||||
|
||||
// Check timeout
|
||||
if s.start.IsZero() {
|
||||
s.start = s.Now()
|
||||
}
|
||||
if s.Now().Sub(s.start) > s.MaxTransactionRetryTime {
|
||||
s.stop = true
|
||||
s.cause = "Timeout"
|
||||
return
|
||||
}
|
||||
|
||||
// Reset after determined to evaluate this error
|
||||
s.LastErrWasRetryable = false
|
||||
|
||||
// Failed to connect
|
||||
if conn == nil {
|
||||
s.LastErrWasRetryable = true
|
||||
s.cause = "No available connection"
|
||||
return
|
||||
}
|
||||
|
||||
// Check if the connection died, if it died during commit it is not safe to retry.
|
||||
if !conn.IsAlive() {
|
||||
if isCommitting {
|
||||
s.stop = true
|
||||
// The error is most probably io.EOF so enrich the error
|
||||
// to make this error more recognizable.
|
||||
s.LastErr = &CommitFailedDeadError{inner: s.LastErr}
|
||||
return
|
||||
}
|
||||
|
||||
s.deadErrors += 1
|
||||
s.stop = s.deadErrors > s.MaxDeadConnections
|
||||
s.LastErrWasRetryable = true
|
||||
s.cause = "Connection lost"
|
||||
s.skipSleep = true
|
||||
return
|
||||
}
|
||||
|
||||
if dbErr, isDbErr := err.(*db.Neo4jError); isDbErr {
|
||||
if dbErr.IsRetriableCluster() {
|
||||
// Force routing tables to be updated before trying again
|
||||
s.Router.Invalidate(s.DatabaseName)
|
||||
s.cause = "Cluster error"
|
||||
s.LastErrWasRetryable = true
|
||||
return
|
||||
}
|
||||
|
||||
if dbErr.IsRetriableTransient() {
|
||||
s.cause = "Transient error"
|
||||
s.LastErrWasRetryable = true
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
s.stop = true
|
||||
}
|
||||
|
||||
func (s *State) Continue() bool {
|
||||
// No error happened yet
|
||||
if !s.stop && s.LastErr == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
// Track the error and the cause
|
||||
s.Errs = append(s.Errs, s.LastErr)
|
||||
if s.cause != "" {
|
||||
s.Causes = append(s.Causes, s.cause)
|
||||
}
|
||||
|
||||
// Retry after optional sleep
|
||||
if !s.stop {
|
||||
if s.skipSleep {
|
||||
s.Log.Debugf(s.LogName, s.LogId, "Retrying transaction (%s): %s", s.cause, s.LastErr)
|
||||
} else {
|
||||
s.Throttle = s.Throttle.next()
|
||||
sleepTime := s.Throttle.delay()
|
||||
s.Log.Debugf(s.LogName, s.LogId,
|
||||
"Retrying transaction (%s): %s [after %s]", s.cause, s.LastErr, sleepTime)
|
||||
s.Sleep(sleepTime)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
@ -1,38 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) "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 retry
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Throttler time.Duration
|
||||
|
||||
func (t Throttler) next() Throttler {
|
||||
delay := time.Duration(t)
|
||||
const delayJitter = 0.2
|
||||
jitter := float64(delay) * delayJitter
|
||||
return Throttler(delay - time.Duration(jitter) + time.Duration(2*jitter*rand.Float64()))
|
||||
}
|
||||
|
||||
func (t Throttler) delay() time.Duration {
|
||||
return time.Duration(t)
|
||||
}
|
@ -1,47 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) "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 router
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/neo4j/neo4j-go-driver/v4/neo4j/db"
|
||||
)
|
||||
|
||||
type ReadRoutingTableError struct {
|
||||
err error
|
||||
server string
|
||||
}
|
||||
|
||||
func (e *ReadRoutingTableError) Error() string {
|
||||
if e.err != nil || len(e.server) > 0 {
|
||||
return fmt.Sprintf("Unable to retrieve routing table from %s: %s", e.server, e.err)
|
||||
}
|
||||
return "Unable to retrieve routing table, no router provided"
|
||||
}
|
||||
|
||||
func wrapError(server string, err error) error {
|
||||
// Preserve error originating from the database, wrap other errors
|
||||
_, isNeo4jErr := err.(*db.Neo4jError)
|
||||
if isNeo4jErr {
|
||||
return err
|
||||
}
|
||||
return &ReadRoutingTableError{server: server, err: err}
|
||||
}
|
@ -1,60 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) "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 router
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/neo4j/neo4j-go-driver/v4/neo4j/log"
|
||||
|
||||
"github.com/neo4j/neo4j-go-driver/v4/neo4j/db"
|
||||
)
|
||||
|
||||
// Tries to read routing table from any of the specified routers using new or existing connection
|
||||
// from the supplied pool.
|
||||
func readTable(ctx context.Context, pool Pool, routers []string, routerContext map[string]string, bookmarks []string,
|
||||
database, impersonatedUser string, boltLogger log.BoltLogger) (*db.RoutingTable, error) {
|
||||
// Preserve last error to be returned, set a default for case of no routers
|
||||
var err error = &ReadRoutingTableError{}
|
||||
|
||||
// Try the routers one at the time since some of them might no longer support routing and we
|
||||
// can't force the pool to not re-use these when putting them back in the pool and retrieving
|
||||
// another db.
|
||||
for _, router := range routers {
|
||||
var conn db.Connection
|
||||
if conn, err = pool.Borrow(ctx, []string{router}, true, boltLogger); err != nil {
|
||||
// Check if failed due to context timing out
|
||||
if ctx.Err() != nil {
|
||||
return nil, wrapError(router, ctx.Err())
|
||||
}
|
||||
err = wrapError(router, err)
|
||||
continue
|
||||
}
|
||||
|
||||
// We have a connection to the "router"
|
||||
var table *db.RoutingTable
|
||||
table, err = conn.GetRoutingTable(routerContext, bookmarks, database, impersonatedUser)
|
||||
pool.Return(conn)
|
||||
if err == nil {
|
||||
return table, nil
|
||||
}
|
||||
err = wrapError(router, err)
|
||||
}
|
||||
return nil, err
|
||||
}
|
@ -1,242 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) "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 router
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/neo4j/neo4j-go-driver/v4/neo4j/db"
|
||||
"github.com/neo4j/neo4j-go-driver/v4/neo4j/log"
|
||||
)
|
||||
|
||||
const missingWriterRetries = 100
|
||||
const missingReaderRetries = 100
|
||||
|
||||
type databaseRouter struct {
|
||||
dueUnix int64
|
||||
table *db.RoutingTable
|
||||
}
|
||||
|
||||
// Thread safe
|
||||
type Router struct {
|
||||
routerContext map[string]string
|
||||
pool Pool
|
||||
dbRouters map[string]*databaseRouter
|
||||
dbRoutersMut sync.Mutex
|
||||
now func() time.Time
|
||||
sleep func(time.Duration)
|
||||
rootRouter string
|
||||
getRouters func() []string
|
||||
log log.Logger
|
||||
logId string
|
||||
}
|
||||
|
||||
type Pool interface {
|
||||
Borrow(ctx context.Context, servers []string, wait bool, boltLogger log.BoltLogger) (db.Connection, error)
|
||||
Return(c db.Connection)
|
||||
}
|
||||
|
||||
func New(rootRouter string, getRouters func() []string, routerContext map[string]string, pool Pool, logger log.Logger, logId string) *Router {
|
||||
r := &Router{
|
||||
rootRouter: rootRouter,
|
||||
getRouters: getRouters,
|
||||
routerContext: routerContext,
|
||||
pool: pool,
|
||||
dbRouters: make(map[string]*databaseRouter),
|
||||
now: time.Now,
|
||||
sleep: time.Sleep,
|
||||
log: logger,
|
||||
logId: logId,
|
||||
}
|
||||
r.log.Infof(log.Router, r.logId, "Created {context: %v}", routerContext)
|
||||
return r
|
||||
}
|
||||
|
||||
func (r *Router) readTable(ctx context.Context, dbRouter *databaseRouter, bookmarks []string, database, impersonatedUser string, boltLogger log.BoltLogger) (*db.RoutingTable, error) {
|
||||
var (
|
||||
table *db.RoutingTable
|
||||
err error
|
||||
)
|
||||
|
||||
// Try last known set of routers if there are any
|
||||
if dbRouter != nil && len(dbRouter.table.Routers) > 0 {
|
||||
routers := dbRouter.table.Routers
|
||||
r.log.Infof(log.Router, r.logId, "Reading routing table for '%s' from previously known routers: %v", database, routers)
|
||||
table, err = readTable(ctx, r.pool, routers, r.routerContext, bookmarks, database, impersonatedUser, boltLogger)
|
||||
}
|
||||
|
||||
// Try initial router if no routers or failed
|
||||
if table == nil {
|
||||
r.log.Infof(log.Router, r.logId, "Reading routing table from initial router: %s", r.rootRouter)
|
||||
table, err = readTable(ctx, r.pool, []string{r.rootRouter}, r.routerContext, bookmarks, database, impersonatedUser, boltLogger)
|
||||
}
|
||||
|
||||
// Use hook to retrieve possibly different set of routers and retry
|
||||
if table == nil && r.getRouters != nil {
|
||||
routers := r.getRouters()
|
||||
r.log.Infof(log.Router, r.logId, "Reading routing table for '%s' from custom routers: %v", routers)
|
||||
table, err = readTable(ctx, r.pool, routers, r.routerContext, bookmarks, database, impersonatedUser, boltLogger)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
r.log.Error(log.Router, r.logId, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if table == nil {
|
||||
// Safe guard for logical error somewhere else
|
||||
err = errors.New("No error and no table")
|
||||
r.log.Error(log.Router, r.logId, err)
|
||||
return nil, err
|
||||
}
|
||||
return table, nil
|
||||
}
|
||||
|
||||
func (r *Router) getOrReadTable(ctx context.Context, bookmarks []string, database string, boltLogger log.BoltLogger) (*db.RoutingTable, error) {
|
||||
now := r.now()
|
||||
|
||||
r.dbRoutersMut.Lock()
|
||||
defer r.dbRoutersMut.Unlock()
|
||||
|
||||
dbRouter := r.dbRouters[database]
|
||||
if dbRouter != nil && now.Unix() < dbRouter.dueUnix {
|
||||
return dbRouter.table, nil
|
||||
}
|
||||
|
||||
table, err := r.readTable(ctx, dbRouter, bookmarks, database, "", boltLogger)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Store the routing table
|
||||
r.dbRouters[database] = &databaseRouter{
|
||||
table: table,
|
||||
dueUnix: now.Add(time.Duration(table.TimeToLive) * time.Second).Unix(),
|
||||
}
|
||||
r.log.Debugf(log.Router, r.logId, "New routing table for '%s', TTL %d", database, table.TimeToLive)
|
||||
|
||||
return table, nil
|
||||
}
|
||||
|
||||
func (r *Router) Readers(ctx context.Context, bookmarks []string, database string, boltLogger log.BoltLogger) ([]string, error) {
|
||||
table, err := r.getOrReadTable(ctx, bookmarks, database, boltLogger)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// During startup we can get tables without any readers
|
||||
retries := missingReaderRetries
|
||||
for len(table.Readers) == 0 {
|
||||
retries--
|
||||
if retries == 0 {
|
||||
break
|
||||
}
|
||||
r.log.Infof(log.Router, r.logId, "Invalidating routing table, no readers")
|
||||
r.Invalidate(table.DatabaseName)
|
||||
r.sleep(100 * time.Millisecond)
|
||||
table, err = r.getOrReadTable(ctx, bookmarks, database, boltLogger)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if len(table.Readers) == 0 {
|
||||
return nil, wrapError(r.rootRouter, errors.New("No readers"))
|
||||
}
|
||||
|
||||
return table.Readers, nil
|
||||
}
|
||||
|
||||
func (r *Router) Writers(ctx context.Context, bookmarks []string, database string, boltLogger log.BoltLogger) ([]string, error) {
|
||||
table, err := r.getOrReadTable(ctx, bookmarks, database, boltLogger)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// During election we can get tables without any writers
|
||||
retries := missingWriterRetries
|
||||
for len(table.Writers) == 0 {
|
||||
retries--
|
||||
if retries == 0 {
|
||||
break
|
||||
}
|
||||
r.log.Infof(log.Router, r.logId, "Invalidating routing table, no writers")
|
||||
r.Invalidate(database)
|
||||
r.sleep(100 * time.Millisecond)
|
||||
table, err = r.getOrReadTable(ctx, bookmarks, database, boltLogger)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if len(table.Writers) == 0 {
|
||||
return nil, wrapError(r.rootRouter, errors.New("No writers"))
|
||||
}
|
||||
|
||||
return table.Writers, nil
|
||||
}
|
||||
|
||||
func (r *Router) GetNameOfDefaultDatabase(ctx context.Context, bookmarks []string, user string, boltLogger log.BoltLogger) (string, error) {
|
||||
table, err := r.readTable(ctx, nil, bookmarks, db.DefaultDatabase, user, boltLogger)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
// Store the fresh routing table as well to avoid another roundtrip to receive servers from session.
|
||||
now := r.now()
|
||||
r.dbRoutersMut.Lock()
|
||||
defer r.dbRoutersMut.Unlock()
|
||||
r.dbRouters[table.DatabaseName] = &databaseRouter{
|
||||
table: table,
|
||||
dueUnix: now.Add(time.Duration(table.TimeToLive) * time.Second).Unix(),
|
||||
}
|
||||
r.log.Debugf(log.Router, r.logId, "New routing table when retrieving default database for impersonated user: '%s', TTL %d", table.DatabaseName, table.TimeToLive)
|
||||
|
||||
return table.DatabaseName, err
|
||||
}
|
||||
|
||||
func (r *Router) Context() map[string]string {
|
||||
return r.routerContext
|
||||
}
|
||||
|
||||
func (r *Router) Invalidate(database string) {
|
||||
r.log.Infof(log.Router, r.logId, "Invalidating routing table for '%s'", database)
|
||||
r.dbRoutersMut.Lock()
|
||||
defer r.dbRoutersMut.Unlock()
|
||||
// Reset due time to the 70s, this will make next access refresh the routing table using
|
||||
// last set of routers instead of the original one.
|
||||
dbRouter := r.dbRouters[database]
|
||||
if dbRouter != nil {
|
||||
dbRouter.dueUnix = 0
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Router) CleanUp() {
|
||||
r.log.Debugf(log.Router, r.logId, "Cleaning up")
|
||||
now := r.now().Unix()
|
||||
r.dbRoutersMut.Lock()
|
||||
defer r.dbRoutersMut.Unlock()
|
||||
|
||||
for dbName, dbRouter := range r.dbRouters {
|
||||
if now > dbRouter.dueUnix {
|
||||
delete(r.dbRouters, dbName)
|
||||
}
|
||||
}
|
||||
}
|
@ -1,34 +0,0 @@
|
||||
package log
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
type BoltLogger interface {
|
||||
LogClientMessage(context string, msg string, args ...interface{})
|
||||
LogServerMessage(context string, msg string, args ...interface{})
|
||||
}
|
||||
|
||||
type ConsoleBoltLogger struct {
|
||||
}
|
||||
|
||||
func (cbl *ConsoleBoltLogger) LogClientMessage(id, msg string, args ...interface{}) {
|
||||
cbl.logBoltMessage("C", id, msg, args)
|
||||
}
|
||||
|
||||
func (cbl *ConsoleBoltLogger) LogServerMessage(id, msg string, args ...interface{}) {
|
||||
cbl.logBoltMessage("S", id, msg, args)
|
||||
}
|
||||
|
||||
func (cbl *ConsoleBoltLogger) logBoltMessage(src, id string, msg string, args []interface{}) {
|
||||
_, _ = fmt.Fprintf(os.Stdout, "%s BOLT %s%s: %s\n", time.Now().Format(timeFormat), formatId(id), src, fmt.Sprintf(msg, args...))
|
||||
}
|
||||
|
||||
func formatId(id string) string {
|
||||
if id == "" {
|
||||
return ""
|
||||
}
|
||||
return fmt.Sprintf("[%s] ", id)
|
||||
}
|
@ -1,69 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) "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 log
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Console is a simple logger that logs to stdout/console.
|
||||
// Turn the different log levels on/off as wished, all are off by default.
|
||||
type Console struct {
|
||||
Errors bool
|
||||
Infos bool
|
||||
Warns bool
|
||||
Debugs bool
|
||||
}
|
||||
|
||||
const timeFormat = "2006-01-02 15:04:05.000"
|
||||
|
||||
func (l *Console) Error(name, id string, err error) {
|
||||
if !l.Errors {
|
||||
return
|
||||
}
|
||||
now := time.Now()
|
||||
fmt.Fprintf(os.Stderr, "%s ERROR [%s %s] %s\n", now.Format(timeFormat), name, id, err.Error())
|
||||
}
|
||||
|
||||
func (l *Console) Infof(name, id string, msg string, args ...interface{}) {
|
||||
if !l.Infos {
|
||||
return
|
||||
}
|
||||
now := time.Now()
|
||||
fmt.Fprintf(os.Stdout, "%s INFO [%s %s] %s\n", now.Format(timeFormat), name, id, fmt.Sprintf(msg, args...))
|
||||
}
|
||||
|
||||
func (l *Console) Warnf(name, id string, msg string, args ...interface{}) {
|
||||
if !l.Warns {
|
||||
return
|
||||
}
|
||||
now := time.Now()
|
||||
fmt.Fprintf(os.Stdout, "%s WARN [%s %s] %s\n", now.Format(timeFormat), name, id, fmt.Sprintf(msg, args...))
|
||||
}
|
||||
|
||||
func (l *Console) Debugf(name, id string, msg string, args ...interface{}) {
|
||||
if !l.Debugs {
|
||||
return
|
||||
}
|
||||
now := time.Now()
|
||||
fmt.Fprintf(os.Stdout, "%s DEBUG [%s %s] %s\n", now.Format(timeFormat), name, id, fmt.Sprintf(msg, args...))
|
||||
}
|
@ -1,68 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) "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 log defines the logging interface used internally by the driver and
|
||||
// provides default logging implementations.
|
||||
package log
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
// Logger is used throughout the driver for logging purposes.
|
||||
// Driver client can implement this interface and provide an implementation
|
||||
// upon driver creation.
|
||||
//
|
||||
// All logging functions takes a name and id that corresponds to the name of
|
||||
// the logging component and it's identity, for example "router" and "1" to
|
||||
// indicate who is logging and what instance.
|
||||
//
|
||||
// Database connections takes to form of "bolt3" and "bolt-123@192.168.0.1:7687"
|
||||
// where "bolt3" is the name of the protocol handler in use, "bolt-123" is the
|
||||
// databases identity of the connection on server "192.168.0.1:7687".
|
||||
type Logger interface {
|
||||
// Error is called whenever the driver encounters an error that might
|
||||
// or might not cause a retry operation which means that all logged
|
||||
// errors are not critical. Type of err might or might not be a publicly
|
||||
// exported type. The same root cause of an error might be reported
|
||||
// more than once by different components using same or different err types.
|
||||
Error(name string, id string, err error)
|
||||
Warnf(name string, id string, msg string, args ...interface{})
|
||||
Infof(name string, id string, msg string, args ...interface{})
|
||||
Debugf(name string, id string, msg string, args ...interface{})
|
||||
}
|
||||
|
||||
// List of component names used as parameter to logger functions.
|
||||
const (
|
||||
Bolt3 = "bolt3"
|
||||
Bolt4 = "bolt4"
|
||||
Driver = "driver"
|
||||
Pool = "pool"
|
||||
Router = "router"
|
||||
Session = "session"
|
||||
)
|
||||
|
||||
// Last used component id
|
||||
var id uint32
|
||||
|
||||
// NewId generates a new id atomically.
|
||||
func NewId() string {
|
||||
return strconv.FormatUint(uint64(atomic.AddUint32(&id, 1)), 10)
|
||||
}
|
@ -1,35 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) "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 log
|
||||
|
||||
// Logger implementation that throws away all log events.
|
||||
type Void struct{}
|
||||
|
||||
func (l Void) Error(name, id string, err error) {
|
||||
}
|
||||
|
||||
func (l Void) Infof(name, id string, msg string, args ...interface{}) {
|
||||
}
|
||||
|
||||
func (l Void) Warnf(name, id string, msg string, args ...interface{}) {
|
||||
}
|
||||
|
||||
func (l Void) Debugf(name, id string, msg string, args ...interface{}) {
|
||||
}
|
@ -1,167 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) "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 (
|
||||
"github.com/neo4j/neo4j-go-driver/v4/neo4j/db"
|
||||
)
|
||||
|
||||
type Result interface {
|
||||
// Keys returns the keys available on the result set.
|
||||
Keys() ([]string, error)
|
||||
// Next returns true only if there is a record to be processed.
|
||||
Next() bool
|
||||
// NextRecord returns true if there is a record to be processed, record parameter is set
|
||||
// to point to current record.
|
||||
NextRecord(record **Record) bool
|
||||
// Err returns the latest error that caused this Next to return false.
|
||||
Err() error
|
||||
// Record returns the current record.
|
||||
Record() *Record
|
||||
// Collect fetches all remaining records and returns them.
|
||||
Collect() ([]*Record, error)
|
||||
// Single returns one and only one record from the stream.
|
||||
// If the result stream contains zero or more than one records, error is returned.
|
||||
Single() (*Record, error)
|
||||
// Consume discards all remaining records and returns the summary information
|
||||
// about the statement execution.
|
||||
Consume() (ResultSummary, error)
|
||||
}
|
||||
|
||||
type result struct {
|
||||
conn db.Connection
|
||||
streamHandle db.StreamHandle
|
||||
cypher string
|
||||
params map[string]interface{}
|
||||
record *Record
|
||||
summary *db.Summary
|
||||
err error
|
||||
}
|
||||
|
||||
func newResult(conn db.Connection, str db.StreamHandle, cypher string, params map[string]interface{}) *result {
|
||||
return &result{
|
||||
conn: conn,
|
||||
streamHandle: str,
|
||||
cypher: cypher,
|
||||
params: params,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *result) Keys() ([]string, error) {
|
||||
return r.conn.Keys(r.streamHandle)
|
||||
}
|
||||
|
||||
func (r *result) Next() bool {
|
||||
r.record, r.summary, r.err = r.conn.Next(r.streamHandle)
|
||||
return r.record != nil
|
||||
}
|
||||
|
||||
func (r *result) NextRecord(out **Record) bool {
|
||||
r.record, r.summary, r.err = r.conn.Next(r.streamHandle)
|
||||
if out != nil {
|
||||
*out = r.record
|
||||
}
|
||||
return r.record != nil
|
||||
}
|
||||
|
||||
func (r *result) Record() *Record {
|
||||
return r.record
|
||||
}
|
||||
|
||||
func (r *result) Err() error {
|
||||
return wrapError(r.err)
|
||||
}
|
||||
|
||||
func (r *result) Collect() ([]*Record, error) {
|
||||
recs := make([]*Record, 0, 1024)
|
||||
for r.summary == nil && r.err == nil {
|
||||
r.record, r.summary, r.err = r.conn.Next(r.streamHandle)
|
||||
if r.record != nil {
|
||||
recs = append(recs, r.record)
|
||||
}
|
||||
}
|
||||
if r.err != nil {
|
||||
return nil, wrapError(r.err)
|
||||
}
|
||||
return recs, nil
|
||||
}
|
||||
|
||||
func (r *result) buffer() {
|
||||
r.err = r.conn.Buffer(r.streamHandle)
|
||||
}
|
||||
|
||||
func (r *result) Single() (*Record, error) {
|
||||
// Try retrieving the single record
|
||||
r.record, r.summary, r.err = r.conn.Next(r.streamHandle)
|
||||
if r.err != nil {
|
||||
return nil, wrapError(r.err)
|
||||
}
|
||||
if r.summary != nil {
|
||||
r.err = &UsageError{Message: "Result contains no more records"}
|
||||
return nil, r.err
|
||||
}
|
||||
|
||||
// This is the potential single record
|
||||
single := r.record
|
||||
|
||||
// Probe connection for more records
|
||||
r.record, r.summary, r.err = r.conn.Next(r.streamHandle)
|
||||
if r.record != nil {
|
||||
// There were more records, consume the stream since the user didn't
|
||||
// expect more records and should therefore not use them.
|
||||
r.summary, _ = r.conn.Consume(r.streamHandle)
|
||||
r.err = &UsageError{Message: "Result contains more than one record"}
|
||||
r.record = nil
|
||||
return nil, r.err
|
||||
}
|
||||
if r.err != nil {
|
||||
// Might be more records or not, anyway something is bad.
|
||||
// Both r.record and r.summary are nil at this point which is good.
|
||||
return nil, wrapError(r.err)
|
||||
}
|
||||
// We got the expected summary
|
||||
// r.record contains the single record and r.summary the summary.
|
||||
r.record = single
|
||||
return single, nil
|
||||
}
|
||||
|
||||
func (r *result) toResultSummary() ResultSummary {
|
||||
return &resultSummary{
|
||||
sum: r.summary,
|
||||
cypher: r.cypher,
|
||||
params: r.params,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *result) Consume() (ResultSummary, error) {
|
||||
// Already failed, reuse the internal error, might have been
|
||||
// set by Single to indicate some kind of usage error that "destroyed"
|
||||
// the result.
|
||||
if r.err != nil {
|
||||
return nil, wrapError(r.err)
|
||||
}
|
||||
|
||||
r.record = nil
|
||||
r.summary, r.err = r.conn.Consume(r.streamHandle)
|
||||
if r.err != nil {
|
||||
return nil, wrapError(r.err)
|
||||
}
|
||||
return r.toResultSummary(), nil
|
||||
}
|
@ -1,80 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) "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"
|
||||
|
||||
// Single returns one and only one record from the result stream. Any error passed in
|
||||
// or reported while navigating the result stream is returned without any conversion.
|
||||
// If the result stream contains zero or more than one records error is returned.
|
||||
// record, err := neo4j.Single(session.Run(...))
|
||||
func Single(result Result, err error) (*Record, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return result.Single()
|
||||
}
|
||||
|
||||
// Collect loops through the result stream, collects records into a slice and returns the
|
||||
// resulting slice. Any error passed in or reported while navigating the result stream is
|
||||
// returned without any conversion.
|
||||
// records, err := neo4j.Collect(session.Run(...))
|
||||
func Collect(result Result, err error) ([]*Record, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return result.Collect()
|
||||
}
|
||||
|
||||
// AsRecords passes any existing error or casts from to a slice of records.
|
||||
// Use in combination with Collect and transactional functions:
|
||||
// records, err := neo4j.AsRecords(session.ReadTransaction(func (tx neo4j.Transaction) {
|
||||
// return neo4j.Collect(tx.Run(...))
|
||||
// }))
|
||||
func AsRecords(from interface{}, err error) ([]*Record, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
recs, ok := from.([]*Record)
|
||||
if !ok {
|
||||
return nil, &UsageError{
|
||||
Message: fmt.Sprintf("Expected type []*Record, not %T", from),
|
||||
}
|
||||
}
|
||||
return recs, nil
|
||||
}
|
||||
|
||||
// AsRecord passes any existing error or casts from to a record.
|
||||
// Use in combination with Single and transactional functions:
|
||||
// record, err := neo4j.AsRecord(session.ReadTransaction(func (tx neo4j.Transaction) {
|
||||
// return neo4j.Single(tx.Run(...))
|
||||
// }))
|
||||
func AsRecord(from interface{}, err error) (*Record, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rec, ok := from.(*Record)
|
||||
if !ok {
|
||||
return nil, &UsageError{
|
||||
Message: fmt.Sprintf("Expected type *Record, not %T", from),
|
||||
}
|
||||
}
|
||||
return rec, nil
|
||||
}
|
@ -1,504 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) "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/neo4j/neo4j-go-driver/v4/neo4j/db"
|
||||
"time"
|
||||
)
|
||||
|
||||
// StatementType defines the type of the statement
|
||||
type StatementType int
|
||||
|
||||
const (
|
||||
// StatementTypeUnknown identifies an unknown statement type
|
||||
StatementTypeUnknown StatementType = 0
|
||||
// StatementTypeReadOnly identifies a read-only statement
|
||||
StatementTypeReadOnly StatementType = 1
|
||||
// StatementTypeReadWrite identifies a read-write statement
|
||||
StatementTypeReadWrite StatementType = 2
|
||||
// StatementTypeWriteOnly identifies a write-only statement
|
||||
StatementTypeWriteOnly StatementType = 3
|
||||
// StatementTypeSchemaWrite identifies a schema-write statement
|
||||
StatementTypeSchemaWrite StatementType = 4
|
||||
)
|
||||
|
||||
func (st StatementType) String() string {
|
||||
switch st {
|
||||
case StatementTypeReadOnly:
|
||||
return "r"
|
||||
case StatementTypeReadWrite:
|
||||
return "rw"
|
||||
case StatementTypeWriteOnly:
|
||||
return "w"
|
||||
case StatementTypeSchemaWrite:
|
||||
return "s"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
type ResultSummary interface {
|
||||
// Server returns basic information about the server where the statement is carried out.
|
||||
Server() ServerInfo
|
||||
// Deprecated: since 4.4, will be removed in 5.0. Use Query instead
|
||||
Statement() Statement
|
||||
// Query returns the query that has been executed.
|
||||
Query() Query
|
||||
// StatementType returns type of statement that has been executed.
|
||||
StatementType() StatementType
|
||||
// Counters returns statistics counts for the statement.
|
||||
Counters() Counters
|
||||
// Plan returns statement plan for the executed statement if available, otherwise null.
|
||||
Plan() Plan
|
||||
// Profile returns profiled statement plan for the executed statement if available, otherwise null.
|
||||
Profile() ProfiledPlan
|
||||
// Notifications returns a slice of notifications produced while executing the statement.
|
||||
// The list will be empty if no notifications produced while executing the statement.
|
||||
Notifications() []Notification
|
||||
// ResultAvailableAfter returns the time it took for the server to make the result available for consumption.
|
||||
ResultAvailableAfter() time.Duration
|
||||
// ResultConsumedAfter returns the time it took the server to consume the result.
|
||||
ResultConsumedAfter() time.Duration
|
||||
// Database returns information about the database where the result is obtained from
|
||||
// Returns nil for Neo4j versions prior to v4.
|
||||
// Returns the default "neo4j" database for Community Edition servers.
|
||||
Database() DatabaseInfo
|
||||
}
|
||||
|
||||
// Counters contains statistics about the changes made to the database made as part
|
||||
// of the statement execution.
|
||||
type Counters interface {
|
||||
// Whether there were any updates at all, eg. any of the counters are greater than 0.
|
||||
ContainsUpdates() bool
|
||||
// The number of nodes created.
|
||||
NodesCreated() int
|
||||
// The number of nodes deleted.
|
||||
NodesDeleted() int
|
||||
// The number of relationships created.
|
||||
RelationshipsCreated() int
|
||||
// The number of relationships deleted.
|
||||
RelationshipsDeleted() int
|
||||
PropertiesSet() int
|
||||
// The number of labels added to nodes.
|
||||
LabelsAdded() int
|
||||
// The number of labels removed from nodes.
|
||||
LabelsRemoved() int
|
||||
// The number of indexes added to the schema.
|
||||
IndexesAdded() int
|
||||
// The number of indexes removed from the schema.
|
||||
IndexesRemoved() int
|
||||
// The number of constraints added to the schema.
|
||||
ConstraintsAdded() int
|
||||
// The number of constraints removed from the schema.
|
||||
ConstraintsRemoved() int
|
||||
// The number of system updates
|
||||
SystemUpdates() int
|
||||
// ContainsSystemUpdates indicates whether the query contains system updates
|
||||
ContainsSystemUpdates() bool
|
||||
}
|
||||
|
||||
type Statement interface {
|
||||
Query
|
||||
}
|
||||
|
||||
type Query interface {
|
||||
// Text returns the statement's text.
|
||||
Text() string
|
||||
// Deprecated: since 4.4, will be removed in 5.0. Use Parameters instead
|
||||
Params() map[string]interface{}
|
||||
// Parameters returns the statement's parameters.
|
||||
Parameters() map[string]interface{}
|
||||
}
|
||||
|
||||
// ServerInfo contains basic information of the server.
|
||||
type ServerInfo interface {
|
||||
// Address returns the address of the server.
|
||||
Address() string
|
||||
// Version returns the version of Neo4j running at the server.
|
||||
// Deprecated: since 4.3, this function is deprecated. It will be removed in 5.0. please use Agent, ProtocolVersion, or call the dbms.components procedure instead
|
||||
Version() string
|
||||
Agent() string
|
||||
ProtocolVersion() db.ProtocolVersion
|
||||
}
|
||||
|
||||
// DatabaseInfo contains basic information of the database the query result has been obtained from.
|
||||
type DatabaseInfo interface {
|
||||
Name() string
|
||||
}
|
||||
|
||||
// Plan describes the actual plan that the database planner produced and used (or will use) to execute your statement.
|
||||
// This can be extremely helpful in understanding what a statement is doing, and how to optimize it. For more details,
|
||||
// see the Neo4j Manual. The plan for the statement is a tree of plans - each sub-tree containing zero or more child
|
||||
// plans. The statement starts with the root plan. Each sub-plan is of a specific operator, which describes what
|
||||
// that part of the plan does - for instance, perform an index lookup or filter results.
|
||||
// The Neo4j Manual contains a reference of the available operator types, and these may differ across Neo4j versions.
|
||||
type Plan interface {
|
||||
// Operator returns the operation this plan is performing.
|
||||
Operator() string
|
||||
// Arguments returns the arguments for the operator used.
|
||||
// Many operators have arguments defining their specific behavior. This map contains those arguments.
|
||||
Arguments() map[string]interface{}
|
||||
// Identifiers returns a list of identifiers used by this plan. Identifiers used by this part of the plan.
|
||||
// These can be both identifiers introduced by you, or automatically generated.
|
||||
Identifiers() []string
|
||||
// Children returns zero or more child plans. A plan is a tree, where each child is another plan.
|
||||
// The children are where this part of the plan gets its input records - unless this is an operator that
|
||||
// introduces new records on its own.
|
||||
Children() []Plan
|
||||
}
|
||||
|
||||
// ProfiledPlan is the same as a regular Plan - except this plan has been executed, meaning it also
|
||||
// contains detailed information about how much work each step of the plan incurred on the database.
|
||||
type ProfiledPlan interface {
|
||||
// Operator returns the operation this plan is performing.
|
||||
Operator() string
|
||||
// Arguments returns the arguments for the operator used.
|
||||
// Many operators have arguments defining their specific behavior. This map contains those arguments.
|
||||
Arguments() map[string]interface{}
|
||||
// Identifiers returns a list of identifiers used by this plan. Identifiers used by this part of the plan.
|
||||
// These can be both identifiers introduced by you, or automatically generated.
|
||||
Identifiers() []string
|
||||
// DbHits returns the number of times this part of the plan touched the underlying data stores/
|
||||
DbHits() int64
|
||||
// Records returns the number of records this part of the plan produced.
|
||||
Records() int64
|
||||
// Children returns zero or more child plans. A plan is a tree, where each child is another plan.
|
||||
// The children are where this part of the plan gets its input records - unless this is an operator that
|
||||
// introduces new records on its own.
|
||||
Children() []ProfiledPlan
|
||||
PageCacheMisses() int64
|
||||
PageCacheHits() int64
|
||||
PageCacheHitRatio() float64
|
||||
Time() int64
|
||||
}
|
||||
|
||||
// Notification represents notifications generated when executing a statement.
|
||||
// A notification can be visualized in a client pinpointing problems or other information about the statement.
|
||||
type Notification interface {
|
||||
// Code returns a notification code for the discovered issue of this notification.
|
||||
Code() string
|
||||
// Title returns a short summary of this notification.
|
||||
Title() string
|
||||
// Description returns a longer description of this notification.
|
||||
Description() string
|
||||
// Position returns the position in the statement where this notification points to.
|
||||
// Not all notifications have a unique position to point to and in that case the position would be set to nil.
|
||||
Position() InputPosition
|
||||
// Severity returns the severity level of this notification.
|
||||
Severity() string
|
||||
}
|
||||
|
||||
// InputPosition contains information about a specific position in a statement
|
||||
type InputPosition interface {
|
||||
// Offset returns the character offset referred to by this position; offset numbers start at 0.
|
||||
Offset() int
|
||||
// Line returns the line number referred to by this position; line numbers start at 1.
|
||||
Line() int
|
||||
// Column returns the column number referred to by this position; column numbers start at 1.
|
||||
Column() int
|
||||
}
|
||||
|
||||
type resultSummary struct {
|
||||
sum *db.Summary
|
||||
cypher string
|
||||
params map[string]interface{}
|
||||
}
|
||||
|
||||
func (s *resultSummary) Agent() string {
|
||||
return s.sum.Agent
|
||||
}
|
||||
|
||||
func (s *resultSummary) ProtocolVersion() db.ProtocolVersion {
|
||||
return db.ProtocolVersion{
|
||||
Major: s.sum.Major,
|
||||
Minor: s.sum.Minor,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *resultSummary) Server() ServerInfo {
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *resultSummary) Address() string {
|
||||
return s.sum.ServerName
|
||||
}
|
||||
|
||||
func (s *resultSummary) Version() string {
|
||||
return s.sum.Agent
|
||||
}
|
||||
|
||||
func (s *resultSummary) Statement() Statement {
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *resultSummary) Query() Query {
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *resultSummary) StatementType() StatementType {
|
||||
return StatementType(s.sum.StmntType)
|
||||
}
|
||||
|
||||
func (s *resultSummary) Text() string {
|
||||
return s.cypher
|
||||
}
|
||||
|
||||
func (s *resultSummary) Params() map[string]interface{} {
|
||||
return s.Parameters()
|
||||
}
|
||||
|
||||
func (s *resultSummary) Parameters() map[string]interface{} {
|
||||
return s.params
|
||||
}
|
||||
|
||||
func (s *resultSummary) Counters() Counters {
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *resultSummary) ContainsUpdates() bool {
|
||||
return len(s.sum.Counters) > 0
|
||||
}
|
||||
|
||||
func (s *resultSummary) getCounter(n string) int {
|
||||
if s.sum.Counters == nil {
|
||||
return 0
|
||||
}
|
||||
return s.sum.Counters[n]
|
||||
}
|
||||
|
||||
func (s *resultSummary) SystemUpdates() int {
|
||||
return s.getCounter(db.SystemUpdates)
|
||||
}
|
||||
|
||||
func (s *resultSummary) ContainsSystemUpdates() bool {
|
||||
if s.sum.ContainsSystemUpdates != nil {
|
||||
return *s.sum.ContainsSystemUpdates
|
||||
}
|
||||
return s.getCounter(db.SystemUpdates) > 0
|
||||
}
|
||||
|
||||
func (s *resultSummary) NodesCreated() int {
|
||||
return s.getCounter(db.NodesCreated)
|
||||
}
|
||||
|
||||
func (s *resultSummary) NodesDeleted() int {
|
||||
return s.getCounter(db.NodesDeleted)
|
||||
}
|
||||
|
||||
func (s *resultSummary) RelationshipsCreated() int {
|
||||
return s.getCounter(db.RelationshipsCreated)
|
||||
}
|
||||
|
||||
func (s *resultSummary) RelationshipsDeleted() int {
|
||||
return s.getCounter(db.RelationshipsDeleted)
|
||||
}
|
||||
|
||||
func (s *resultSummary) PropertiesSet() int {
|
||||
return s.getCounter(db.PropertiesSet)
|
||||
}
|
||||
|
||||
func (s *resultSummary) LabelsAdded() int {
|
||||
return s.getCounter(db.LabelsAdded)
|
||||
}
|
||||
|
||||
func (s *resultSummary) LabelsRemoved() int {
|
||||
return s.getCounter(db.LabelsRemoved)
|
||||
}
|
||||
|
||||
func (s *resultSummary) IndexesAdded() int {
|
||||
return s.getCounter(db.IndexesAdded)
|
||||
}
|
||||
|
||||
func (s *resultSummary) IndexesRemoved() int {
|
||||
return s.getCounter(db.IndexesRemoved)
|
||||
}
|
||||
|
||||
func (s *resultSummary) ConstraintsAdded() int {
|
||||
return s.getCounter(db.ConstraintsAdded)
|
||||
}
|
||||
|
||||
func (s *resultSummary) ConstraintsRemoved() int {
|
||||
return s.getCounter(db.ConstraintsRemoved)
|
||||
}
|
||||
|
||||
func (s *resultSummary) ResultAvailableAfter() time.Duration {
|
||||
return time.Duration(s.sum.TFirst) * time.Millisecond
|
||||
}
|
||||
|
||||
func (s *resultSummary) ResultConsumedAfter() time.Duration {
|
||||
return time.Duration(s.sum.TLast) * time.Millisecond
|
||||
}
|
||||
|
||||
func (s *resultSummary) Plan() Plan {
|
||||
if s.sum.Plan == nil {
|
||||
return nil
|
||||
}
|
||||
return &plan{plan: s.sum.Plan}
|
||||
}
|
||||
|
||||
func (s *resultSummary) Database() DatabaseInfo {
|
||||
database := s.sum.Database
|
||||
if database == "" {
|
||||
return nil
|
||||
}
|
||||
return &databaseInfo{name: database}
|
||||
}
|
||||
|
||||
type databaseInfo struct {
|
||||
name string
|
||||
}
|
||||
|
||||
func (d *databaseInfo) Name() string {
|
||||
return d.name
|
||||
}
|
||||
|
||||
type plan struct {
|
||||
plan *db.Plan
|
||||
}
|
||||
|
||||
func (p *plan) Operator() string {
|
||||
return p.plan.Operator
|
||||
}
|
||||
|
||||
func (p *plan) Arguments() map[string]interface{} {
|
||||
return p.plan.Arguments
|
||||
}
|
||||
|
||||
func (p *plan) Identifiers() []string {
|
||||
return p.plan.Identifiers
|
||||
}
|
||||
|
||||
func (p *plan) Children() []Plan {
|
||||
children := make([]Plan, len(p.plan.Children))
|
||||
for i, c := range p.plan.Children {
|
||||
children[i] = &plan{plan: &c}
|
||||
}
|
||||
return children
|
||||
}
|
||||
|
||||
func (s *resultSummary) Profile() ProfiledPlan {
|
||||
if s.sum.ProfiledPlan == nil {
|
||||
return nil
|
||||
}
|
||||
return &profile{profile: s.sum.ProfiledPlan}
|
||||
}
|
||||
|
||||
type profile struct {
|
||||
profile *db.ProfiledPlan
|
||||
}
|
||||
|
||||
func (p *profile) String() string {
|
||||
return fmt.Sprintf("%v", *p.profile)
|
||||
}
|
||||
|
||||
func (p *profile) Operator() string {
|
||||
return p.profile.Operator
|
||||
}
|
||||
|
||||
func (p *profile) Arguments() map[string]interface{} {
|
||||
return p.profile.Arguments
|
||||
}
|
||||
|
||||
func (p *profile) Identifiers() []string {
|
||||
return p.profile.Identifiers
|
||||
}
|
||||
|
||||
func (p *profile) DbHits() int64 {
|
||||
return p.profile.DbHits
|
||||
}
|
||||
|
||||
func (p *profile) Records() int64 {
|
||||
return p.profile.Records
|
||||
}
|
||||
|
||||
func (p *profile) Children() []ProfiledPlan {
|
||||
children := make([]ProfiledPlan, len(p.profile.Children))
|
||||
for i, c := range p.profile.Children {
|
||||
child := c
|
||||
children[i] = &profile{profile: &child}
|
||||
}
|
||||
return children
|
||||
}
|
||||
|
||||
func (p *profile) PageCacheMisses() int64 {
|
||||
return p.profile.PageCacheMisses
|
||||
}
|
||||
|
||||
func (p *profile) PageCacheHits() int64 {
|
||||
return p.profile.PageCacheHits
|
||||
}
|
||||
|
||||
func (p *profile) PageCacheHitRatio() float64 {
|
||||
return p.profile.PageCacheHitRatio
|
||||
}
|
||||
|
||||
func (p *profile) Time() int64 {
|
||||
return p.profile.Time
|
||||
}
|
||||
|
||||
func (s *resultSummary) Notifications() []Notification {
|
||||
if s.sum.Notifications == nil {
|
||||
return nil
|
||||
}
|
||||
notifications := make([]Notification, len(s.sum.Notifications))
|
||||
for i := range s.sum.Notifications {
|
||||
notifications[i] = ¬ification{notification: &s.sum.Notifications[i]}
|
||||
}
|
||||
return notifications
|
||||
}
|
||||
|
||||
type notification struct {
|
||||
notification *db.Notification
|
||||
}
|
||||
|
||||
func (n *notification) Code() string {
|
||||
return n.notification.Code
|
||||
}
|
||||
|
||||
func (n *notification) Title() string {
|
||||
return n.notification.Title
|
||||
}
|
||||
|
||||
func (n *notification) Description() string {
|
||||
return n.notification.Description
|
||||
}
|
||||
|
||||
func (n *notification) Severity() string {
|
||||
return n.notification.Severity
|
||||
}
|
||||
|
||||
func (n *notification) Position() InputPosition {
|
||||
if n.notification.Position == nil {
|
||||
return nil
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func (n *notification) Offset() int {
|
||||
return n.notification.Position.Offset
|
||||
}
|
||||
func (n *notification) Column() int {
|
||||
return n.notification.Position.Column
|
||||
}
|
||||
func (n *notification) Line() int {
|
||||
return n.notification.Position.Line
|
||||
}
|
@ -1,529 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) "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 (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/neo4j/neo4j-go-driver/v4/neo4j/db"
|
||||
"github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/retry"
|
||||
"github.com/neo4j/neo4j-go-driver/v4/neo4j/log"
|
||||
)
|
||||
|
||||
// TransactionWork represents a unit of work that will be executed against the provided
|
||||
// transaction
|
||||
type TransactionWork func(tx Transaction) (interface{}, error)
|
||||
|
||||
// Session represents a logical connection (which is not tied to a physical connection)
|
||||
// to the server
|
||||
type Session interface {
|
||||
// LastBookmark returns the bookmark received following the last successfully completed transaction.
|
||||
// If no bookmark was received or if this transaction was rolled back, the bookmark value will not be changed.
|
||||
LastBookmark() string
|
||||
// BeginTransaction starts a new explicit transaction on this session
|
||||
BeginTransaction(configurers ...func(*TransactionConfig)) (Transaction, error)
|
||||
// ReadTransaction executes the given unit of work in a AccessModeRead transaction with
|
||||
// retry logic in place
|
||||
ReadTransaction(work TransactionWork, configurers ...func(*TransactionConfig)) (interface{}, error)
|
||||
// WriteTransaction executes the given unit of work in a AccessModeWrite transaction with
|
||||
// retry logic in place
|
||||
WriteTransaction(work TransactionWork, configurers ...func(*TransactionConfig)) (interface{}, error)
|
||||
// Run executes an auto-commit statement and returns a result
|
||||
Run(cypher string, params map[string]interface{}, configurers ...func(*TransactionConfig)) (Result, error)
|
||||
// Close closes any open resources and marks this session as unusable
|
||||
Close() error
|
||||
}
|
||||
|
||||
// SessionConfig is used to configure a new session, its zero value uses safe defaults.
|
||||
type SessionConfig struct {
|
||||
// AccessMode used when using Session.Run and explicit transactions. Used to route query to
|
||||
// to read or write servers when running in a cluster. Session.ReadTransaction and Session.WriteTransaction
|
||||
// does not rely on this mode.
|
||||
AccessMode AccessMode
|
||||
// Bookmarks are the initial bookmarks used to ensure that the executing server is at least up
|
||||
// to date to the point represented by the latest of the provided bookmarks. After running commands
|
||||
// on the session the bookmark can be retrieved with Session.LastBookmark. All commands executing
|
||||
// within the same session will automatically use the bookmark from the previous command in the
|
||||
// session.
|
||||
Bookmarks []string
|
||||
// DatabaseName contains the name of the database that the commands in the session will execute on.
|
||||
DatabaseName string
|
||||
// FetchSize defines how many records to pull from server in each batch.
|
||||
// From Bolt protocol v4 (Neo4j 4+) records can be fetched in batches as compared to fetching
|
||||
// all in previous versions.
|
||||
//
|
||||
// If FetchSize is set to FetchDefault, the driver decides the appropriate size. If set to a positive value
|
||||
// that size is used if the underlying protocol supports it otherwise it is ignored.
|
||||
//
|
||||
// To turn off fetching in batches and always fetch everything, set FetchSize to FetchAll.
|
||||
// If a single large result is to be retrieved this is the most performant setting.
|
||||
FetchSize int
|
||||
// Logging target the session will send its Bolt message traces
|
||||
//
|
||||
// Possible to use custom logger (implement log.BoltLogger interface) or
|
||||
// use neo4j.ConsoleBoltLogger.
|
||||
BoltLogger log.BoltLogger
|
||||
// ImpersonatedUser sets the Neo4j user that the session will be acting as.
|
||||
// If not set, the user configured for the driver will be used.
|
||||
//
|
||||
// If user impersonation is used, the default database for that impersonated
|
||||
// user will be used unless DatabaseName is set.
|
||||
//
|
||||
// In the former case, when routing is enabled, using impersonation
|
||||
// without DatabaseName will cause the driver to query the
|
||||
// cluster for the name of the default database of the impersonated user.
|
||||
// This is done at the beginning of the session so that queries are routed
|
||||
// to the correct cluster member (different databases may have different
|
||||
// leaders).
|
||||
ImpersonatedUser string
|
||||
}
|
||||
|
||||
// FetchAll turns off fetching records in batches.
|
||||
const FetchAll = -1
|
||||
|
||||
// FetchDefault lets the driver decide fetch size
|
||||
const FetchDefault = 0
|
||||
|
||||
// Connection pool as seen by the session.
|
||||
type sessionPool interface {
|
||||
Borrow(ctx context.Context, serverNames []string, wait bool, boltLogger log.BoltLogger) (db.Connection, error)
|
||||
Return(c db.Connection)
|
||||
CleanUp()
|
||||
}
|
||||
|
||||
type session struct {
|
||||
config *Config
|
||||
defaultMode db.AccessMode
|
||||
bookmarks []string
|
||||
databaseName string
|
||||
impersonatedUser string
|
||||
getDefaultDbName bool
|
||||
pool sessionPool
|
||||
router sessionRouter
|
||||
txExplicit *transaction
|
||||
txAuto *autoTransaction
|
||||
sleep func(d time.Duration)
|
||||
now func() time.Time
|
||||
logId string
|
||||
log log.Logger
|
||||
throttleTime time.Duration
|
||||
fetchSize int
|
||||
boltLogger log.BoltLogger
|
||||
}
|
||||
|
||||
// Remove empty string bookmarks to check for "bad" callers
|
||||
// To avoid allocating, first check if this is a problem
|
||||
func cleanupBookmarks(bookmarks []string) []string {
|
||||
hasBad := false
|
||||
for _, b := range bookmarks {
|
||||
if len(b) == 0 {
|
||||
hasBad = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !hasBad {
|
||||
return bookmarks
|
||||
}
|
||||
|
||||
cleaned := make([]string, 0, len(bookmarks)-1)
|
||||
for _, b := range bookmarks {
|
||||
if len(b) > 0 {
|
||||
cleaned = append(cleaned, b)
|
||||
}
|
||||
}
|
||||
return cleaned
|
||||
}
|
||||
|
||||
func newSession(config *Config, sessConfig SessionConfig, router sessionRouter, pool sessionPool, logger log.Logger) *session {
|
||||
logId := log.NewId()
|
||||
logger.Debugf(log.Session, logId, "Created")
|
||||
|
||||
fetchSize := config.FetchSize
|
||||
if sessConfig.FetchSize != FetchDefault {
|
||||
fetchSize = sessConfig.FetchSize
|
||||
}
|
||||
|
||||
return &session{
|
||||
config: config,
|
||||
router: router,
|
||||
pool: pool,
|
||||
defaultMode: db.AccessMode(sessConfig.AccessMode),
|
||||
bookmarks: cleanupBookmarks(sessConfig.Bookmarks),
|
||||
databaseName: sessConfig.DatabaseName,
|
||||
impersonatedUser: sessConfig.ImpersonatedUser,
|
||||
getDefaultDbName: sessConfig.DatabaseName == "",
|
||||
sleep: time.Sleep,
|
||||
now: time.Now,
|
||||
log: logger,
|
||||
logId: logId,
|
||||
throttleTime: time.Second * 1,
|
||||
fetchSize: fetchSize,
|
||||
boltLogger: sessConfig.BoltLogger,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *session) LastBookmark() string {
|
||||
// Pick up bookmark from pending auto-commit if there is a bookmark on it
|
||||
if s.txAuto != nil {
|
||||
s.retrieveBookmarks(s.txAuto.conn)
|
||||
}
|
||||
|
||||
// Report bookmark from previously closed connection or from initial set
|
||||
if len(s.bookmarks) > 0 {
|
||||
return s.bookmarks[len(s.bookmarks)-1]
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func (s *session) BeginTransaction(configurers ...func(*TransactionConfig)) (Transaction, error) {
|
||||
// Guard for more than one transaction per session
|
||||
if s.txExplicit != nil {
|
||||
err := &UsageError{Message: "Session already has a pending transaction"}
|
||||
s.log.Error(log.Session, s.logId, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if s.txAuto != nil {
|
||||
s.txAuto.done()
|
||||
}
|
||||
|
||||
// Apply configuration functions
|
||||
config := TransactionConfig{Timeout: 0, Metadata: nil}
|
||||
for _, c := range configurers {
|
||||
c(&config)
|
||||
}
|
||||
|
||||
// Get a connection from the pool. This could fail in clustered environment.
|
||||
conn, err := s.getConnection(s.defaultMode)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Begin transaction
|
||||
txHandle, err := conn.TxBegin(db.TxConfig{
|
||||
Mode: s.defaultMode,
|
||||
Bookmarks: s.bookmarks,
|
||||
Timeout: config.Timeout,
|
||||
Meta: config.Metadata,
|
||||
ImpersonatedUser: s.impersonatedUser,
|
||||
})
|
||||
if err != nil {
|
||||
s.pool.Return(conn)
|
||||
return nil, wrapError(err)
|
||||
}
|
||||
|
||||
// Create transaction wrapper
|
||||
s.txExplicit = &transaction{
|
||||
conn: conn,
|
||||
fetchSize: s.fetchSize,
|
||||
txHandle: txHandle,
|
||||
onClosed: func() {
|
||||
// On transaction closed (rollbacked or committed)
|
||||
s.retrieveBookmarks(conn)
|
||||
s.pool.Return(conn)
|
||||
s.txExplicit = nil
|
||||
},
|
||||
}
|
||||
|
||||
return s.txExplicit, nil
|
||||
}
|
||||
|
||||
func (s *session) runRetriable(
|
||||
mode db.AccessMode,
|
||||
work TransactionWork, configurers ...func(*TransactionConfig)) (interface{}, error) {
|
||||
|
||||
// Guard for more than one transaction per session
|
||||
if s.txExplicit != nil {
|
||||
err := &UsageError{Message: "Session already has a pending transaction"}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if s.txAuto != nil {
|
||||
s.txAuto.done()
|
||||
}
|
||||
|
||||
config := TransactionConfig{Timeout: 0, Metadata: nil}
|
||||
for _, c := range configurers {
|
||||
c(&config)
|
||||
}
|
||||
|
||||
state := retry.State{
|
||||
MaxTransactionRetryTime: s.config.MaxTransactionRetryTime,
|
||||
Log: s.log,
|
||||
LogName: log.Session,
|
||||
LogId: s.logId,
|
||||
Now: s.now,
|
||||
Sleep: s.sleep,
|
||||
Throttle: retry.Throttler(s.throttleTime),
|
||||
MaxDeadConnections: s.config.MaxConnectionPoolSize,
|
||||
Router: s.router,
|
||||
DatabaseName: s.databaseName,
|
||||
}
|
||||
for state.Continue() {
|
||||
if workResult, successfullyCompleted := s.tryRun(&state, mode, &config, work); successfullyCompleted {
|
||||
return workResult, nil
|
||||
}
|
||||
}
|
||||
|
||||
// When retries have occurred, wrap the error, the last error is always added but
|
||||
// cause is only set when the retry logic could detect something strange.
|
||||
if state.LastErrWasRetryable {
|
||||
err := newTransactionExecutionLimit(state.Errs, state.Causes)
|
||||
s.log.Error(log.Session, s.logId, err)
|
||||
return nil, err
|
||||
}
|
||||
// Wrap and log the error if it belongs to the driver
|
||||
err := wrapError(state.LastErr)
|
||||
switch err.(type) {
|
||||
case *UsageError, *ConnectivityError:
|
||||
s.log.Error(log.Session, s.logId, err)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
func (s *session) tryRun(state *retry.State, mode db.AccessMode, config *TransactionConfig, work TransactionWork) (interface{}, bool) {
|
||||
conn, err := s.getConnection(mode)
|
||||
if err != nil {
|
||||
state.OnFailure(conn, err, false)
|
||||
return nil, false
|
||||
}
|
||||
|
||||
defer s.pool.Return(conn)
|
||||
txHandle, err := conn.TxBegin(db.TxConfig{
|
||||
Mode: mode,
|
||||
Bookmarks: s.bookmarks,
|
||||
Timeout: config.Timeout,
|
||||
Meta: config.Metadata,
|
||||
ImpersonatedUser: s.impersonatedUser,
|
||||
})
|
||||
if err != nil {
|
||||
state.OnFailure(conn, err, false)
|
||||
return nil, false
|
||||
}
|
||||
|
||||
tx := retryableTransaction{conn: conn, fetchSize: s.fetchSize, txHandle: txHandle}
|
||||
x, err := work(&tx)
|
||||
// Evaluate the returned error from all the work for retryable, this means
|
||||
// that client can mess up the error handling.
|
||||
if err != nil {
|
||||
// If the client returns a client specific error that means that
|
||||
// client wants to rollback. We don't do an explicit rollback here
|
||||
// but instead realy on pool invoking reset on the connection, that
|
||||
// will do an implicit rollback.
|
||||
state.OnFailure(conn, err, false)
|
||||
return nil, false
|
||||
}
|
||||
|
||||
err = conn.TxCommit(txHandle)
|
||||
if err != nil {
|
||||
state.OnFailure(conn, err, true)
|
||||
return nil, false
|
||||
}
|
||||
|
||||
s.retrieveBookmarks(conn)
|
||||
return x, true
|
||||
}
|
||||
|
||||
func (s *session) ReadTransaction(
|
||||
work TransactionWork, configurers ...func(*TransactionConfig)) (interface{}, error) {
|
||||
|
||||
return s.runRetriable(db.ReadMode, work, configurers...)
|
||||
}
|
||||
|
||||
func (s *session) WriteTransaction(
|
||||
work TransactionWork, configurers ...func(*TransactionConfig)) (interface{}, error) {
|
||||
|
||||
return s.runRetriable(db.WriteMode, work, configurers...)
|
||||
}
|
||||
|
||||
func (s *session) getServers(ctx context.Context, mode db.AccessMode) ([]string, error) {
|
||||
if mode == db.ReadMode {
|
||||
return s.router.Readers(ctx, s.bookmarks, s.databaseName, s.boltLogger)
|
||||
} else {
|
||||
return s.router.Writers(ctx, s.bookmarks, s.databaseName, s.boltLogger)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *session) getConnection(mode db.AccessMode) (db.Connection, error) {
|
||||
var ctx context.Context
|
||||
if s.config.ConnectionAcquisitionTimeout > 0 {
|
||||
var cancel context.CancelFunc
|
||||
ctx, cancel = context.WithTimeout(context.Background(), s.config.ConnectionAcquisitionTimeout)
|
||||
if cancel != nil {
|
||||
defer cancel()
|
||||
}
|
||||
} else {
|
||||
ctx = context.Background()
|
||||
}
|
||||
// If client requested user impersonation but provided no database we need to retrieve
|
||||
// the name of the configured default database for that user before asking for a connection
|
||||
if s.getDefaultDbName {
|
||||
defaultDb, err := s.router.GetNameOfDefaultDatabase(ctx, s.bookmarks, s.impersonatedUser, s.boltLogger)
|
||||
if err != nil {
|
||||
return nil, wrapError(err)
|
||||
}
|
||||
s.log.Debugf(log.Session, s.logId, "Retrieved default database for impersonated user, uses db '%s'", defaultDb)
|
||||
s.databaseName = defaultDb
|
||||
s.getDefaultDbName = false
|
||||
}
|
||||
servers, err := s.getServers(ctx, mode)
|
||||
if err != nil {
|
||||
return nil, wrapError(err)
|
||||
}
|
||||
|
||||
conn, err := s.pool.Borrow(ctx, servers, s.config.ConnectionAcquisitionTimeout != 0, s.boltLogger)
|
||||
if err != nil {
|
||||
return nil, wrapError(err)
|
||||
}
|
||||
|
||||
// Select database on server
|
||||
if s.databaseName != db.DefaultDatabase {
|
||||
dbSelector, ok := conn.(db.DatabaseSelector)
|
||||
if !ok {
|
||||
s.pool.Return(conn)
|
||||
return nil, &UsageError{Message: "Database does not support multidatabase"}
|
||||
}
|
||||
dbSelector.SelectDatabase(s.databaseName)
|
||||
}
|
||||
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
func (s *session) retrieveBookmarks(conn db.Connection) {
|
||||
if conn == nil {
|
||||
return
|
||||
}
|
||||
bookmark := conn.Bookmark()
|
||||
if len(bookmark) > 0 {
|
||||
s.bookmarks = []string{bookmark}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *session) Run(
|
||||
cypher string, params map[string]interface{}, configurers ...func(*TransactionConfig)) (Result, error) {
|
||||
|
||||
if s.txExplicit != nil {
|
||||
err := &UsageError{Message: "Trying to run auto-commit transaction while in explicit transaction"}
|
||||
s.log.Error(log.Session, s.logId, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if s.txAuto != nil {
|
||||
s.txAuto.done()
|
||||
}
|
||||
|
||||
config := TransactionConfig{Timeout: 0, Metadata: nil}
|
||||
for _, c := range configurers {
|
||||
c(&config)
|
||||
}
|
||||
|
||||
var (
|
||||
conn db.Connection
|
||||
err error
|
||||
)
|
||||
for {
|
||||
conn, err = s.getConnection(s.defaultMode)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = conn.ForceReset()
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
stream, err := conn.Run(
|
||||
db.Command{
|
||||
Cypher: cypher,
|
||||
Params: params,
|
||||
FetchSize: s.fetchSize,
|
||||
},
|
||||
db.TxConfig{
|
||||
Mode: s.defaultMode,
|
||||
Bookmarks: s.bookmarks,
|
||||
Timeout: config.Timeout,
|
||||
Meta: config.Metadata,
|
||||
ImpersonatedUser: s.impersonatedUser,
|
||||
})
|
||||
if err != nil {
|
||||
s.pool.Return(conn)
|
||||
return nil, wrapError(err)
|
||||
}
|
||||
|
||||
s.txAuto = &autoTransaction{
|
||||
conn: conn,
|
||||
res: newResult(conn, stream, cypher, params),
|
||||
onClosed: func() {
|
||||
s.retrieveBookmarks(conn)
|
||||
s.pool.Return(conn)
|
||||
s.txAuto = nil
|
||||
},
|
||||
}
|
||||
|
||||
return s.txAuto.res, nil
|
||||
}
|
||||
|
||||
func (s *session) Close() error {
|
||||
var err error
|
||||
|
||||
if s.txExplicit != nil {
|
||||
err = s.txExplicit.Close()
|
||||
}
|
||||
|
||||
if s.txAuto != nil {
|
||||
s.txAuto.discard()
|
||||
}
|
||||
|
||||
s.log.Debugf(log.Session, s.logId, "Closed")
|
||||
|
||||
// Schedule cleanups
|
||||
go func() {
|
||||
s.pool.CleanUp()
|
||||
s.router.CleanUp()
|
||||
}()
|
||||
return err
|
||||
}
|
||||
|
||||
type sessionWithError struct {
|
||||
err error
|
||||
}
|
||||
|
||||
func (s *sessionWithError) LastBookmark() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (s *sessionWithError) BeginTransaction(configurers ...func(*TransactionConfig)) (Transaction, error) {
|
||||
return nil, s.err
|
||||
}
|
||||
func (s *sessionWithError) ReadTransaction(work TransactionWork, configurers ...func(*TransactionConfig)) (interface{}, error) {
|
||||
return nil, s.err
|
||||
}
|
||||
func (s *sessionWithError) WriteTransaction(work TransactionWork, configurers ...func(*TransactionConfig)) (interface{}, error) {
|
||||
return nil, s.err
|
||||
}
|
||||
func (s *sessionWithError) Run(cypher string, params map[string]interface{}, configurers ...func(*TransactionConfig)) (Result, error) {
|
||||
return nil, s.err
|
||||
}
|
||||
func (s *sessionWithError) Close() error {
|
||||
return s.err
|
||||
}
|
@ -1,131 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) "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 (
|
||||
"github.com/neo4j/neo4j-go-driver/v4/neo4j/db"
|
||||
)
|
||||
|
||||
// Transaction represents a transaction in the Neo4j database
|
||||
type Transaction interface {
|
||||
// Run executes a statement on this transaction and returns a result
|
||||
Run(cypher string, params map[string]interface{}) (Result, error)
|
||||
// Commit commits the transaction
|
||||
Commit() error
|
||||
// Rollback rolls back the transaction
|
||||
Rollback() error
|
||||
// Close rolls back the actual transaction if it's not already committed/rolled back
|
||||
// and closes all resources associated with this transaction
|
||||
Close() error
|
||||
}
|
||||
|
||||
// Transaction implementation when explicit transaction started
|
||||
type transaction struct {
|
||||
conn db.Connection
|
||||
fetchSize int
|
||||
txHandle db.TxHandle
|
||||
done bool
|
||||
err error
|
||||
onClosed func()
|
||||
}
|
||||
|
||||
func (tx *transaction) Run(cypher string, params map[string]interface{}) (Result, error) {
|
||||
stream, err := tx.conn.RunTx(tx.txHandle, db.Command{Cypher: cypher, Params: params, FetchSize: tx.fetchSize})
|
||||
if err != nil {
|
||||
return nil, wrapError(err)
|
||||
}
|
||||
return newResult(tx.conn, stream, cypher, params), nil
|
||||
}
|
||||
|
||||
func (tx *transaction) Commit() error {
|
||||
if tx.done {
|
||||
return tx.err
|
||||
}
|
||||
tx.err = tx.conn.TxCommit(tx.txHandle)
|
||||
tx.done = true
|
||||
tx.onClosed()
|
||||
return wrapError(tx.err)
|
||||
}
|
||||
|
||||
func (tx *transaction) Rollback() error {
|
||||
if tx.done {
|
||||
return tx.err
|
||||
}
|
||||
tx.err = tx.conn.TxRollback(tx.txHandle)
|
||||
tx.done = true
|
||||
tx.onClosed()
|
||||
return wrapError(tx.err)
|
||||
}
|
||||
|
||||
func (tx *transaction) Close() error {
|
||||
return tx.Rollback()
|
||||
}
|
||||
|
||||
// Transaction implementation used as parameter to transactional functions
|
||||
type retryableTransaction struct {
|
||||
conn db.Connection
|
||||
fetchSize int
|
||||
txHandle db.TxHandle
|
||||
}
|
||||
|
||||
func (tx *retryableTransaction) Run(cypher string, params map[string]interface{}) (Result, error) {
|
||||
stream, err := tx.conn.RunTx(tx.txHandle, db.Command{Cypher: cypher, Params: params, FetchSize: tx.fetchSize})
|
||||
if err != nil {
|
||||
return nil, wrapError(err)
|
||||
}
|
||||
return newResult(tx.conn, stream, cypher, params), nil
|
||||
}
|
||||
|
||||
func (tx *retryableTransaction) Commit() error {
|
||||
return &UsageError{Message: "Commit not allowed on retryable transaction"}
|
||||
}
|
||||
|
||||
func (tx *retryableTransaction) Rollback() error {
|
||||
return &UsageError{Message: "Rollback not allowed on retryable transaction"}
|
||||
}
|
||||
|
||||
func (tx *retryableTransaction) Close() error {
|
||||
return &UsageError{Message: "Close not allowed on retryable transaction"}
|
||||
}
|
||||
|
||||
// Represents an auto commit transaction.
|
||||
// Does not implement the Transaction interface.
|
||||
type autoTransaction struct {
|
||||
conn db.Connection
|
||||
res *result
|
||||
closed bool
|
||||
onClosed func()
|
||||
}
|
||||
|
||||
func (tx *autoTransaction) done() {
|
||||
if !tx.closed {
|
||||
tx.res.buffer()
|
||||
tx.closed = true
|
||||
tx.onClosed()
|
||||
}
|
||||
}
|
||||
|
||||
func (tx *autoTransaction) discard() {
|
||||
if !tx.closed {
|
||||
tx.res.Consume()
|
||||
tx.closed = true
|
||||
tx.onClosed()
|
||||
}
|
||||
}
|
@ -1,70 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) "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 "time"
|
||||
|
||||
// TransactionConfig holds the settings for explicit and auto-commit transactions. Actual configuration is expected
|
||||
// to be done using configuration functions that are predefined, i.e. 'WithTxTimeout' and 'WithTxMetadata', or one
|
||||
// that you could write by your own.
|
||||
type TransactionConfig struct {
|
||||
// Timeout is the configured transaction timeout.
|
||||
Timeout time.Duration
|
||||
// Metadata is the configured transaction metadata that will be attached to the underlying transaction.
|
||||
Metadata map[string]interface{}
|
||||
}
|
||||
|
||||
// WithTxTimeout returns a transaction configuration function that applies a timeout to a transaction.
|
||||
//
|
||||
// To apply a transaction timeout to an explicit transaction:
|
||||
// session.BeginTransaction(WithTxTimeout(5*time.Second))
|
||||
//
|
||||
// To apply a transaction timeout to an auto-commit transaction:
|
||||
// session.Run("RETURN 1", nil, WithTxTimeout(5*time.Second))
|
||||
//
|
||||
// To apply a transaction timeout to a read transaction function:
|
||||
// session.ReadTransaction(DoWork, WithTxTimeout(5*time.Second))
|
||||
//
|
||||
// To apply a transaction timeout to a write transaction function:
|
||||
// session.WriteTransaction(DoWork, WithTxTimeout(5*time.Second))
|
||||
func WithTxTimeout(timeout time.Duration) func(*TransactionConfig) {
|
||||
return func(config *TransactionConfig) {
|
||||
config.Timeout = timeout
|
||||
}
|
||||
}
|
||||
|
||||
// WithTxMetadata returns a transaction configuration function that attaches metadata to a transaction.
|
||||
//
|
||||
// To attach a metadata to an explicit transaction:
|
||||
// session.BeginTransaction(WithTxMetadata(map[string)interface{}{"work-id": 1}))
|
||||
//
|
||||
// To attach a metadata to an auto-commit transaction:
|
||||
// session.Run("RETURN 1", nil, WithTxMetadata(map[string)interface{}{"work-id": 1}))
|
||||
//
|
||||
// To attach a metadata to a read transaction function:
|
||||
// session.ReadTransaction(DoWork, WithTxMetadata(map[string)interface{}{"work-id": 1}))
|
||||
//
|
||||
// To attach a metadata to a write transaction function:
|
||||
// session.WriteTransaction(DoWork, WithTxMetadata(map[string)interface{}{"work-id": 1}))
|
||||
func WithTxMetadata(metadata map[string]interface{}) func(*TransactionConfig) {
|
||||
return func(config *TransactionConfig) {
|
||||
config.Metadata = metadata
|
||||
}
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) "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
|
||||
|
||||
const UserAgent = "Go Driver/4.4"
|
@ -1,201 +0,0 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
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.
|
@ -1,98 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) "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 (
|
||||
"time"
|
||||
|
||||
"github.com/neo4j/neo4j-go-driver/v5/neo4j/db"
|
||||
"github.com/neo4j/neo4j-go-driver/v5/neo4j/dbtype"
|
||||
)
|
||||
|
||||
// Aliases to simplify client usage (fewer imports) and to provide some backwards
|
||||
// compatibility with 1.x driver.
|
||||
//
|
||||
// A separate dbtype package is needed to avoid circular package references and to avoid
|
||||
// unnecessary copying/conversions between structs since serializing/deserializing is
|
||||
// handled within bolt package and bolt package is used from this package.
|
||||
type (
|
||||
Point2D = dbtype.Point2D
|
||||
Point3D = dbtype.Point3D
|
||||
Date = dbtype.Date
|
||||
LocalTime = dbtype.LocalTime
|
||||
LocalDateTime = dbtype.LocalDateTime
|
||||
Time = dbtype.Time
|
||||
OffsetTime = dbtype.Time
|
||||
Duration = dbtype.Duration
|
||||
Node = dbtype.Node
|
||||
Relationship = dbtype.Relationship
|
||||
Path = dbtype.Path
|
||||
Record = db.Record
|
||||
)
|
||||
|
||||
// DateOf creates a neo4j.Date from time.Time.
|
||||
// Hour, minute, second and nanoseconds are set to zero and location is set to UTC.
|
||||
//
|
||||
// Conversion can also be done by casting a time.Time to neo4j.Date but beware that time
|
||||
// components and location will be left as is but ignored when used as query parameter.
|
||||
func DateOf(t time.Time) Date {
|
||||
y, m, d := t.Date()
|
||||
t = time.Date(y, m, d, 0, 0, 0, 0, time.UTC)
|
||||
return dbtype.Date(t)
|
||||
}
|
||||
|
||||
// LocalTimeOf creates a neo4j.LocalTime from time.Time.
|
||||
// Year, month and day are set to zero and location is set to local.
|
||||
//
|
||||
// Conversion can also be done by casting a time.Time to neo4j.LocalTime but beware that date
|
||||
// components and location will be left as is but ignored when used as query parameter.
|
||||
func LocalTimeOf(t time.Time) LocalTime {
|
||||
t = time.Date(0, 0, 0, t.Hour(), t.Minute(), t.Second(), t.Nanosecond(), time.Local)
|
||||
return dbtype.LocalTime(t)
|
||||
}
|
||||
|
||||
// LocalDateTimeOf creates a neo4j.Local from time.Time.
|
||||
//
|
||||
// Conversion can also be done by casting a time.Time to neo4j.LocalTime but beware that location
|
||||
// will be left as is but interpreted as local when used as query parameter.
|
||||
func LocalDateTimeOf(t time.Time) LocalDateTime {
|
||||
t = time.Date(t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second(), t.Nanosecond(), time.Local)
|
||||
return dbtype.LocalDateTime(t)
|
||||
}
|
||||
|
||||
// OffsetTimeOf creates a neo4j.OffsetTime from time.Time.
|
||||
// Year, month and day are set to zero and location is set to "Offset" using zone offset from
|
||||
// time.Time.
|
||||
//
|
||||
// Conversion can also be done by casting a time.Time to neo4j.OffsetTime but beware that date
|
||||
// components and location will be left as is but ignored when used as query parameter. Since
|
||||
// location will contain the original value, the value "offset" will not be used by the driver
|
||||
// but the actual name of the location in time.Time and that offset.
|
||||
func OffsetTimeOf(t time.Time) OffsetTime {
|
||||
_, offset := t.Zone()
|
||||
l := time.FixedZone("Offset", int(offset))
|
||||
t = time.Date(0, 0, 0, t.Hour(), t.Minute(), t.Second(), t.Nanosecond(), l)
|
||||
return dbtype.Time(t)
|
||||
}
|
||||
|
||||
// DurationOf creates neo4j.Duration from specified time parts.
|
||||
func DurationOf(months, days, seconds int64, nanos int) Duration {
|
||||
return Duration{Months: months, Days: days, Seconds: seconds, Nanos: nanos}
|
||||
}
|
@ -1,104 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) "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
|
||||
|
||||
// AuthToken contains credentials to be sent over to the neo4j server.
|
||||
type AuthToken struct {
|
||||
tokens map[string]interface{}
|
||||
}
|
||||
|
||||
const keyScheme = "scheme"
|
||||
const schemeNone = "none"
|
||||
const schemeBasic = "basic"
|
||||
const schemeKerberos = "kerberos"
|
||||
const schemeBearer = "bearer"
|
||||
const keyPrincipal = "principal"
|
||||
const keyCredentials = "credentials"
|
||||
const keyRealm = "realm"
|
||||
|
||||
// NoAuth generates an empty authentication token
|
||||
func NoAuth() AuthToken {
|
||||
return AuthToken{tokens: map[string]interface{}{
|
||||
keyScheme: schemeNone,
|
||||
}}
|
||||
}
|
||||
|
||||
// BasicAuth generates a basic authentication token with provided username, password and realm
|
||||
func BasicAuth(username string, password string, realm string) AuthToken {
|
||||
tokens := map[string]interface{}{
|
||||
keyScheme: schemeBasic,
|
||||
keyPrincipal: username,
|
||||
keyCredentials: password,
|
||||
}
|
||||
|
||||
if realm != "" {
|
||||
tokens[keyRealm] = realm
|
||||
}
|
||||
|
||||
return AuthToken{tokens: tokens}
|
||||
}
|
||||
|
||||
// KerberosAuth generates a kerberos authentication token with provided base-64 encoded kerberos ticket
|
||||
func KerberosAuth(ticket string) AuthToken {
|
||||
token := AuthToken{
|
||||
tokens: map[string]interface{}{
|
||||
keyScheme: schemeKerberos,
|
||||
// Backwards compatibility: Neo4j servers pre 4.4 require the presence of the principal.
|
||||
keyPrincipal: "",
|
||||
keyCredentials: ticket,
|
||||
},
|
||||
}
|
||||
|
||||
return token
|
||||
}
|
||||
|
||||
// BearerAuth generates an authentication token with the provided base-64 value generated by a Single Sign-On provider
|
||||
func BearerAuth(token string) AuthToken {
|
||||
result := AuthToken{
|
||||
tokens: map[string]interface{}{
|
||||
keyScheme: schemeBearer,
|
||||
keyCredentials: token,
|
||||
},
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// CustomAuth generates a custom authentication token with provided parameters
|
||||
func CustomAuth(scheme string, username string, password string, realm string, parameters map[string]interface{}) AuthToken {
|
||||
tokens := map[string]interface{}{
|
||||
keyScheme: scheme,
|
||||
keyPrincipal: username,
|
||||
}
|
||||
|
||||
if password != "" {
|
||||
tokens[keyCredentials] = password
|
||||
}
|
||||
|
||||
if realm != "" {
|
||||
tokens[keyRealm] = realm
|
||||
}
|
||||
|
||||
if len(parameters) > 0 {
|
||||
tokens["parameters"] = parameters
|
||||
}
|
||||
|
||||
return AuthToken{tokens: tokens}
|
||||
}
|
@ -1,57 +0,0 @@
|
||||
package neo4j
|
||||
|
||||
/*
|
||||
Bookmarks is a holder for server-side bookmarks which are used for causally chaining sessions.
|
||||
|
||||
See also CombineBookmarks.
|
||||
|
||||
Note:
|
||||
Will be changed from being a type alias to being a struct in 6.0. Please use BookmarksFromRawValues for construction
|
||||
from raw values and BookmarksToRawValues for accessing the raw values.
|
||||
*/
|
||||
type Bookmarks = []string
|
||||
|
||||
/*
|
||||
CombineBookmarks is a helper method to combine []Bookmarks into a single Bookmarks instance.
|
||||
|
||||
Let s1, s2, s3 be Session interfaces. You can easily causally chain the sessions like so:
|
||||
|
||||
s4 := driver.NewSession(neo4j.SessionConfig{
|
||||
Bookmarks: neo4j.CombineBookmarks(s1.LastBookmarks(), s2.LastBookmarks(), s3.LastBookmarks()),
|
||||
})
|
||||
|
||||
The server will then make sure to execute all transactions in s4 after any that were already executed in s1, s2, or s3
|
||||
at the time of calling LastBookmarks.
|
||||
*/
|
||||
func CombineBookmarks(bookmarks... Bookmarks) Bookmarks {
|
||||
var lenSum int
|
||||
for _, b := range bookmarks {
|
||||
lenSum += len(b)
|
||||
}
|
||||
res := make([]string, lenSum)
|
||||
var i int
|
||||
for _, b := range bookmarks {
|
||||
i += copy(res[i:], b)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
/*
|
||||
BookmarksToRawValues exposes the raw server-side bookmarks.
|
||||
|
||||
You should not need to use this method unless you want to serialize bookmarks.
|
||||
See Session.LastBookmarks and CombineBookmarks for alternatives.
|
||||
*/
|
||||
func BookmarksToRawValues(bookmarks Bookmarks) []string {
|
||||
return bookmarks
|
||||
}
|
||||
|
||||
/*
|
||||
BookmarksFromRawValues creates Bookmarks from raw server-side bookmarks.
|
||||
|
||||
You should not need to use this method unless you want to de-serialize bookmarks.
|
||||
See Session.LastBookmarks and CombineBookmarks for alternatives.
|
||||
*/
|
||||
func BookmarksFromRawValues(values... string) Bookmarks {
|
||||
return values
|
||||
}
|
@ -1,205 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) "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 (
|
||||
"crypto/x509"
|
||||
"math"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/neo4j/neo4j-go-driver/v5/neo4j/log"
|
||||
)
|
||||
|
||||
// A Config contains options that can be used to customize certain
|
||||
// aspects of the driver
|
||||
type Config struct {
|
||||
// RootCAs defines the set of certificate authorities that the driver trusts. If set
|
||||
// to nil, the driver uses hosts system certificates.
|
||||
//
|
||||
// The trusted certificates are used to validate connections for URI schemes 'bolt+s'
|
||||
// and 'neo4j+s'.
|
||||
RootCAs *x509.CertPool
|
||||
|
||||
// Logging target the driver will send its log outputs
|
||||
//
|
||||
// Possible to use custom logger (implement log.Logger interface) or
|
||||
// use neo4j.ConsoleLogger.
|
||||
//
|
||||
// default: No Op Logger (log.Void)
|
||||
Log log.Logger
|
||||
// Resolver that would be used to resolve initial router address. This may
|
||||
// be useful if you want to provide more than one URL for initial router.
|
||||
// If not specified, the URL provided to NewDriver or NewDriverWithContext
|
||||
// is used as the initial router.
|
||||
//
|
||||
// default: nil
|
||||
AddressResolver ServerAddressResolver
|
||||
// Maximum amount of time a retryable operation would continue retrying. It
|
||||
// cannot be specified as a negative value.
|
||||
//
|
||||
// default: 30 * time.Second
|
||||
MaxTransactionRetryTime time.Duration
|
||||
// Maximum number of connections per URL to allow on this driver. It
|
||||
// cannot be specified as 0 and negative values are interpreted as
|
||||
// math.MaxInt32.
|
||||
//
|
||||
// default: 100
|
||||
MaxConnectionPoolSize int
|
||||
// Maximum connection lifetime on pooled connections. Values less than
|
||||
// or equal to 0 disables the lifetime check.
|
||||
//
|
||||
// default: 1 * time.Hour
|
||||
MaxConnectionLifetime time.Duration
|
||||
// Maximum amount of time to either acquire an idle connection from the pool
|
||||
// or create a new connection (when the pool is not full). Negative values
|
||||
// result in an infinite wait time, whereas a 0 value results in no timeout.
|
||||
// If no timeout is set, an immediate failure follows when there are no
|
||||
// available connections.
|
||||
//
|
||||
// Since 4.3, this setting competes with connection read timeout hints, if
|
||||
// the server-side option called
|
||||
// "dbms.connector.bolt.connection_keep_alive_for_requests" is enabled.
|
||||
// The read timeout is automatically applied and may result in connections
|
||||
// being dropped if they are idle beyond the corresponding period.
|
||||
//
|
||||
// Since 5.0, this setting competes with the context-aware APIs. These APIs
|
||||
// are discoverable through NewDriverWithContext.
|
||||
// When a connection needs to be acquired from the internal driver
|
||||
// connection pool and the user-provided context.Context carries a deadline
|
||||
// (through context.WithTimeout or context.WithDeadline), the earliest
|
||||
// deadline wins. Connections are still subject to early terminations if a read timeout
|
||||
// hint is received.
|
||||
//
|
||||
// default: 1 * time.Minute
|
||||
ConnectionAcquisitionTimeout time.Duration
|
||||
// Connect timeout that will be set on underlying sockets. Values less than
|
||||
// or equal to 0 results in no timeout being applied.
|
||||
//
|
||||
// Since 5.0, this setting competes with the context-aware APIs. These APIs
|
||||
// are discoverable through NewDriverWithContext.
|
||||
// If a connection needs to be created when one of these APIs is called
|
||||
// and the user-provided context.Context carries a deadline (through
|
||||
// context.WithTimeout or context.WithDeadline), the TCP dialer will pick
|
||||
// the earliest between this setting and the context deadline.
|
||||
//
|
||||
// default: 5 * time.Second
|
||||
SocketConnectTimeout time.Duration
|
||||
// Whether to enable TCP keep alive on underlying sockets.
|
||||
//
|
||||
// default: true
|
||||
SocketKeepalive bool
|
||||
// Optionally override the user agent string sent to Neo4j server.
|
||||
//
|
||||
// default: neo4j.UserAgent
|
||||
UserAgent string
|
||||
// FetchSize defines how many records to pull from server in each batch.
|
||||
// From Bolt protocol v4 (Neo4j 4+) records can be fetched in batches as
|
||||
// compared to fetching all in previous versions.
|
||||
//
|
||||
// If FetchSize is set to FetchDefault, the driver decides the appropriate
|
||||
// size. If set to a positive value that size is used if the underlying
|
||||
// protocol supports it otherwise it is ignored.
|
||||
//
|
||||
// To turn off fetching in batches and always fetch everything, set
|
||||
// FetchSize to FetchAll.
|
||||
// If a single large result is to be retrieved, this is the most performant
|
||||
// setting.
|
||||
FetchSize int
|
||||
}
|
||||
|
||||
func defaultConfig() *Config {
|
||||
return &Config{
|
||||
AddressResolver: nil,
|
||||
MaxTransactionRetryTime: 30 * time.Second,
|
||||
MaxConnectionPoolSize: 100,
|
||||
MaxConnectionLifetime: 1 * time.Hour,
|
||||
ConnectionAcquisitionTimeout: 1 * time.Minute,
|
||||
SocketConnectTimeout: 5 * time.Second,
|
||||
SocketKeepalive: true,
|
||||
RootCAs: nil,
|
||||
UserAgent: UserAgent,
|
||||
FetchSize: FetchDefault,
|
||||
}
|
||||
}
|
||||
|
||||
func validateAndNormaliseConfig(config *Config) error {
|
||||
// Max Transaction Retry Time
|
||||
if config.MaxTransactionRetryTime < 0 {
|
||||
return &UsageError{Message: "Maximum transaction retry time cannot be smaller than 0"}
|
||||
}
|
||||
|
||||
// Max Connection Pool Size
|
||||
if config.MaxConnectionPoolSize == 0 {
|
||||
return &UsageError{Message: "Maximum connection pool cannot be 0"}
|
||||
}
|
||||
|
||||
if config.MaxConnectionPoolSize < 0 {
|
||||
config.MaxConnectionPoolSize = math.MaxInt32
|
||||
}
|
||||
|
||||
// Max Connection Lifetime
|
||||
if config.MaxConnectionLifetime < 0 {
|
||||
config.MaxConnectionLifetime = 0
|
||||
}
|
||||
|
||||
// Connection Acquisition Timeout
|
||||
if config.ConnectionAcquisitionTimeout < 0 {
|
||||
config.ConnectionAcquisitionTimeout = -1
|
||||
}
|
||||
|
||||
// Socket Connect Timeout
|
||||
if config.SocketConnectTimeout < 0 {
|
||||
config.SocketConnectTimeout = 0
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ServerAddress represents a host and port. Host can either be an IP address or a DNS name.
|
||||
// Both IPv4 and IPv6 hosts are supported.
|
||||
type ServerAddress interface {
|
||||
// Hostname returns the host portion of this ServerAddress.
|
||||
Hostname() string
|
||||
// Port returns the port portion of this ServerAddress.
|
||||
Port() string
|
||||
}
|
||||
|
||||
// ServerAddressResolver is a function type that defines the resolver function used by the routing driver to
|
||||
// resolve the initial address used to create the driver.
|
||||
type ServerAddressResolver func(address ServerAddress) []ServerAddress
|
||||
|
||||
func newServerAddressURL(hostname string, port string) *url.URL {
|
||||
if hostname == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
hostAndPort := hostname
|
||||
if port != "" {
|
||||
hostAndPort = hostAndPort + ":" + port
|
||||
}
|
||||
|
||||
return &url.URL{Host: hostAndPort}
|
||||
}
|
||||
|
||||
// NewServerAddress generates a ServerAddress with provided hostname and port information.
|
||||
func NewServerAddress(hostname string, port string) ServerAddress {
|
||||
return newServerAddressURL(hostname, port)
|
||||
}
|
@ -1,52 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) "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 (
|
||||
"github.com/neo4j/neo4j-go-driver/v5/neo4j/log"
|
||||
)
|
||||
|
||||
// LogLevel is the type that default logging implementations use for available
|
||||
// log levels
|
||||
type LogLevel int
|
||||
|
||||
const (
|
||||
// ERROR is the level that error messages are written
|
||||
ERROR LogLevel = 1
|
||||
// WARNING is the level that warning messages are written
|
||||
WARNING = 2
|
||||
// INFO is the level that info messages are written
|
||||
INFO = 3
|
||||
// DEBUG is the level that debug messages are written
|
||||
DEBUG = 4
|
||||
)
|
||||
|
||||
func ConsoleLogger(level LogLevel) *log.Console {
|
||||
return &log.Console{
|
||||
Errors: level >= ERROR,
|
||||
Warns: level >= WARNING,
|
||||
Infos: level >= INFO,
|
||||
Debugs: level >= DEBUG,
|
||||
}
|
||||
}
|
||||
|
||||
func ConsoleBoltLogger() *log.ConsoleBoltLogger {
|
||||
return &log.ConsoleBoltLogger{}
|
||||
}
|
@ -1,134 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) "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 db
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Database server failed to fulfill request.
|
||||
type Neo4jError struct {
|
||||
Code string
|
||||
Msg string
|
||||
parsed bool
|
||||
classification string
|
||||
category string
|
||||
title string
|
||||
}
|
||||
|
||||
func (e *Neo4jError) Error() string {
|
||||
return fmt.Sprintf("Neo4jError: %s (%s)", e.Code, e.Msg)
|
||||
}
|
||||
|
||||
func (e *Neo4jError) Classification() string {
|
||||
e.parse()
|
||||
return e.classification
|
||||
}
|
||||
|
||||
func (e *Neo4jError) Category() string {
|
||||
e.parse()
|
||||
return e.category
|
||||
}
|
||||
|
||||
func (e *Neo4jError) Title() string {
|
||||
e.parse()
|
||||
return e.title
|
||||
}
|
||||
|
||||
// parse parses code from Neo4j into usable parts.
|
||||
// Code Neo.ClientError.General.ForbiddenReadOnlyDatabase is split into:
|
||||
// Classification: ClientError
|
||||
// Category: General
|
||||
// Title: ForbiddernReadOnlyDatabase
|
||||
func (e *Neo4jError) parse() {
|
||||
if e.parsed {
|
||||
return
|
||||
}
|
||||
e.parsed = true
|
||||
parts := strings.Split(e.Code, ".")
|
||||
if len(parts) != 4 {
|
||||
return
|
||||
}
|
||||
e.classification = parts[1]
|
||||
e.category = parts[2]
|
||||
e.title = parts[3]
|
||||
}
|
||||
|
||||
func (e *Neo4jError) IsAuthenticationFailed() bool {
|
||||
return e.Code == "Neo.ClientError.Security.Unauthorized"
|
||||
}
|
||||
|
||||
func (e *Neo4jError) IsRetriableTransient() bool {
|
||||
e.parse()
|
||||
if e.classification != "TransientError" {
|
||||
return false
|
||||
}
|
||||
switch e.Code {
|
||||
// Happens when client aborts transaction, should not retry
|
||||
case "Neo.TransientError.Transaction.Terminated", "Neo.TransientError.Transaction.LockClientStopped":
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (e *Neo4jError) IsRetriableCluster() bool {
|
||||
switch e.Code {
|
||||
case "Neo.ClientError.Cluster.NotALeader", "Neo.ClientError.General.ForbiddenOnReadOnlyDatabase":
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type FeatureNotSupportedError struct {
|
||||
Server string
|
||||
Feature string
|
||||
Reason string
|
||||
}
|
||||
|
||||
func (e *FeatureNotSupportedError) Error() string {
|
||||
return fmt.Sprintf("Server %s does not support: %s (%s)", e.Server, e.Feature, e.Reason)
|
||||
}
|
||||
|
||||
type UnsupportedTypeError struct {
|
||||
Type reflect.Type
|
||||
}
|
||||
|
||||
func (e *UnsupportedTypeError) Error() string {
|
||||
return fmt.Sprintf("Usage of type '%s' is not supported", e.Type.String())
|
||||
}
|
||||
|
||||
type ProtocolError struct {
|
||||
MessageType string
|
||||
Field string
|
||||
Err string
|
||||
}
|
||||
|
||||
func (e *ProtocolError) Error() string {
|
||||
if e.MessageType == "" {
|
||||
return fmt.Sprintf("ProtocolError: %s", e.Err)
|
||||
}
|
||||
if e.Field == "" {
|
||||
return fmt.Sprintf("ProtocolError: message %s could not be hydrated: %s", e.MessageType, e.Err)
|
||||
}
|
||||
return fmt.Sprintf("ProtocolError: field %s of message %s could not be hydrated: %s",
|
||||
e.Field, e.MessageType, e.Err)
|
||||
}
|
@ -1,44 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) "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 db
|
||||
|
||||
type Record struct {
|
||||
// Values contains all the values in the record.
|
||||
Values []interface{}
|
||||
// Keys contains names of the values in the record.
|
||||
// Should not be modified. Same instance is used for all records within the same result.
|
||||
Keys []string
|
||||
}
|
||||
|
||||
// Get returns the value corresponding to the given key along with a boolean that is true if
|
||||
// a value was found and false if there were no key with the given name.
|
||||
//
|
||||
// If there are a lot of keys in combination with a lot of records to iterate, consider to retrieve
|
||||
// values from Values slice directly or make a key -> index map before iterating. This implementation
|
||||
// does not make or use a key -> index map since the overhead of making the map might not be beneficial
|
||||
// for small and few records.
|
||||
func (r Record) Get(key string) (interface{}, bool) {
|
||||
for i, ckey := range r.Keys {
|
||||
if key == ckey {
|
||||
return r.Values[i], true
|
||||
}
|
||||
}
|
||||
return nil, false
|
||||
}
|
@ -1,142 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) "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 db
|
||||
|
||||
// Definitions of these should correspond to public API
|
||||
type StatementType int
|
||||
|
||||
const (
|
||||
StatementTypeUnknown StatementType = 0
|
||||
StatementTypeRead StatementType = 1
|
||||
StatementTypeReadWrite StatementType = 2
|
||||
StatementTypeWrite StatementType = 3
|
||||
StatementTypeSchemaWrite StatementType = 4
|
||||
)
|
||||
|
||||
// Counter key names
|
||||
const (
|
||||
NodesCreated = "nodes-created"
|
||||
NodesDeleted = "nodes-deleted"
|
||||
RelationshipsCreated = "relationships-created"
|
||||
RelationshipsDeleted = "relationships-deleted"
|
||||
PropertiesSet = "properties-set"
|
||||
LabelsAdded = "labels-added"
|
||||
LabelsRemoved = "labels-removed"
|
||||
IndexesAdded = "indexes-added"
|
||||
IndexesRemoved = "indexes-removed"
|
||||
ConstraintsAdded = "constraints-added"
|
||||
ConstraintsRemoved = "constraints-removed"
|
||||
SystemUpdates = "system-updates"
|
||||
)
|
||||
|
||||
// Plan describes the actual plan that the database planner produced and used (or will use) to execute your statement.
|
||||
// This can be extremely helpful in understanding what a statement is doing, and how to optimize it. For more details,
|
||||
// see the Neo4j Manual. The plan for the statement is a tree of plans - each sub-tree containing zero or more child
|
||||
// plans. The statement starts with the root plan. Each sub-plan is of a specific operator, which describes what
|
||||
// that part of the plan does - for instance, perform an index lookup or filter results.
|
||||
// The Neo4j Manual contains a reference of the available operator types, and these may differ across Neo4j versions.
|
||||
type Plan struct {
|
||||
// Operator is the operation this plan is performing.
|
||||
Operator string
|
||||
// Arguments for the operator.
|
||||
// Many operators have arguments defining their specific behavior. This map contains those arguments.
|
||||
Arguments map[string]interface{}
|
||||
// List of identifiers used by this plan. Identifiers used by this part of the plan.
|
||||
// These can be both identifiers introduced by you, or automatically generated.
|
||||
Identifiers []string
|
||||
// Zero or more child plans. A plan is a tree, where each child is another plan.
|
||||
// The children are where this part of the plan gets its input records - unless this is an operator that
|
||||
// introduces new records on its own.
|
||||
Children []Plan
|
||||
}
|
||||
|
||||
// ProfiledPlan is the same as a regular Plan - except this plan has been executed, meaning it also
|
||||
// contains detailed information about how much work each step of the plan incurred on the database.
|
||||
type ProfiledPlan struct {
|
||||
// Operator contains the operation this plan is performing.
|
||||
Operator string
|
||||
// Arguments contains the arguments for the operator used.
|
||||
// Many operators have arguments defining their specific behavior. This map contains those arguments.
|
||||
Arguments map[string]interface{}
|
||||
// Identifiers contains a list of identifiers used by this plan. Identifiers used by this part of the plan.
|
||||
// These can be both identifiers introduced by you, or automatically generated.
|
||||
Identifiers []string
|
||||
// DbHits contains the number of times this part of the plan touched the underlying data stores/
|
||||
DbHits int64
|
||||
// Records contains the number of records this part of the plan produced.
|
||||
Records int64
|
||||
// Children contains zero or more child plans. A plan is a tree, where each child is another plan.
|
||||
// The children are where this part of the plan gets its input records - unless this is an operator that
|
||||
// introduces new records on its own.
|
||||
Children []ProfiledPlan
|
||||
PageCacheMisses int64
|
||||
PageCacheHits int64
|
||||
PageCacheHitRatio float64
|
||||
Time int64
|
||||
}
|
||||
|
||||
// Notification represents notifications generated when executing a statement.
|
||||
// A notification can be visualized in a client pinpointing problems or other information about the statement.
|
||||
type Notification struct {
|
||||
// Code contains a notification code for the discovered issue of this notification.
|
||||
Code string
|
||||
// Title contains a short summary of this notification.
|
||||
Title string
|
||||
// Description contains a longer description of this notification.
|
||||
Description string
|
||||
// Position contains the position in the statement where this notification points to.
|
||||
// Not all notifications have a unique position to point to and in that case the position would be set to nil.
|
||||
Position *InputPosition
|
||||
// Severity contains the severity level of this notification.
|
||||
Severity string
|
||||
}
|
||||
|
||||
// InputPosition contains information about a specific position in a statement
|
||||
type InputPosition struct {
|
||||
// Offset contains the character offset referred to by this position; offset numbers start at 0.
|
||||
Offset int
|
||||
// Line contains the line number referred to by this position; line numbers start at 1.
|
||||
Line int
|
||||
// Column contains the column number referred to by this position; column numbers start at 1.
|
||||
Column int
|
||||
}
|
||||
|
||||
type ProtocolVersion struct {
|
||||
Major int
|
||||
Minor int
|
||||
}
|
||||
|
||||
type Summary struct {
|
||||
Bookmark string
|
||||
StmntType StatementType
|
||||
ServerName string
|
||||
Agent string
|
||||
Major int
|
||||
Minor int
|
||||
Counters map[string]int
|
||||
TFirst int64
|
||||
TLast int64
|
||||
Plan *Plan
|
||||
ProfiledPlan *ProfiledPlan
|
||||
Notifications []Notification
|
||||
Database string
|
||||
ContainsSystemUpdates *bool
|
||||
ContainsUpdates *bool
|
||||
}
|
@ -1,46 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) "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 dbtype contains definitions of supported database types.
|
||||
package dbtype
|
||||
|
||||
// Node represents a node in the neo4j graph database
|
||||
type Node struct {
|
||||
Id int64 // Id of this node.
|
||||
Labels []string // Labels attached to this Node.
|
||||
Props map[string]interface{} // Properties of this Node.
|
||||
}
|
||||
|
||||
// Relationship represents a relationship in the neo4j graph database
|
||||
type Relationship struct {
|
||||
Id int64 // Identity of this Relationship.
|
||||
StartId int64 // Identity of the start node of this Relationship.
|
||||
EndId int64 // Identity of the end node of this Relationship.
|
||||
Type string // Type of this Relationship.
|
||||
Props map[string]interface{} // Properties of this Relationship.
|
||||
}
|
||||
|
||||
// Path represents a directed sequence of relationships between two nodes.
|
||||
// This generally represents a traversal or walk through a graph and maintains a direction separate from that of any
|
||||
// relationships traversed. It is allowed to be of size 0, meaning there are no relationships in it. In this case,
|
||||
// it contains only a single node which is both the start and the end of the path.
|
||||
type Path struct {
|
||||
Nodes []Node // All the nodes in the path.
|
||||
Relationships []Relationship
|
||||
}
|
@ -1,49 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) "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 dbtype
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Point2D represents a two dimensional point in a particular coordinate reference system.
|
||||
type Point2D struct {
|
||||
X float64
|
||||
Y float64
|
||||
SpatialRefId uint32 // Id of coordinate reference system.
|
||||
}
|
||||
|
||||
// Point3D represents a three dimensional point in a particular coordinate reference system.
|
||||
type Point3D struct {
|
||||
X float64
|
||||
Y float64
|
||||
Z float64
|
||||
SpatialRefId uint32 // Id of coordinate reference system.
|
||||
}
|
||||
|
||||
// String returns string representation of this point.
|
||||
func (p Point2D) String() string {
|
||||
return fmt.Sprintf("Point{srId=%d, x=%f, y=%f}", p.SpatialRefId, p.X, p.Y)
|
||||
}
|
||||
|
||||
// String returns string representation of this point.
|
||||
func (p Point3D) String() string {
|
||||
return fmt.Sprintf("Point{srId=%d, x=%f, y=%f, z=%f}", p.SpatialRefId, p.X, p.Y, p.Z)
|
||||
}
|
@ -1,89 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) "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 dbtype
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Cypher DateTime corresponds to Go time.Time
|
||||
|
||||
type (
|
||||
Time time.Time // Time since start of day with timezone information
|
||||
Date time.Time // Date value, without a time zone and time related components.
|
||||
LocalTime time.Time // Time since start of day in local timezone
|
||||
LocalDateTime time.Time // Date and time in local timezone
|
||||
)
|
||||
|
||||
// Time casts LocalDateTime to time.Time
|
||||
func (t LocalDateTime) Time() time.Time {
|
||||
return time.Time(t)
|
||||
}
|
||||
|
||||
// Time casts LocalTime to time.Time
|
||||
func (t LocalTime) Time() time.Time {
|
||||
return time.Time(t)
|
||||
}
|
||||
|
||||
// Time casts Date to time.Time
|
||||
func (t Date) Time() time.Time {
|
||||
return time.Time(t)
|
||||
}
|
||||
|
||||
// Time casts Time to time.Time
|
||||
func (t Time) Time() time.Time {
|
||||
return time.Time(t)
|
||||
}
|
||||
|
||||
// Duration represents temporal amount containing months, days, seconds and nanoseconds.
|
||||
// Supports longer durations than time.Duration
|
||||
type Duration struct {
|
||||
Months int64
|
||||
Days int64
|
||||
Seconds int64
|
||||
Nanos int
|
||||
}
|
||||
|
||||
// String returns the string representation of this Duration in ISO-8601 compliant form.
|
||||
func (d Duration) String() string {
|
||||
sign := ""
|
||||
if d.Seconds < 0 && d.Nanos > 0 {
|
||||
d.Seconds++
|
||||
d.Nanos = int(time.Second) - d.Nanos
|
||||
|
||||
if d.Seconds == 0 {
|
||||
sign = "-"
|
||||
}
|
||||
}
|
||||
|
||||
timePart := ""
|
||||
if d.Nanos == 0 {
|
||||
timePart = fmt.Sprintf("%s%d", sign, d.Seconds)
|
||||
} else {
|
||||
timePart = fmt.Sprintf("%s%d.%09d", sign, d.Seconds, d.Nanos)
|
||||
}
|
||||
|
||||
return fmt.Sprintf("P%dM%dDT%sS", d.Months, d.Days, timePart)
|
||||
}
|
||||
|
||||
func (d1 Duration) Equal(d2 Duration) bool {
|
||||
return d1.Months == d2.Months && d1.Days == d2.Days && d1.Seconds == d2.Seconds && d1.Nanos == d2.Nanos
|
||||
}
|
@ -1,49 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) "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 (
|
||||
"context"
|
||||
"github.com/neo4j/neo4j-go-driver/v5/neo4j/internal/db"
|
||||
"github.com/neo4j/neo4j-go-driver/v5/neo4j/log"
|
||||
)
|
||||
|
||||
// A router implementation that never routes
|
||||
type directRouter struct {
|
||||
address string
|
||||
}
|
||||
|
||||
func (r *directRouter) Readers(ctx context.Context, bookmarks []string, database string, boltLogger log.BoltLogger) ([]string, error) {
|
||||
return []string{r.address}, nil
|
||||
}
|
||||
|
||||
func (r *directRouter) Writers(ctx context.Context, bookmarks []string, database string, boltLogger log.BoltLogger) ([]string, error) {
|
||||
return []string{r.address}, nil
|
||||
}
|
||||
|
||||
func (r *directRouter) GetNameOfDefaultDatabase(ctx context.Context, bookmarks []string, user string, boltLogger log.BoltLogger) (string, error) {
|
||||
return db.DefaultDatabase, nil
|
||||
}
|
||||
|
||||
func (r *directRouter) Invalidate(database string) {
|
||||
}
|
||||
|
||||
func (r *directRouter) CleanUp() {
|
||||
}
|
@ -1,87 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) "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 provides required functionality to connect and execute statements against a Neo4j Database.
|
||||
package neo4j
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
// Driver represents a pool(s) of connections to a neo4j server or cluster. It's
|
||||
// safe for concurrent use.
|
||||
// Deprecated: please use DriverWithContext instead. This interface will be removed in 6.0.
|
||||
type Driver interface {
|
||||
// Target returns the url this driver is bootstrapped
|
||||
Target() url.URL
|
||||
// NewSession creates a new session based on the specified session configuration.
|
||||
NewSession(config SessionConfig) Session
|
||||
// VerifyConnectivity checks that the driver can connect to a remote server or cluster by
|
||||
// establishing a network connection with the remote. Returns nil if succesful
|
||||
// or error describing the problem.
|
||||
VerifyConnectivity() error
|
||||
// Close the driver and all underlying connections
|
||||
Close() error
|
||||
}
|
||||
|
||||
// NewDriver is the entry point to the neo4j driver to create an instance of a Driver. It is the first function to
|
||||
// be called in order to establish a connection to a neo4j database. It requires a Bolt URI and an authentication
|
||||
// token as parameters and can also take optional configuration function(s) as variadic parameters.
|
||||
//
|
||||
// In order to connect to a single instance database, you need to pass a URI with scheme 'bolt', 'bolt+s' or 'bolt+ssc'.
|
||||
// driver, err = NewDriver("bolt://db.server:7687", BasicAuth(username, password))
|
||||
//
|
||||
// In order to connect to a causal cluster database, you need to pass a URI with scheme 'neo4j', 'neo4j+s' or 'neo4j+ssc'
|
||||
// and its host part set to be one of the core cluster members.
|
||||
// driver, err = NewDriver("neo4j://core.db.server:7687", BasicAuth(username, password))
|
||||
//
|
||||
// You can override default configuration options by providing a configuration function(s)
|
||||
// driver, err = NewDriver(uri, BasicAuth(username, password), function (config *Config) {
|
||||
// config.MaxConnectionPoolSize = 10
|
||||
// })
|
||||
//
|
||||
// Deprecated: please use NewDriverWithContext instead. This function will be removed in 6.0.
|
||||
func NewDriver(target string, auth AuthToken, configurers ...func(*Config)) (Driver, error) {
|
||||
delegate, err := NewDriverWithContext(target, auth, configurers...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &driver{delegate: delegate}, nil
|
||||
}
|
||||
|
||||
type driver struct {
|
||||
delegate DriverWithContext
|
||||
}
|
||||
|
||||
func (d *driver) Target() url.URL {
|
||||
return d.delegate.Target()
|
||||
}
|
||||
|
||||
func (d *driver) NewSession(config SessionConfig) Session {
|
||||
return d.delegate.NewSession(config).legacy()
|
||||
}
|
||||
|
||||
func (d *driver) VerifyConnectivity() error {
|
||||
return d.delegate.VerifyConnectivity(context.Background())
|
||||
}
|
||||
|
||||
func (d *driver) Close() error {
|
||||
return d.delegate.Close(context.Background())
|
||||
}
|
@ -1,286 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) "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 provides required functionality to connect and execute statements against a Neo4j Database.
|
||||
package neo4j
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/neo4j/neo4j-go-driver/v5/neo4j/internal/db"
|
||||
"net/url"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/neo4j/neo4j-go-driver/v5/neo4j/log"
|
||||
|
||||
"github.com/neo4j/neo4j-go-driver/v5/neo4j/internal/connector"
|
||||
"github.com/neo4j/neo4j-go-driver/v5/neo4j/internal/pool"
|
||||
"github.com/neo4j/neo4j-go-driver/v5/neo4j/internal/router"
|
||||
)
|
||||
|
||||
// AccessMode defines modes that routing driver decides to which cluster member
|
||||
// a connection should be opened.
|
||||
type AccessMode int
|
||||
|
||||
const (
|
||||
// AccessModeWrite tells the driver to use a connection to 'Leader'
|
||||
AccessModeWrite AccessMode = 0
|
||||
// AccessModeRead tells the driver to use a connection to one of the 'Follower' or 'Read Replica'.
|
||||
AccessModeRead AccessMode = 1
|
||||
)
|
||||
|
||||
// DriverWithContext represents a pool(s) of connections to a neo4j server or cluster. It's
|
||||
// safe for concurrent use.
|
||||
type DriverWithContext interface {
|
||||
// Target returns the url this driver is bootstrapped
|
||||
Target() url.URL
|
||||
// NewSession creates a new session based on the specified session configuration.
|
||||
NewSession(config SessionConfig) SessionWithContext
|
||||
// VerifyConnectivity checks that the driver can connect to a remote server or cluster by
|
||||
// establishing a network connection with the remote. Returns nil if successful
|
||||
// or error describing the problem.
|
||||
VerifyConnectivity(ctx context.Context) error
|
||||
// Close the driver and all underlying connections
|
||||
Close(ctx context.Context) error
|
||||
}
|
||||
|
||||
// NewDriverWithContext is the entry point to the neo4j driver to create an instance of a Driver. It is the first function to
|
||||
// be called in order to establish a connection to a neo4j database. It requires a Bolt URI and an authentication
|
||||
// token as parameters and can also take optional configuration function(s) as variadic parameters.
|
||||
//
|
||||
// In order to connect to a single instance database, you need to pass a URI with scheme 'bolt', 'bolt+s' or 'bolt+ssc'.
|
||||
// driver, err = NewDriverWithContext("bolt://db.server:7687", BasicAuth(username, password))
|
||||
//
|
||||
// In order to connect to a causal cluster database, you need to pass a URI with scheme 'neo4j', 'neo4j+s' or 'neo4j+ssc'
|
||||
// and its host part set to be one of the core cluster members.
|
||||
// driver, err = NewDriverWithContext("neo4j://core.db.server:7687", BasicAuth(username, password))
|
||||
//
|
||||
// You can override default configuration options by providing a configuration function(s)
|
||||
// driver, err = NewDriverWithContext(uri, BasicAuth(username, password), function (config *Config) {
|
||||
// config.MaxConnectionPoolSize = 10
|
||||
// })
|
||||
func NewDriverWithContext(target string, auth AuthToken, configurers ...func(*Config)) (DriverWithContext, error) {
|
||||
parsed, err := url.Parse(target)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
d := driverWithContext{target: parsed}
|
||||
|
||||
routing := true
|
||||
d.connector.Network = "tcp"
|
||||
address := parsed.Host
|
||||
switch parsed.Scheme {
|
||||
case "bolt":
|
||||
routing = false
|
||||
d.connector.SkipEncryption = true
|
||||
case "bolt+unix":
|
||||
// bolt+unix://<path to socket>
|
||||
routing = false
|
||||
d.connector.SkipEncryption = true
|
||||
d.connector.Network = "unix"
|
||||
if parsed.Host != "" {
|
||||
return nil, &UsageError{
|
||||
Message: fmt.Sprintf("Host part should be empty for scheme %s", parsed.Scheme),
|
||||
}
|
||||
}
|
||||
address = parsed.Path
|
||||
case "bolt+s":
|
||||
routing = false
|
||||
case "bolt+ssc":
|
||||
d.connector.SkipVerify = true
|
||||
routing = false
|
||||
case "neo4j":
|
||||
d.connector.SkipEncryption = true
|
||||
case "neo4j+ssc":
|
||||
d.connector.SkipVerify = true
|
||||
case "neo4j+s":
|
||||
default:
|
||||
return nil, &UsageError{
|
||||
Message: fmt.Sprintf("URI scheme %s is not supported", parsed.Scheme),
|
||||
}
|
||||
}
|
||||
|
||||
if parsed.Host != "" && parsed.Port() == "" {
|
||||
address += ":7687"
|
||||
parsed.Host = address
|
||||
}
|
||||
|
||||
if !routing && len(parsed.RawQuery) > 0 {
|
||||
return nil, &UsageError{
|
||||
Message: fmt.Sprintf("Routing context is not supported for URL scheme %s", parsed.Scheme),
|
||||
}
|
||||
}
|
||||
|
||||
// Apply client hooks for setting up configuration
|
||||
d.config = defaultConfig()
|
||||
for _, configurer := range configurers {
|
||||
configurer(d.config)
|
||||
}
|
||||
if err := validateAndNormaliseConfig(d.config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Setup logging
|
||||
d.log = d.config.Log
|
||||
if d.log == nil {
|
||||
// Default to void logger
|
||||
d.log = &log.Void{}
|
||||
}
|
||||
d.logId = log.NewId()
|
||||
|
||||
routingContext, err := routingContextFromUrl(routing, parsed)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Continue to setup connector
|
||||
d.connector.DialTimeout = d.config.SocketConnectTimeout
|
||||
d.connector.SocketKeepAlive = d.config.SocketKeepalive
|
||||
d.connector.UserAgent = d.config.UserAgent
|
||||
d.connector.RootCAs = d.config.RootCAs
|
||||
d.connector.Log = d.log
|
||||
d.connector.Auth = auth.tokens
|
||||
d.connector.RoutingContext = routingContext
|
||||
|
||||
// Let the pool use the same logid as the driver to simplify log reading.
|
||||
d.pool = pool.New(d.config.MaxConnectionPoolSize, d.config.MaxConnectionLifetime, d.connector.Connect, d.log, d.logId)
|
||||
|
||||
if !routing {
|
||||
d.router = &directRouter{address: address}
|
||||
} else {
|
||||
var routersResolver func() []string
|
||||
addressResolverHook := d.config.AddressResolver
|
||||
if addressResolverHook != nil {
|
||||
routersResolver = func() []string {
|
||||
addresses := addressResolverHook(parsed)
|
||||
servers := make([]string, len(addresses))
|
||||
for i, a := range addresses {
|
||||
servers[i] = fmt.Sprintf("%s:%s", a.Hostname(), a.Port())
|
||||
}
|
||||
return servers
|
||||
}
|
||||
}
|
||||
// Let the router use the same logid as the driver to simplify log reading.
|
||||
d.router = router.New(address, routersResolver, routingContext, d.pool, d.log, d.logId)
|
||||
}
|
||||
|
||||
d.log.Infof(log.Driver, d.logId, "Created { target: %s }", address)
|
||||
return &d, nil
|
||||
}
|
||||
|
||||
const routingContextAddressKey = "address"
|
||||
|
||||
func routingContextFromUrl(useRouting bool, u *url.URL) (map[string]string, error) {
|
||||
if !useRouting {
|
||||
return nil, nil
|
||||
}
|
||||
queryValues := u.Query()
|
||||
routingContext := make(map[string]string, len(queryValues)+1 /*For address*/)
|
||||
for k, vs := range queryValues {
|
||||
if len(vs) > 1 {
|
||||
return nil, &UsageError{
|
||||
Message: fmt.Sprintf("Duplicated routing context key '%s'", k),
|
||||
}
|
||||
}
|
||||
if len(vs) == 0 {
|
||||
return nil, &UsageError{
|
||||
Message: fmt.Sprintf("Empty routing context key '%s'", k),
|
||||
}
|
||||
}
|
||||
v := vs[0]
|
||||
v = strings.TrimSpace(v)
|
||||
if len(v) == 0 {
|
||||
return nil, &UsageError{
|
||||
Message: fmt.Sprintf("Empty routing context value for key '%s'", k),
|
||||
}
|
||||
}
|
||||
if k == routingContextAddressKey {
|
||||
return nil, &UsageError{Message: fmt.Sprintf("Illegal key '%s' for routing context", k)}
|
||||
}
|
||||
routingContext[k] = v
|
||||
}
|
||||
routingContext[routingContextAddressKey] = u.Host
|
||||
return routingContext, nil
|
||||
}
|
||||
|
||||
type sessionRouter interface {
|
||||
// Readers returns the list of servers that can serve reads on the requested database.
|
||||
Readers(ctx context.Context, bookmarks []string, database string, boltLogger log.BoltLogger) ([]string, error)
|
||||
// Writers returns the list of servers that can serve writes on the requested database.
|
||||
Writers(ctx context.Context, bookmarks []string, database string, boltLogger log.BoltLogger) ([]string, error)
|
||||
// GetNameOfDefaultDatabase returns the name of the default database for the specified user.
|
||||
// The correct database name is needed when requesting readers or writers.
|
||||
GetNameOfDefaultDatabase(ctx context.Context, bookmarks []string, user string, boltLogger log.BoltLogger) (string, error)
|
||||
Invalidate(database string)
|
||||
CleanUp()
|
||||
}
|
||||
|
||||
type driverWithContext struct {
|
||||
target *url.URL
|
||||
config *Config
|
||||
pool *pool.Pool
|
||||
mut sync.Mutex
|
||||
connector connector.Connector
|
||||
router sessionRouter
|
||||
logId string
|
||||
log log.Logger
|
||||
}
|
||||
|
||||
func (d *driverWithContext) Target() url.URL {
|
||||
return *d.target
|
||||
}
|
||||
|
||||
func (d *driverWithContext) NewSession(config SessionConfig) SessionWithContext {
|
||||
if config.DatabaseName == "" {
|
||||
config.DatabaseName = db.DefaultDatabase
|
||||
}
|
||||
|
||||
d.mut.Lock()
|
||||
defer d.mut.Unlock()
|
||||
if d.pool == nil {
|
||||
return &erroredSessionWithContext{
|
||||
err: &UsageError{Message: "Trying to create session on closed driver"}}
|
||||
}
|
||||
return newSessionWithContext(d.config, config, d.router, d.pool, d.log)
|
||||
}
|
||||
|
||||
func (d *driverWithContext) VerifyConnectivity(ctx context.Context) error {
|
||||
session := d.NewSession(SessionConfig{AccessMode: AccessModeRead})
|
||||
defer session.Close(ctx)
|
||||
result, err := session.Run(ctx, "RETURN 1 AS n", nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = result.Consume(ctx)
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *driverWithContext) Close(ctx context.Context) error {
|
||||
d.mut.Lock()
|
||||
defer d.mut.Unlock()
|
||||
// Safeguard against closing more than once
|
||||
if d.pool != nil {
|
||||
d.pool.Close(ctx)
|
||||
}
|
||||
d.pool = nil
|
||||
d.log.Infof(log.Driver, d.logId, "Closed")
|
||||
return nil
|
||||
}
|
@ -1,154 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) "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/neo4j/neo4j-go-driver/v5/neo4j/db"
|
||||
"github.com/neo4j/neo4j-go-driver/v5/neo4j/internal/bolt"
|
||||
"github.com/neo4j/neo4j-go-driver/v5/neo4j/internal/connector"
|
||||
"github.com/neo4j/neo4j-go-driver/v5/neo4j/internal/pool"
|
||||
"github.com/neo4j/neo4j-go-driver/v5/neo4j/internal/retry"
|
||||
"github.com/neo4j/neo4j-go-driver/v5/neo4j/internal/router"
|
||||
"io"
|
||||
"net"
|
||||
)
|
||||
|
||||
// Neo4jError represents errors originating from Neo4j service.
|
||||
// Alias for convenience. This error is defined in db package and
|
||||
// used internally.
|
||||
type Neo4jError = db.Neo4jError
|
||||
|
||||
// UsageError represents errors caused by incorrect usage of the driver API.
|
||||
// This does not include Cypher syntax (those errors will be Neo4jError).
|
||||
type UsageError struct {
|
||||
Message string
|
||||
}
|
||||
|
||||
func (e *UsageError) Error() string {
|
||||
return e.Message
|
||||
}
|
||||
|
||||
// TransactionExecutionLimit error indicates that a retryable transaction has
|
||||
// failed due to reaching a limit like a timeout or maximum number of attempts.
|
||||
type TransactionExecutionLimit struct {
|
||||
Errors []error
|
||||
Causes []string
|
||||
}
|
||||
|
||||
func newTransactionExecutionLimit(errors []error, causes []string) *TransactionExecutionLimit {
|
||||
tel := &TransactionExecutionLimit{Errors: make([]error, len(errors)), Causes: causes}
|
||||
for i, err := range errors {
|
||||
tel.Errors[i] = wrapError(err)
|
||||
}
|
||||
|
||||
return tel
|
||||
}
|
||||
|
||||
func (e *TransactionExecutionLimit) Error() string {
|
||||
cause := "Unknown cause"
|
||||
l := len(e.Causes)
|
||||
if l > 0 {
|
||||
cause = e.Causes[l-1]
|
||||
}
|
||||
var err error
|
||||
l = len(e.Errors)
|
||||
if l > 0 {
|
||||
err = e.Errors[l-1]
|
||||
}
|
||||
return fmt.Sprintf("TransactionExecutionLimit: %s after %d attempts, last error: %s", cause, len(e.Errors), err)
|
||||
}
|
||||
|
||||
// ConnectivityError represent errors caused by the driver not being able to connect to Neo4j services,
|
||||
// or lost connections.
|
||||
type ConnectivityError struct {
|
||||
inner error
|
||||
}
|
||||
|
||||
func (e *ConnectivityError) Error() string {
|
||||
return fmt.Sprintf("ConnectivityError: %s", e.inner.Error())
|
||||
}
|
||||
|
||||
// IsNeo4jError returns true if the provided error is an instance of Neo4jError.
|
||||
func IsNeo4jError(err error) bool {
|
||||
_, is := err.(*Neo4jError)
|
||||
return is
|
||||
}
|
||||
|
||||
// IsUsageError returns true if the provided error is an instance of UsageError.
|
||||
func IsUsageError(err error) bool {
|
||||
_, is := err.(*UsageError)
|
||||
return is
|
||||
}
|
||||
|
||||
// IsConnectivityError returns true if the provided error is an instance of ConnectivityError.
|
||||
func IsConnectivityError(err error) bool {
|
||||
_, is := err.(*ConnectivityError)
|
||||
return is
|
||||
}
|
||||
|
||||
// IsTransactionExecutionLimit returns true if the provided error is an instance of TransactionExecutionLimit.
|
||||
func IsTransactionExecutionLimit(err error) bool {
|
||||
_, is := err.(*TransactionExecutionLimit)
|
||||
return is
|
||||
}
|
||||
|
||||
// TokenExpiredError represent errors caused by the driver not being able to connect to Neo4j services,
|
||||
// or lost connections.
|
||||
type TokenExpiredError struct {
|
||||
Code string
|
||||
Message string
|
||||
}
|
||||
|
||||
func (e *TokenExpiredError) Error() string {
|
||||
return fmt.Sprintf("TokenExpiredError: %s (%s)", e.Code, e.Message)
|
||||
}
|
||||
|
||||
func wrapError(err error) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
if err == io.EOF {
|
||||
return &ConnectivityError{inner: err}
|
||||
}
|
||||
switch e := err.(type) {
|
||||
case *db.UnsupportedTypeError, *db.FeatureNotSupportedError:
|
||||
// Usage of a type not supported by database network protocol or feature
|
||||
// not supported by current version or edition.
|
||||
return &UsageError{Message: err.Error()}
|
||||
case *connector.TlsError, net.Error:
|
||||
return &ConnectivityError{inner: err}
|
||||
case *pool.PoolTimeout, *pool.PoolFull:
|
||||
return &ConnectivityError{inner: err}
|
||||
case *router.ReadRoutingTableError:
|
||||
return &ConnectivityError{inner: err}
|
||||
case *retry.CommitFailedDeadError:
|
||||
return &ConnectivityError{inner: err}
|
||||
case *bolt.ConnectionReadTimeout:
|
||||
return &ConnectivityError{inner: err}
|
||||
case *bolt.ConnectionWriteTimeout:
|
||||
return &ConnectivityError{inner: err}
|
||||
case *db.Neo4jError:
|
||||
if e.Code == "Neo.ClientError.Security.TokenExpired" {
|
||||
return &TokenExpiredError{Code: e.Code, Message: e.Msg}
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
@ -1,794 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) "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 bolt
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
idb "github.com/neo4j/neo4j-go-driver/v5/neo4j/internal/db"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/neo4j/neo4j-go-driver/v5/neo4j/db"
|
||||
"github.com/neo4j/neo4j-go-driver/v5/neo4j/internal/packstream"
|
||||
"github.com/neo4j/neo4j-go-driver/v5/neo4j/log"
|
||||
)
|
||||
|
||||
const (
|
||||
bolt3_ready = iota // Ready for use
|
||||
bolt3_streaming // Receiving result from auto commit query
|
||||
bolt3_pendingtx // Transaction has been requested but not applied
|
||||
bolt3_tx // Transaction pending
|
||||
bolt3_streamingtx // Receiving result from a query within a transaction
|
||||
bolt3_failed // Recoverable error, needs reset
|
||||
bolt3_dead // Non recoverable protocol or connection error
|
||||
bolt3_unauthorized // Initial state, not sent hello message with authentication
|
||||
)
|
||||
|
||||
type internalTx3 struct {
|
||||
mode idb.AccessMode
|
||||
bookmarks []string
|
||||
timeout time.Duration
|
||||
txMeta map[string]interface{}
|
||||
}
|
||||
|
||||
func (i *internalTx3) toMeta() map[string]interface{} {
|
||||
meta := map[string]interface{}{}
|
||||
if i.mode == idb.ReadMode {
|
||||
meta["mode"] = "r"
|
||||
}
|
||||
if len(i.bookmarks) > 0 {
|
||||
meta["bookmarks"] = i.bookmarks
|
||||
}
|
||||
ms := int(i.timeout.Nanoseconds() / 1e6)
|
||||
if ms > 0 {
|
||||
meta["tx_timeout"] = ms
|
||||
}
|
||||
if len(i.txMeta) > 0 {
|
||||
meta["tx_metadata"] = i.txMeta
|
||||
}
|
||||
return meta
|
||||
}
|
||||
|
||||
type bolt3 struct {
|
||||
state int
|
||||
txId idb.TxHandle
|
||||
currStream *stream
|
||||
conn net.Conn
|
||||
serverName string
|
||||
out *outgoing
|
||||
in *incoming
|
||||
connId string
|
||||
logId string
|
||||
serverVersion string
|
||||
tfirst int64 // Time that server started streaming
|
||||
pendingTx *internalTx3 // Stashed away when tx started explcitly
|
||||
bookmark string // Last bookmark
|
||||
birthDate time.Time
|
||||
log log.Logger
|
||||
err error // Last fatal error
|
||||
minor int
|
||||
}
|
||||
|
||||
func NewBolt3(serverName string, conn net.Conn, logger log.Logger, boltLog log.BoltLogger) *bolt3 {
|
||||
b := &bolt3{
|
||||
state: bolt3_unauthorized,
|
||||
conn: conn,
|
||||
serverName: serverName,
|
||||
in: &incoming{
|
||||
buf: make([]byte, 4096),
|
||||
hyd: hydrator{
|
||||
boltLogger: boltLog,
|
||||
},
|
||||
connReadTimeout: -1,
|
||||
logger: logger,
|
||||
logName: log.Bolt3,
|
||||
},
|
||||
birthDate: time.Now(),
|
||||
log: logger,
|
||||
}
|
||||
b.out = &outgoing{
|
||||
chunker: newChunker(),
|
||||
packer: packstream.Packer{},
|
||||
onErr: func(err error) {
|
||||
if b.err == nil {
|
||||
b.err = err
|
||||
}
|
||||
b.state = bolt3_dead
|
||||
},
|
||||
boltLogger: boltLog,
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func (b *bolt3) ServerName() string {
|
||||
return b.serverName
|
||||
}
|
||||
|
||||
func (b *bolt3) ServerVersion() string {
|
||||
return b.serverVersion
|
||||
}
|
||||
|
||||
// Sets b.err and b.state on failure
|
||||
func (b *bolt3) receiveMsg(ctx context.Context) interface{} {
|
||||
msg, err := b.in.next(ctx, b.conn)
|
||||
if err != nil {
|
||||
b.err = err
|
||||
b.log.Error(log.Bolt3, b.logId, b.err)
|
||||
b.state = bolt3_dead
|
||||
return nil
|
||||
}
|
||||
return msg
|
||||
}
|
||||
|
||||
// Receives a message that is assumed to be a success response or a failure in response
|
||||
// to a sent command.
|
||||
// Sets b.err and b.state on failure
|
||||
func (b *bolt3) receiveSuccess(ctx context.Context) *success {
|
||||
switch v := b.receiveMsg(ctx).(type) {
|
||||
case *success:
|
||||
return v
|
||||
case *db.Neo4jError:
|
||||
b.state = bolt3_failed
|
||||
b.err = v
|
||||
if v.Classification() == "ClientError" {
|
||||
// These could include potentially large cypher statement, only log to debug
|
||||
b.log.Debugf(log.Bolt3, b.logId, "%s", v)
|
||||
} else {
|
||||
b.log.Error(log.Bolt3, b.logId, v)
|
||||
}
|
||||
return nil
|
||||
default:
|
||||
// Receive failed, state has been set
|
||||
if b.err != nil {
|
||||
return nil
|
||||
}
|
||||
// Unexpected message received
|
||||
b.state = bolt3_dead
|
||||
b.err = errors.New("Expected success or database error")
|
||||
b.log.Error(log.Bolt3, b.logId, b.err)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (b *bolt3) connect(ctx context.Context, minor int, auth map[string]interface{}, userAgent string) error {
|
||||
if err := b.assertState(bolt3_unauthorized); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
hello := map[string]interface{}{
|
||||
"user_agent": userAgent,
|
||||
}
|
||||
// Merge authentication info into hello message
|
||||
for k, v := range auth {
|
||||
_, exists := hello[k]
|
||||
if exists {
|
||||
continue
|
||||
}
|
||||
hello[k] = v
|
||||
}
|
||||
|
||||
// Send hello message and wait for confirmation
|
||||
b.out.appendHello(hello)
|
||||
if b.out.send(ctx, b.conn); b.err != nil {
|
||||
return b.err
|
||||
}
|
||||
|
||||
succ := b.receiveSuccess(ctx)
|
||||
if b.err != nil {
|
||||
return b.err
|
||||
}
|
||||
|
||||
b.connId = succ.connectionId
|
||||
connectionLogId := fmt.Sprintf("%s@%s", b.connId, b.serverName)
|
||||
b.logId = connectionLogId
|
||||
b.in.logId = connectionLogId
|
||||
b.in.hyd.logId = connectionLogId
|
||||
b.out.logId = connectionLogId
|
||||
b.serverVersion = succ.server
|
||||
|
||||
// Transition into ready state
|
||||
b.state = bolt3_ready
|
||||
b.minor = minor
|
||||
b.log.Infof(log.Bolt3, b.logId, "Connected")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *bolt3) TxBegin(ctx context.Context, txConfig idb.TxConfig) (idb.
|
||||
TxHandle, error) {
|
||||
// Ok, to begin transaction while streaming auto-commit, just empty the stream and continue.
|
||||
if b.state == bolt3_streaming {
|
||||
if err := b.bufferStream(ctx); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
if err := b.assertState(bolt3_ready); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if err := b.checkImpersonation(txConfig.ImpersonatedUser); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
tx := &internalTx3{
|
||||
mode: txConfig.Mode,
|
||||
bookmarks: txConfig.Bookmarks,
|
||||
timeout: txConfig.Timeout,
|
||||
txMeta: txConfig.Meta,
|
||||
}
|
||||
|
||||
// If there are bookmarks, begin the transaction immediately to get the error from the
|
||||
// server early on. Requires a network roundtrip.
|
||||
if len(tx.bookmarks) > 0 {
|
||||
b.out.appendBegin(tx.toMeta())
|
||||
if b.out.send(ctx, b.conn); b.err != nil {
|
||||
return 0, b.err
|
||||
}
|
||||
if b.receiveSuccess(ctx); b.err != nil {
|
||||
return 0, b.err
|
||||
}
|
||||
b.state = bolt3_tx
|
||||
} else {
|
||||
// Stash this into pending internal tx
|
||||
b.pendingTx = tx
|
||||
b.state = bolt3_pendingtx
|
||||
}
|
||||
b.txId = idb.TxHandle(time.Now().Unix())
|
||||
return b.txId, nil
|
||||
}
|
||||
|
||||
// Should NOT set b.err or change b.state as this is used to guard from
|
||||
// misuse from clients that stick to their connections when they shouldn't.
|
||||
func (b *bolt3) assertTxHandle(h1, h2 idb.TxHandle) error {
|
||||
if h1 != h2 {
|
||||
err := errors.New("Invalid transaction handle")
|
||||
b.log.Error(log.Bolt3, b.logId, err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Should NOT set b.err or b.state since the connection is still valid
|
||||
func (b *bolt3) assertState(allowed ...int) error {
|
||||
// Forward prior error instead, this former error is probably the
|
||||
// root cause of any state error. Like a call to Run with malformed
|
||||
// cypher causes an error and another call to Commit would cause the
|
||||
// state to be wrong. Do not log this.
|
||||
if b.err != nil {
|
||||
return b.err
|
||||
}
|
||||
for _, a := range allowed {
|
||||
if b.state == a {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
err := errors.New(fmt.Sprintf("Invalid state %d, expected: %+v", b.state, allowed))
|
||||
b.log.Error(log.Bolt3, b.logId, err)
|
||||
return err
|
||||
}
|
||||
|
||||
func (b *bolt3) TxCommit(ctx context.Context, txh idb.TxHandle) error {
|
||||
if err := b.assertTxHandle(b.txId, txh); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Nothing to do, a transaction started but no commands were issued on it, server is unaware
|
||||
if b.state == bolt3_pendingtx {
|
||||
b.state = bolt3_ready
|
||||
return nil
|
||||
}
|
||||
|
||||
// Consume pending stream if any to turn state from streamingtx to tx
|
||||
// Access to streams outside of tx boundary is not allowed, therefore we should discard
|
||||
// the stream (not buffer).
|
||||
if b.state == bolt3_streamingtx {
|
||||
if err := b.discardStream(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Should be in vanilla tx state now
|
||||
if err := b.assertState(bolt3_tx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Send request to server to commit
|
||||
b.out.appendCommit()
|
||||
if b.out.send(ctx, b.conn); b.err != nil {
|
||||
return b.err
|
||||
}
|
||||
|
||||
// Evaluate server response
|
||||
succ := b.receiveSuccess(ctx)
|
||||
if b.err != nil {
|
||||
return b.err
|
||||
}
|
||||
// Keep track of bookmark
|
||||
if len(succ.bookmark) > 0 {
|
||||
b.bookmark = succ.bookmark
|
||||
}
|
||||
|
||||
// Transition into ready state
|
||||
b.state = bolt3_ready
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *bolt3) TxRollback(ctx context.Context, txh idb.TxHandle) error {
|
||||
if err := b.assertTxHandle(b.txId, txh); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Nothing to do, a transaction started but no commands were issued on it
|
||||
if b.state == bolt3_pendingtx {
|
||||
b.state = bolt3_ready
|
||||
return nil
|
||||
}
|
||||
|
||||
// Can not send rollback while still streaming, consume to turn state into tx
|
||||
// Access to streams outside of tx boundary is not allowed, therefore we should discard
|
||||
// the stream (not buffer).
|
||||
if b.state == bolt3_streamingtx {
|
||||
if err := b.discardStream(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Should be in vanilla tx state now
|
||||
if err := b.assertState(bolt3_tx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Send rollback request to server
|
||||
b.out.appendRollback()
|
||||
if b.out.send(ctx, b.conn); b.err != nil {
|
||||
return b.err
|
||||
}
|
||||
|
||||
// Receive rollback confirmation
|
||||
if b.receiveSuccess(ctx); b.err != nil {
|
||||
return b.err
|
||||
}
|
||||
|
||||
b.state = bolt3_ready
|
||||
return nil
|
||||
}
|
||||
|
||||
// Discards all records in current stream
|
||||
func (b *bolt3) discardStream(ctx context.Context) error {
|
||||
if b.state != bolt3_streaming && b.state != bolt3_streamingtx {
|
||||
// Nothing to do
|
||||
return nil
|
||||
}
|
||||
|
||||
var (
|
||||
sum *db.Summary
|
||||
err error
|
||||
)
|
||||
for sum == nil && err == nil {
|
||||
_, sum, err = b.receiveNext(ctx)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Collects all records in current stream
|
||||
func (b *bolt3) bufferStream(ctx context.Context) error {
|
||||
if b.state != bolt3_streaming && b.state != bolt3_streamingtx {
|
||||
// Nothing to do
|
||||
return nil
|
||||
}
|
||||
|
||||
n := 0
|
||||
var (
|
||||
sum *db.Summary
|
||||
err error
|
||||
rec *db.Record
|
||||
)
|
||||
for sum == nil && err == nil {
|
||||
rec, sum, err = b.receiveNext(ctx)
|
||||
if rec != nil {
|
||||
b.currStream.push(rec)
|
||||
n++
|
||||
}
|
||||
}
|
||||
|
||||
if n > 0 {
|
||||
b.log.Warnf(log.Bolt3, b.logId, "Buffered %d records", n)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (b *bolt3) run(ctx context.Context, cypher string, params map[string]interface{}, tx *internalTx3) (*stream, error) {
|
||||
// If already streaming, finish current stream first
|
||||
if err := b.bufferStream(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := b.assertState(bolt3_tx, bolt3_ready, bolt3_pendingtx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var meta map[string]interface{}
|
||||
if tx != nil {
|
||||
meta = tx.toMeta()
|
||||
}
|
||||
|
||||
// Append lazy begin transaction message
|
||||
if b.state == bolt3_pendingtx {
|
||||
b.out.appendBegin(meta)
|
||||
meta = nil
|
||||
}
|
||||
|
||||
// Append run message
|
||||
b.out.appendRun(cypher, params, meta)
|
||||
|
||||
// Append pull all message and send it along with other pending messages
|
||||
b.out.appendPullAll()
|
||||
if b.out.send(ctx, b.conn); b.err != nil {
|
||||
return nil, b.err
|
||||
}
|
||||
|
||||
// Process server responses
|
||||
// Receive confirmation of transaction begin if it was started above
|
||||
if b.state == bolt3_pendingtx {
|
||||
if b.receiveSuccess(ctx); b.err != nil {
|
||||
return nil, b.err
|
||||
}
|
||||
b.state = bolt3_tx
|
||||
}
|
||||
|
||||
// Receive confirmation of run message
|
||||
succ := b.receiveSuccess(ctx)
|
||||
if b.err != nil {
|
||||
return nil, b.err
|
||||
}
|
||||
b.tfirst = succ.tfirst
|
||||
// Change state to streaming
|
||||
if b.state == bolt3_ready {
|
||||
b.state = bolt3_streaming
|
||||
} else {
|
||||
b.state = bolt3_streamingtx
|
||||
}
|
||||
|
||||
b.currStream = &stream{keys: succ.fields}
|
||||
return b.currStream, nil
|
||||
}
|
||||
|
||||
func (b *bolt3) Run(ctx context.Context, runCommand idb.Command,
|
||||
txConfig idb.TxConfig) (idb.StreamHandle, error) {
|
||||
if err := b.assertState(bolt3_streaming, bolt3_ready); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := b.checkImpersonation(txConfig.ImpersonatedUser); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tx := internalTx3{
|
||||
mode: txConfig.Mode,
|
||||
bookmarks: txConfig.Bookmarks,
|
||||
timeout: txConfig.Timeout,
|
||||
txMeta: txConfig.Meta,
|
||||
}
|
||||
stream, err := b.run(ctx, runCommand.Cypher, runCommand.Params, &tx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return stream, nil
|
||||
}
|
||||
|
||||
func (b *bolt3) RunTx(ctx context.Context, txh idb.TxHandle,
|
||||
runCommand idb.Command) (idb.StreamHandle, error) {
|
||||
if err := b.assertTxHandle(b.txId, txh); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
stream, err := b.run(ctx, runCommand.Cypher, runCommand.Params, b.pendingTx)
|
||||
b.pendingTx = nil
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return stream, nil
|
||||
}
|
||||
|
||||
func (b *bolt3) Keys(streamHandle idb.StreamHandle) ([]string, error) {
|
||||
stream, ok := streamHandle.(*stream)
|
||||
if !ok {
|
||||
return nil, errors.New("Invalid stream handle")
|
||||
}
|
||||
// Don't care about if the stream is the current or even if it belongs to this connection.
|
||||
return stream.keys, nil
|
||||
}
|
||||
|
||||
// Reads one record from the stream.
|
||||
func (b *bolt3) Next(ctx context.Context, streamHandle idb.StreamHandle) (
|
||||
*db.Record, *db.Summary, error) {
|
||||
stream, ok := streamHandle.(*stream)
|
||||
if !ok {
|
||||
return nil, nil, errors.New("Invalid stream handle")
|
||||
}
|
||||
|
||||
// Buffered stream or someone elses stream, doesn't matter...
|
||||
buf, rec, sum, err := stream.bufferedNext()
|
||||
if buf {
|
||||
return rec, sum, err
|
||||
}
|
||||
|
||||
// Nothing in the stream buffer, the stream must be the current
|
||||
// one to fetch on it otherwise something is wrong.
|
||||
if stream != b.currStream {
|
||||
return nil, nil, errors.New("Invalid stream handle")
|
||||
}
|
||||
|
||||
return b.receiveNext(ctx)
|
||||
}
|
||||
|
||||
func (b *bolt3) Consume(ctx context.Context, streamHandle idb.StreamHandle) (
|
||||
*db.Summary, error) {
|
||||
stream, ok := streamHandle.(*stream)
|
||||
if !ok {
|
||||
return nil, errors.New("Invalid stream handle")
|
||||
}
|
||||
|
||||
// If the stream isn't current, it should either already be complete
|
||||
// or have an error.
|
||||
if stream != b.currStream {
|
||||
return stream.sum, stream.err
|
||||
}
|
||||
|
||||
// It is the current stream, it should not be complete but...
|
||||
if stream.err != nil || stream.sum != nil {
|
||||
return stream.sum, stream.err
|
||||
}
|
||||
|
||||
b.discardStream(ctx)
|
||||
return stream.sum, stream.err
|
||||
}
|
||||
|
||||
func (b *bolt3) Buffer(ctx context.Context,
|
||||
streamHandle idb.StreamHandle) error {
|
||||
stream, ok := streamHandle.(*stream)
|
||||
if !ok {
|
||||
return errors.New("Invalid stream handle")
|
||||
}
|
||||
|
||||
// If the stream isn't current, it should either already be complete
|
||||
// or have an error.
|
||||
if stream != b.currStream {
|
||||
return stream.Err()
|
||||
}
|
||||
|
||||
// It is the current stream, it should not be complete but...
|
||||
if stream.err != nil || stream.sum != nil {
|
||||
return stream.Err()
|
||||
}
|
||||
|
||||
b.bufferStream(ctx)
|
||||
return stream.Err()
|
||||
}
|
||||
|
||||
// Reads one record from the network.
|
||||
func (b *bolt3) receiveNext(ctx context.Context) (*db.Record, *db.Summary, error) {
|
||||
if err := b.assertState(bolt3_streaming, bolt3_streamingtx); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
res := b.receiveMsg(ctx)
|
||||
if b.err != nil {
|
||||
return nil, nil, b.err
|
||||
}
|
||||
|
||||
switch x := res.(type) {
|
||||
case *db.Record:
|
||||
x.Keys = b.currStream.keys
|
||||
return x, nil, nil
|
||||
case *success:
|
||||
// End of stream, parse summary
|
||||
sum := x.summary()
|
||||
if sum == nil {
|
||||
b.state = bolt3_dead
|
||||
b.err = errors.New("Failed to parse summary")
|
||||
b.currStream.err = b.err
|
||||
b.currStream = nil
|
||||
b.log.Error(log.Bolt3, b.logId, b.err)
|
||||
return nil, nil, b.err
|
||||
}
|
||||
if b.state == bolt3_streamingtx {
|
||||
b.state = bolt3_tx
|
||||
} else {
|
||||
b.state = bolt3_ready
|
||||
// Keep bookmark for auto-commit tx
|
||||
if len(sum.Bookmark) > 0 {
|
||||
b.bookmark = sum.Bookmark
|
||||
}
|
||||
}
|
||||
b.currStream.sum = sum
|
||||
b.currStream = nil
|
||||
// Add some extras to the summary
|
||||
sum.Agent = b.serverVersion
|
||||
sum.Major = 3
|
||||
sum.Minor = b.minor
|
||||
sum.ServerName = b.serverName
|
||||
sum.TFirst = b.tfirst
|
||||
return nil, sum, nil
|
||||
case *db.Neo4jError:
|
||||
b.err = x
|
||||
b.currStream.err = b.err
|
||||
b.currStream = nil
|
||||
b.state = bolt3_failed
|
||||
if x.Classification() == "ClientError" {
|
||||
// These could include potentially large cypher statement, only log to debug
|
||||
b.log.Debugf(log.Bolt3, b.logId, "%s", x)
|
||||
} else {
|
||||
b.log.Error(log.Bolt3, b.logId, x)
|
||||
}
|
||||
return nil, nil, x
|
||||
default:
|
||||
b.state = bolt3_dead
|
||||
b.err = errors.New("Unknown response")
|
||||
b.currStream.err = b.err
|
||||
b.currStream = nil
|
||||
b.log.Error(log.Bolt3, b.logId, b.err)
|
||||
return nil, nil, b.err
|
||||
}
|
||||
}
|
||||
|
||||
func (b *bolt3) Bookmark() string {
|
||||
return b.bookmark
|
||||
}
|
||||
|
||||
func (b *bolt3) IsAlive() bool {
|
||||
return b.state != bolt3_dead
|
||||
}
|
||||
|
||||
func (b *bolt3) HasFailed() bool {
|
||||
return b.state == bolt3_failed
|
||||
}
|
||||
|
||||
func (b *bolt3) Birthdate() time.Time {
|
||||
return b.birthDate
|
||||
}
|
||||
|
||||
func (b *bolt3) Reset(ctx context.Context) {
|
||||
defer func() {
|
||||
// Reset internal state
|
||||
b.txId = 0
|
||||
b.currStream = nil
|
||||
b.bookmark = ""
|
||||
b.pendingTx = nil
|
||||
b.err = nil
|
||||
}()
|
||||
|
||||
if b.state == bolt3_ready || b.state == bolt3_dead {
|
||||
// No need for reset
|
||||
return
|
||||
}
|
||||
|
||||
// Discard any pending stream
|
||||
b.discardStream(ctx)
|
||||
|
||||
if b.state == bolt3_ready || b.state == bolt3_dead {
|
||||
// No need for reset
|
||||
return
|
||||
}
|
||||
|
||||
// Send the reset message to the server
|
||||
// Need to clear any pending error
|
||||
b.err = nil
|
||||
b.out.appendReset()
|
||||
if b.out.send(ctx, b.conn); b.err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Should receive x number of ignores until we get a success
|
||||
for {
|
||||
msg := b.receiveMsg(ctx)
|
||||
if b.err != nil {
|
||||
return
|
||||
}
|
||||
switch msg.(type) {
|
||||
case *ignored:
|
||||
// Command ignored
|
||||
case *success:
|
||||
// Reset confirmed
|
||||
b.state = bolt3_ready
|
||||
return
|
||||
default:
|
||||
b.state = bolt3_dead
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (b *bolt3) checkImpersonation(impersonatedUser string) error {
|
||||
if impersonatedUser != "" {
|
||||
return &db.FeatureNotSupportedError{Server: b.serverName, Feature: "user impersonation", Reason: "requires least server v4.4"}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *bolt3) GetRoutingTable(ctx context.Context,
|
||||
routingContext map[string]string, _ []string, database, impersonatedUser string) (*idb.RoutingTable, error) {
|
||||
if err := b.assertState(bolt3_ready); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if database != idb.DefaultDatabase {
|
||||
return nil, &db.FeatureNotSupportedError{Server: b.serverName, Feature: "route to database", Reason: "requires at least server v4"}
|
||||
}
|
||||
if err := b.checkImpersonation(impersonatedUser); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Only available when Neo4j is setup with clustering
|
||||
runCommand := idb.Command{
|
||||
Cypher: "CALL dbms.cluster.routing.getRoutingTable($context)",
|
||||
Params: map[string]interface{}{"context": routingContext},
|
||||
}
|
||||
txConfig := idb.TxConfig{Mode: idb.ReadMode}
|
||||
streamHandle, err := b.Run(ctx, runCommand, txConfig)
|
||||
if err != nil {
|
||||
// Give a better error
|
||||
dbError, isDbError := err.(*db.Neo4jError)
|
||||
if isDbError && dbError.Code == "Neo.ClientError.Procedure.ProcedureNotFound" {
|
||||
return nil, &db.FeatureNotSupportedError{Server: b.serverName, Feature: "routing", Reason: "requires cluster setup"}
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rec, _, err := b.Next(ctx, streamHandle)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if rec == nil {
|
||||
return nil, errors.New("No routing table record")
|
||||
}
|
||||
// Just empty the stream, ignore the summary should leave the connecion in ready state
|
||||
b.Next(ctx, streamHandle)
|
||||
|
||||
table := parseRoutingTableRecord(rec)
|
||||
if table == nil {
|
||||
return nil, errors.New("Unable to parse routing table")
|
||||
}
|
||||
// Just because
|
||||
table.DatabaseName = idb.DefaultDatabase
|
||||
|
||||
return table, nil
|
||||
}
|
||||
|
||||
// Close closes the underlying connection.
|
||||
// Beware: could be called on another thread when driver is closed.
|
||||
func (b *bolt3) Close(ctx context.Context) {
|
||||
b.log.Infof(log.Bolt3, b.logId, "Close")
|
||||
if b.state != bolt3_dead {
|
||||
b.out.appendGoodbye()
|
||||
b.out.send(ctx, b.conn)
|
||||
}
|
||||
b.conn.Close()
|
||||
b.state = bolt3_dead
|
||||
}
|
||||
|
||||
func (b *bolt3) ForceReset(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *bolt3) SetBoltLogger(boltLogger log.BoltLogger) {
|
||||
b.in.hyd.boltLogger = boltLogger
|
||||
b.out.boltLogger = boltLogger
|
||||
}
|
File diff suppressed because it is too large
Load Diff
99
vendor/github.com/neo4j/neo4j-go-driver/v5/neo4j/internal/bolt/bolt_logging.go
generated
vendored
99
vendor/github.com/neo4j/neo4j-go-driver/v5/neo4j/internal/bolt/bolt_logging.go
generated
vendored
@ -1,99 +0,0 @@
|
||||
package bolt
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/neo4j/neo4j-go-driver/v5/neo4j/db"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type loggableDictionary map[string]interface{}
|
||||
|
||||
func (d loggableDictionary) String() string {
|
||||
if credentials, ok := d["credentials"]; ok {
|
||||
d["credentials"] = "<redacted>"
|
||||
defer func() {
|
||||
d["credentials"] = credentials
|
||||
}()
|
||||
}
|
||||
return serializeTrace(d)
|
||||
}
|
||||
|
||||
type loggableStringDictionary map[string]string
|
||||
|
||||
func (sd loggableStringDictionary) String() string {
|
||||
if credentials, ok := sd["credentials"]; ok {
|
||||
sd["credentials"] = "<redacted>"
|
||||
defer func() {
|
||||
sd["credentials"] = credentials
|
||||
}()
|
||||
}
|
||||
return serializeTrace(sd)
|
||||
}
|
||||
|
||||
type loggableList []interface{}
|
||||
|
||||
func (l loggableList) String() string {
|
||||
return serializeTrace(l)
|
||||
}
|
||||
|
||||
type loggableStringList []string
|
||||
|
||||
func (s loggableStringList) String() string {
|
||||
return serializeTrace(s)
|
||||
}
|
||||
|
||||
type loggableSuccess success
|
||||
type loggedSuccess struct {
|
||||
Server string `json:"server,omitempty"`
|
||||
ConnectionId string `json:"connection_id,omitempty"`
|
||||
Fields []string `json:"fields,omitempty"`
|
||||
TFirst string `json:"t_first,omitempty"`
|
||||
Bookmark string `json:"bookmark,omitempty"`
|
||||
TLast string `json:"t_last,omitempty"`
|
||||
HasMore bool `json:"has_more,omitempy"`
|
||||
Db string `json:"db,omitempty"`
|
||||
Qid int64 `json:"qid,omitempty"`
|
||||
}
|
||||
|
||||
func (s loggableSuccess) String() string {
|
||||
success := loggedSuccess{
|
||||
Server: s.server,
|
||||
ConnectionId: s.connectionId,
|
||||
Fields: s.fields,
|
||||
TFirst: formatOmittingZero(s.tfirst),
|
||||
Bookmark: s.bookmark,
|
||||
TLast: formatOmittingZero(s.tlast),
|
||||
HasMore: s.hasMore,
|
||||
Db: s.db,
|
||||
}
|
||||
if s.qid > -1 {
|
||||
success.Qid = s.qid
|
||||
}
|
||||
return serializeTrace(success)
|
||||
|
||||
}
|
||||
|
||||
func formatOmittingZero(i int64) string {
|
||||
if i == 0 {
|
||||
return ""
|
||||
}
|
||||
return strconv.FormatInt(i, 10)
|
||||
}
|
||||
|
||||
type loggableFailure db.Neo4jError
|
||||
|
||||
func (f loggableFailure) String() string {
|
||||
return serializeTrace(map[string]interface{}{
|
||||
"code": f.Code,
|
||||
"message": f.Msg,
|
||||
})
|
||||
}
|
||||
|
||||
func serializeTrace(v interface{}) string {
|
||||
builder := strings.Builder{}
|
||||
encoder := json.NewEncoder(&builder)
|
||||
encoder.SetEscapeHTML(false)
|
||||
_ = encoder.Encode(v)
|
||||
return strings.TrimSpace(builder.String())
|
||||
}
|
@ -1,126 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) "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 bolt
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/binary"
|
||||
rio "github.com/neo4j/neo4j-go-driver/v5/neo4j/internal/racingio"
|
||||
"io"
|
||||
)
|
||||
|
||||
type chunker struct {
|
||||
buf []byte
|
||||
sizes []int
|
||||
offset int
|
||||
}
|
||||
|
||||
func newChunker() chunker {
|
||||
return chunker{
|
||||
buf: make([]byte, 0, 1024),
|
||||
sizes: make([]int, 0, 3),
|
||||
offset: 0,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *chunker) beginMessage() {
|
||||
// Space for length of next message
|
||||
c.buf = append(c.buf, 0, 0)
|
||||
c.offset += 2
|
||||
}
|
||||
|
||||
func (c *chunker) endMessage() {
|
||||
// Calculate size and stash it
|
||||
size := len(c.buf) - c.offset
|
||||
c.offset += size
|
||||
c.sizes = append(c.sizes, size)
|
||||
|
||||
// Add zero chunk to mark end of message
|
||||
c.buf = append(c.buf, 0, 0)
|
||||
c.offset += 2
|
||||
}
|
||||
|
||||
func (c *chunker) send(ctx context.Context, wr io.Writer) error {
|
||||
// Try to make as few writes as possible to reduce network overhead
|
||||
// Whenever we encounter a message that is bigger than max chunk size we need
|
||||
// to write and make a new chunk
|
||||
start := 0
|
||||
end := 0
|
||||
|
||||
writer := rio.NewRacingWriter(wr)
|
||||
|
||||
for _, size := range c.sizes {
|
||||
if size <= 0xffff {
|
||||
binary.BigEndian.PutUint16(c.buf[end:], uint16(size))
|
||||
// Size + message + end of message marker
|
||||
end += 2 + size + 2
|
||||
} else {
|
||||
// Could be a message that ranges over multiple chunks
|
||||
for size > 0xffff {
|
||||
c.buf[end] = 0xff
|
||||
c.buf[end+1] = 0xff
|
||||
// Size + message
|
||||
end += 2 + 0xffff
|
||||
|
||||
_, err := writer.Write(ctx, c.buf[start:end])
|
||||
if err != nil {
|
||||
return processWriteError(err, ctx)
|
||||
}
|
||||
// Reuse part of buffer that has already been written to specify size
|
||||
// of the chunk
|
||||
end -= 2
|
||||
start = end
|
||||
size -= 0xffff
|
||||
}
|
||||
binary.BigEndian.PutUint16(c.buf[end:], uint16(size))
|
||||
// Size + message + end of message marker
|
||||
end += 2 + size + 2
|
||||
}
|
||||
}
|
||||
|
||||
if end > start {
|
||||
_, err := writer.Write(ctx, c.buf[start:end])
|
||||
if err != nil {
|
||||
return processWriteError(err, ctx)
|
||||
}
|
||||
}
|
||||
|
||||
// Prepare for reuse
|
||||
c.offset = 0
|
||||
c.buf = c.buf[:0]
|
||||
c.sizes = c.sizes[:0]
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func processWriteError(err error, ctx context.Context) error {
|
||||
if IsTimeoutError(err) {
|
||||
return &ConnectionWriteTimeout{
|
||||
userContext: ctx,
|
||||
err: err,
|
||||
}
|
||||
}
|
||||
if err == context.Canceled {
|
||||
return &ConnectionWriteCanceled{
|
||||
err: err,
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
@ -1,106 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) "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 bolt contains implementations of the database functionality.
|
||||
package bolt
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/neo4j/neo4j-go-driver/v5/neo4j/internal/db"
|
||||
"github.com/neo4j/neo4j-go-driver/v5/neo4j/internal/racingio"
|
||||
"net"
|
||||
|
||||
"github.com/neo4j/neo4j-go-driver/v5/neo4j/log"
|
||||
)
|
||||
|
||||
type protocolVersion struct {
|
||||
major byte
|
||||
minor byte
|
||||
back byte // Number of minor versions back
|
||||
}
|
||||
|
||||
// Supported versions in priority order
|
||||
var versions = [4]protocolVersion{
|
||||
{major: 4, minor: 4, back: 2},
|
||||
{major: 4, minor: 1},
|
||||
{major: 4, minor: 0},
|
||||
{major: 3, minor: 0},
|
||||
}
|
||||
|
||||
// Connect initiates the negotiation of the Bolt protocol version.
|
||||
// Returns the instance of bolt protocol implementing the low-level Connection interface.
|
||||
func Connect(ctx context.Context, serverName string, conn net.Conn, auth map[string]interface{}, userAgent string, routingContext map[string]string, logger log.Logger, boltLog log.BoltLogger) (db.Connection, error) {
|
||||
// Perform Bolt handshake to negotiate version
|
||||
// Send handshake to server
|
||||
handshake := []byte{
|
||||
0x60, 0x60, 0xb0, 0x17, // Magic: GoGoBolt
|
||||
0x00, versions[0].back, versions[0].minor, versions[0].major,
|
||||
0x00, versions[1].back, versions[1].minor, versions[1].major,
|
||||
0x00, versions[2].back, versions[2].minor, versions[2].major,
|
||||
0x00, versions[3].back, versions[3].minor, versions[3].major,
|
||||
}
|
||||
if boltLog != nil {
|
||||
boltLog.LogClientMessage("", "<MAGIC> %#010X", handshake[0:4])
|
||||
boltLog.LogClientMessage("", "<HANDSHAKE> %#010X %#010X %#010X %#010X", handshake[4:8], handshake[8:12], handshake[12:16], handshake[16:20])
|
||||
}
|
||||
_, err := racingio.NewRacingWriter(conn).Write(ctx, handshake)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Receive accepted server version
|
||||
buf := make([]byte, 4)
|
||||
_, err = racingio.NewRacingReader(conn).ReadFull(ctx, buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if boltLog != nil {
|
||||
boltLog.LogServerMessage("", "<HANDSHAKE> %#010X", buf)
|
||||
}
|
||||
// Parse received version and construct the correct instance
|
||||
major := buf[3]
|
||||
minor := buf[2]
|
||||
switch major {
|
||||
case 3:
|
||||
// Handover rest of connection handshaking
|
||||
boltConn := NewBolt3(serverName, conn, logger, boltLog)
|
||||
err = boltConn.connect(ctx, int(minor), auth, userAgent)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return boltConn, nil
|
||||
case 4:
|
||||
// Handover rest of connection handshaking
|
||||
boltConn := NewBolt4(serverName, conn, logger, boltLog)
|
||||
err = boltConn.connect(ctx, int(minor), auth, userAgent, routingContext)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return boltConn, nil
|
||||
case 0:
|
||||
err = errors.New(fmt.Sprintf("Server did not accept any of the requested Bolt versions (%#v)", versions))
|
||||
default:
|
||||
err = errors.New(fmt.Sprintf("Server responded with unsupported version %d.%d", major, minor))
|
||||
}
|
||||
|
||||
return nil, err
|
||||
}
|
@ -1,130 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) "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 bolt
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/binary"
|
||||
rio "github.com/neo4j/neo4j-go-driver/v5/neo4j/internal/racingio"
|
||||
"github.com/neo4j/neo4j-go-driver/v5/neo4j/log"
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
// dechunkMessage takes a buffer to be reused and returns the reusable buffer
|
||||
// (might have been reallocated to handle growth), the message buffer and
|
||||
// error.
|
||||
// Reads will race against the provided context ctx
|
||||
// If the server provides the connection read timeout hint readTimeout, a new context will be created from that timeout
|
||||
// and the user-provided context ctx before every read
|
||||
func dechunkMessage(
|
||||
ctx context.Context,
|
||||
conn net.Conn,
|
||||
msgBuf []byte,
|
||||
readTimeout time.Duration,
|
||||
logger log.Logger,
|
||||
logName string,
|
||||
logId string) ([]byte, []byte, error) {
|
||||
|
||||
sizeBuf := []byte{0x00, 0x00}
|
||||
off := 0
|
||||
|
||||
reader := rio.NewRacingReader(conn)
|
||||
|
||||
for {
|
||||
updatedCtx, cancelFunc := newContext(ctx, readTimeout, logger, logName, logId)
|
||||
_, err := reader.ReadFull(updatedCtx, sizeBuf)
|
||||
if err != nil {
|
||||
return msgBuf, nil, processReadError(err, ctx, readTimeout)
|
||||
}
|
||||
if cancelFunc != nil { // reading has been completed, time to release the context
|
||||
cancelFunc()
|
||||
}
|
||||
chunkSize := int(binary.BigEndian.Uint16(sizeBuf))
|
||||
if chunkSize == 0 {
|
||||
if off > 0 {
|
||||
return msgBuf, msgBuf[:off], nil
|
||||
}
|
||||
// Got a nop chunk
|
||||
continue
|
||||
}
|
||||
|
||||
// Need to expand buffer
|
||||
if (off + chunkSize) > cap(msgBuf) {
|
||||
newMsgBuf := make([]byte, (off+chunkSize)+4096)
|
||||
copy(newMsgBuf, msgBuf)
|
||||
msgBuf = newMsgBuf
|
||||
}
|
||||
// Read the chunk into buffer
|
||||
updatedCtx, cancelFunc = newContext(ctx, readTimeout, logger, logName, logId)
|
||||
_, err = reader.ReadFull(updatedCtx, msgBuf[off:(off+chunkSize)])
|
||||
if err != nil {
|
||||
return msgBuf, nil, processReadError(err, ctx, readTimeout)
|
||||
}
|
||||
if cancelFunc != nil { // reading has been completed, time to release the context
|
||||
cancelFunc()
|
||||
}
|
||||
off += chunkSize
|
||||
}
|
||||
}
|
||||
|
||||
// newContext computes a new context and cancel function if a readTimeout is set
|
||||
func newContext(
|
||||
ctx context.Context,
|
||||
readTimeout time.Duration,
|
||||
logger log.Logger,
|
||||
logName string,
|
||||
logId string) (context.Context, context.CancelFunc) {
|
||||
|
||||
if readTimeout >= 0 {
|
||||
newCtx, cancelFunc := context.WithTimeout(ctx, readTimeout)
|
||||
logger.Debugf(logName, logId,
|
||||
"read timeout of %s applied, chunk read deadline is now: %s",
|
||||
readTimeout.String(),
|
||||
deadlineOf(newCtx),
|
||||
)
|
||||
return newCtx, cancelFunc
|
||||
}
|
||||
return ctx, nil
|
||||
}
|
||||
|
||||
func processReadError(err error, ctx context.Context, readTimeout time.Duration) error {
|
||||
if IsTimeoutError(err) {
|
||||
return &ConnectionReadTimeout{
|
||||
userContext: ctx,
|
||||
readTimeout: readTimeout,
|
||||
err: err,
|
||||
}
|
||||
}
|
||||
if err == context.Canceled {
|
||||
return &ConnectionReadCanceled{
|
||||
err: err,
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func deadlineOf(ctx context.Context) string {
|
||||
deadline, hasDeadline := ctx.Deadline()
|
||||
if !hasDeadline {
|
||||
return "N/A (no deadline set)"
|
||||
}
|
||||
return deadline.String()
|
||||
}
|
@ -1,66 +0,0 @@
|
||||
package bolt
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
type ConnectionReadTimeout struct {
|
||||
userContext context.Context
|
||||
readTimeout time.Duration
|
||||
err error
|
||||
}
|
||||
|
||||
func (crt *ConnectionReadTimeout) Error() string {
|
||||
userDeadline := "N/A"
|
||||
if deadline, ok := crt.userContext.Deadline(); ok {
|
||||
userDeadline = deadline.String()
|
||||
}
|
||||
return fmt.Sprintf(
|
||||
"Timeout while reading from connection [server-side timeout hint: %s, user-provided context deadline: %s]: %s",
|
||||
crt.readTimeout.String(),
|
||||
userDeadline,
|
||||
crt.err)
|
||||
}
|
||||
|
||||
type ConnectionWriteTimeout struct {
|
||||
userContext context.Context
|
||||
err error
|
||||
}
|
||||
|
||||
func (cwt *ConnectionWriteTimeout) Error() string {
|
||||
userDeadline := "N/A"
|
||||
if deadline, ok := cwt.userContext.Deadline(); ok {
|
||||
userDeadline = deadline.String()
|
||||
}
|
||||
return fmt.Sprintf("Timeout while writing to connection [user-provided context deadline: %s]: %s", userDeadline, cwt.err)
|
||||
}
|
||||
|
||||
type ConnectionReadCanceled struct {
|
||||
err error
|
||||
}
|
||||
|
||||
func (crc *ConnectionReadCanceled) Error() string {
|
||||
return fmt.Sprintf("Reading from connection has been canceled: %s", crc.err)
|
||||
}
|
||||
|
||||
type ConnectionWriteCanceled struct {
|
||||
err error
|
||||
}
|
||||
|
||||
func (cwc *ConnectionWriteCanceled) Error() string {
|
||||
return fmt.Sprintf("Writing to connection has been canceled: %s", cwc.err)
|
||||
}
|
||||
|
||||
type timeout interface {
|
||||
Timeout() bool
|
||||
}
|
||||
|
||||
func IsTimeoutError(err error) bool {
|
||||
if err == context.DeadlineExceeded {
|
||||
return true
|
||||
}
|
||||
timeoutErr, ok := err.(timeout)
|
||||
return ok && timeoutErr.Timeout()
|
||||
}
|
@ -1,821 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) "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 bolt
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
idb "github.com/neo4j/neo4j-go-driver/v5/neo4j/internal/db"
|
||||
"github.com/neo4j/neo4j-go-driver/v5/neo4j/log"
|
||||
"time"
|
||||
|
||||
"github.com/neo4j/neo4j-go-driver/v5/neo4j/db"
|
||||
"github.com/neo4j/neo4j-go-driver/v5/neo4j/dbtype"
|
||||
"github.com/neo4j/neo4j-go-driver/v5/neo4j/internal/packstream"
|
||||
)
|
||||
|
||||
const containsSystemUpdatesKey = "contains-system-updates"
|
||||
const containsUpdatesKey = "contains-updates"
|
||||
|
||||
type ignored struct{}
|
||||
type success struct {
|
||||
fields []string
|
||||
tfirst int64
|
||||
qid int64
|
||||
bookmark string
|
||||
connectionId string
|
||||
server string
|
||||
db string
|
||||
hasMore bool
|
||||
tlast int64
|
||||
qtype db.StatementType
|
||||
counters map[string]interface{}
|
||||
plan *db.Plan
|
||||
profile *db.ProfiledPlan
|
||||
notifications []db.Notification
|
||||
routingTable *idb.RoutingTable
|
||||
num uint32
|
||||
configurationHints map[string]interface{}
|
||||
}
|
||||
|
||||
func (s *success) String() string {
|
||||
str := fmt.Sprintf("%#v", s)
|
||||
if s.plan != nil {
|
||||
str += fmt.Sprintf(" \nplan: %#v", s.plan)
|
||||
}
|
||||
if s.profile != nil {
|
||||
str += fmt.Sprintf(" \nprofile: %#v", s.profile)
|
||||
}
|
||||
if s.routingTable != nil {
|
||||
str += fmt.Sprintf(" \nrouting table: %#v", s.routingTable)
|
||||
}
|
||||
return str
|
||||
}
|
||||
|
||||
func (s *success) summary() *db.Summary {
|
||||
return &db.Summary{
|
||||
Bookmark: s.bookmark,
|
||||
StmntType: s.qtype,
|
||||
Counters: extractIntCounters(s.counters),
|
||||
TLast: s.tlast,
|
||||
Plan: s.plan,
|
||||
ProfiledPlan: s.profile,
|
||||
Notifications: s.notifications,
|
||||
Database: s.db,
|
||||
ContainsSystemUpdates: extractBoolPointer(s.counters, containsSystemUpdatesKey),
|
||||
ContainsUpdates: extractBoolPointer(s.counters, containsUpdatesKey),
|
||||
}
|
||||
}
|
||||
|
||||
func extractIntCounters(counters map[string]interface{}) map[string]int {
|
||||
result := make(map[string]int, len(counters))
|
||||
for k, v := range counters {
|
||||
if k != containsSystemUpdatesKey && k != containsUpdatesKey {
|
||||
result[k] = v.(int)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func extractBoolPointer(counters map[string]interface{}, key string) *bool {
|
||||
result, ok := counters[key]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return result.(*bool)
|
||||
}
|
||||
|
||||
func (s *success) isResetResponse() bool {
|
||||
return s.num == 0
|
||||
}
|
||||
|
||||
type hydrator struct {
|
||||
unpacker packstream.Unpacker
|
||||
unp *packstream.Unpacker
|
||||
err error
|
||||
cachedIgnored ignored
|
||||
cachedSuccess success
|
||||
boltLogger log.BoltLogger
|
||||
logId string
|
||||
}
|
||||
|
||||
func (h *hydrator) setErr(err error) {
|
||||
if h.err == nil {
|
||||
h.err = err
|
||||
}
|
||||
}
|
||||
|
||||
func (h *hydrator) getErr() error {
|
||||
if h.unp.Err != nil {
|
||||
return h.unp.Err
|
||||
}
|
||||
return h.err
|
||||
}
|
||||
|
||||
func (h *hydrator) assertLength(structType string, expected, actual uint32) {
|
||||
if expected != actual {
|
||||
h.setErr(&db.ProtocolError{
|
||||
MessageType: structType,
|
||||
Err: fmt.Sprintf("Invalid length of struct, expected %d but was %d",
|
||||
expected, actual),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// hydrate hydrates a top-level struct message
|
||||
func (h *hydrator) hydrate(buf []byte) (x interface{}, err error) {
|
||||
h.unp = &h.unpacker
|
||||
h.unp.Reset(buf)
|
||||
h.unp.Next()
|
||||
|
||||
if h.unp.Curr != packstream.PackedStruct {
|
||||
return nil, errors.New(fmt.Sprintf("Expected struct"))
|
||||
}
|
||||
|
||||
n := h.unp.Len()
|
||||
t := h.unp.StructTag()
|
||||
switch t {
|
||||
case msgSuccess:
|
||||
x = h.success(n)
|
||||
case msgIgnored:
|
||||
x = h.ignored(n)
|
||||
case msgFailure:
|
||||
x = h.failure(n)
|
||||
case msgRecord:
|
||||
x = h.record(n)
|
||||
default:
|
||||
return nil, errors.New(fmt.Sprintf("Unexpected tag at top level: %d", t))
|
||||
}
|
||||
err = h.getErr()
|
||||
return
|
||||
}
|
||||
|
||||
func (h *hydrator) ignored(n uint32) *ignored {
|
||||
h.assertLength("ignored", 0, n)
|
||||
if h.getErr() != nil {
|
||||
return nil
|
||||
}
|
||||
if h.boltLogger != nil {
|
||||
h.boltLogger.LogServerMessage(h.logId, "IGNORED")
|
||||
}
|
||||
return &h.cachedIgnored
|
||||
}
|
||||
|
||||
func (h *hydrator) failure(n uint32) *db.Neo4jError {
|
||||
h.assertLength("failure", 1, n)
|
||||
if h.getErr() != nil {
|
||||
return nil
|
||||
}
|
||||
dberr := db.Neo4jError{}
|
||||
h.unp.Next() // Detect map
|
||||
for maplen := h.unp.Len(); maplen > 0; maplen-- {
|
||||
h.unp.Next()
|
||||
key := h.unp.String()
|
||||
h.unp.Next()
|
||||
switch key {
|
||||
case "code":
|
||||
dberr.Code = h.unp.String()
|
||||
case "message":
|
||||
dberr.Msg = h.unp.String()
|
||||
default:
|
||||
// Do not fail on unknown value in map
|
||||
h.trash()
|
||||
}
|
||||
}
|
||||
if h.boltLogger != nil {
|
||||
h.boltLogger.LogServerMessage(h.logId, "FAILURE %s", loggableFailure(dberr))
|
||||
}
|
||||
return &dberr
|
||||
}
|
||||
|
||||
func (h *hydrator) success(n uint32) *success {
|
||||
h.assertLength("success", 1, n)
|
||||
if h.getErr() != nil {
|
||||
return nil
|
||||
}
|
||||
// Use cached success but clear it first
|
||||
h.cachedSuccess = success{}
|
||||
h.cachedSuccess.qid = -1
|
||||
succ := &h.cachedSuccess
|
||||
|
||||
h.unp.Next() // Detect map
|
||||
n = h.unp.Len()
|
||||
succ.num = n
|
||||
for ; n > 0; n-- {
|
||||
// Key
|
||||
h.unp.Next()
|
||||
key := h.unp.String()
|
||||
// Value
|
||||
h.unp.Next()
|
||||
switch key {
|
||||
case "fields":
|
||||
succ.fields = h.strings()
|
||||
case "t_first":
|
||||
succ.tfirst = h.unp.Int()
|
||||
case "qid":
|
||||
succ.qid = h.unp.Int()
|
||||
case "bookmark":
|
||||
succ.bookmark = h.unp.String()
|
||||
case "connection_id":
|
||||
succ.connectionId = h.unp.String()
|
||||
case "server":
|
||||
succ.server = h.unp.String()
|
||||
case "has_more":
|
||||
succ.hasMore = h.unp.Bool()
|
||||
case "t_last":
|
||||
succ.tlast = h.unp.Int()
|
||||
case "type":
|
||||
statementType := h.unp.String()
|
||||
switch statementType {
|
||||
case "r":
|
||||
succ.qtype = db.StatementTypeRead
|
||||
case "w":
|
||||
succ.qtype = db.StatementTypeWrite
|
||||
case "rw":
|
||||
succ.qtype = db.StatementTypeReadWrite
|
||||
case "s":
|
||||
succ.qtype = db.StatementTypeSchemaWrite
|
||||
default:
|
||||
h.setErr(&db.ProtocolError{
|
||||
MessageType: "success",
|
||||
Field: "type",
|
||||
Err: fmt.Sprintf("unrecognized success statement type %s", statementType),
|
||||
})
|
||||
}
|
||||
case "db":
|
||||
succ.db = h.unp.String()
|
||||
case "stats":
|
||||
succ.counters = h.successStats()
|
||||
case "plan":
|
||||
m := h.amap()
|
||||
succ.plan = parsePlan(m)
|
||||
case "profile":
|
||||
m := h.amap()
|
||||
succ.profile = parseProfile(m)
|
||||
case "notifications":
|
||||
l := h.array()
|
||||
succ.notifications = parseNotifications(l)
|
||||
case "rt":
|
||||
succ.routingTable = h.routingTable()
|
||||
case "hints":
|
||||
hints := h.amap()
|
||||
succ.configurationHints = hints
|
||||
default:
|
||||
// Unknown key, waste it
|
||||
h.trash()
|
||||
}
|
||||
}
|
||||
if h.boltLogger != nil {
|
||||
h.boltLogger.LogServerMessage(h.logId, "SUCCESS %s", loggableSuccess(*succ))
|
||||
}
|
||||
return succ
|
||||
}
|
||||
|
||||
func (h *hydrator) successStats() map[string]interface{} {
|
||||
n := h.unp.Len()
|
||||
if n == 0 {
|
||||
return nil
|
||||
}
|
||||
counts := make(map[string]interface{}, n)
|
||||
for ; n > 0; n-- {
|
||||
h.unp.Next()
|
||||
key := h.unp.String()
|
||||
h.unp.Next()
|
||||
val := h.parseStatValue(key)
|
||||
counts[key] = val
|
||||
}
|
||||
return counts
|
||||
}
|
||||
|
||||
func (h *hydrator) parseStatValue(key string) interface{} {
|
||||
var val interface{}
|
||||
switch key {
|
||||
case containsSystemUpdatesKey, containsUpdatesKey:
|
||||
boolValue := h.unp.Bool()
|
||||
val = &boolValue
|
||||
default:
|
||||
val = int(h.unp.Int())
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
// routingTable parses a routing table sent from the server. This is done
|
||||
// the 'hard' way to reduce number of allocations (would be easier to go via
|
||||
// a map) since it is called in normal flow (not that frequent...).
|
||||
func (h *hydrator) routingTable() *idb.RoutingTable {
|
||||
rt := idb.RoutingTable{}
|
||||
// Length of map
|
||||
nkeys := h.unp.Len()
|
||||
for ; nkeys > 0; nkeys-- {
|
||||
h.unp.Next()
|
||||
key := h.unp.String()
|
||||
h.unp.Next()
|
||||
switch key {
|
||||
case "ttl":
|
||||
rt.TimeToLive = int(h.unp.Int())
|
||||
case "servers":
|
||||
nservers := h.unp.Len()
|
||||
for ; nservers > 0; nservers-- {
|
||||
h.routingTableRole(&rt)
|
||||
}
|
||||
case "db":
|
||||
rt.DatabaseName = h.unp.String()
|
||||
default:
|
||||
// Unknown key, waste the value
|
||||
h.trash()
|
||||
}
|
||||
}
|
||||
return &rt
|
||||
}
|
||||
|
||||
func (h *hydrator) routingTableRole(rt *idb.RoutingTable) {
|
||||
h.unp.Next()
|
||||
nkeys := h.unp.Len()
|
||||
var role string
|
||||
var addresses []string
|
||||
for ; nkeys > 0; nkeys-- {
|
||||
h.unp.Next()
|
||||
key := h.unp.String()
|
||||
h.unp.Next()
|
||||
switch key {
|
||||
case "role":
|
||||
role = h.unp.String()
|
||||
case "addresses":
|
||||
addresses = h.strings()
|
||||
default:
|
||||
// Unknown key, waste the value
|
||||
h.trash()
|
||||
}
|
||||
}
|
||||
switch role {
|
||||
case "READ":
|
||||
rt.Readers = addresses
|
||||
case "WRITE":
|
||||
rt.Writers = addresses
|
||||
case "ROUTE":
|
||||
rt.Routers = addresses
|
||||
}
|
||||
}
|
||||
|
||||
func (h *hydrator) strings() []string {
|
||||
n := h.unp.Len()
|
||||
slice := make([]string, n)
|
||||
for i := range slice {
|
||||
h.unp.Next()
|
||||
slice[i] = h.unp.String()
|
||||
}
|
||||
return slice
|
||||
}
|
||||
|
||||
func (h *hydrator) amap() map[string]interface{} {
|
||||
n := h.unp.Len()
|
||||
m := make(map[string]interface{}, n)
|
||||
for ; n > 0; n-- {
|
||||
h.unp.Next()
|
||||
key := h.unp.String()
|
||||
h.unp.Next()
|
||||
m[key] = h.value()
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func (h *hydrator) array() []interface{} {
|
||||
n := h.unp.Len()
|
||||
a := make([]interface{}, n)
|
||||
for i := range a {
|
||||
h.unp.Next()
|
||||
a[i] = h.value()
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
func (h *hydrator) record(n uint32) *db.Record {
|
||||
h.assertLength("record", 1, n)
|
||||
if h.getErr() != nil {
|
||||
return nil
|
||||
}
|
||||
rec := db.Record{}
|
||||
h.unp.Next() // Detect array
|
||||
n = h.unp.Len()
|
||||
rec.Values = make([]interface{}, n)
|
||||
for i := range rec.Values {
|
||||
h.unp.Next()
|
||||
rec.Values[i] = h.value()
|
||||
}
|
||||
if h.boltLogger != nil {
|
||||
h.boltLogger.LogServerMessage(h.logId, "RECORD %s", loggableList(rec.Values))
|
||||
}
|
||||
return &rec
|
||||
}
|
||||
|
||||
func (h *hydrator) value() interface{} {
|
||||
valueType := h.unp.Curr
|
||||
switch valueType {
|
||||
case packstream.PackedInt:
|
||||
return h.unp.Int()
|
||||
case packstream.PackedFloat:
|
||||
return h.unp.Float()
|
||||
case packstream.PackedStr:
|
||||
return h.unp.String()
|
||||
case packstream.PackedStruct:
|
||||
t := h.unp.StructTag()
|
||||
n := h.unp.Len()
|
||||
switch t {
|
||||
case 'N':
|
||||
return h.node(n)
|
||||
case 'R':
|
||||
return h.relationship(n)
|
||||
case 'r':
|
||||
return h.relationnode(n)
|
||||
case 'P':
|
||||
return h.path(n)
|
||||
case 'X':
|
||||
return h.point2d(n)
|
||||
case 'Y':
|
||||
return h.point3d(n)
|
||||
case 'F':
|
||||
return h.dateTimeOffset(n)
|
||||
case 'f':
|
||||
return h.dateTimeNamedZone(n)
|
||||
case 'd':
|
||||
return h.localDateTime(n)
|
||||
case 'D':
|
||||
return h.date(n)
|
||||
case 'T':
|
||||
return h.time(n)
|
||||
case 't':
|
||||
return h.localTime(n)
|
||||
case 'E':
|
||||
return h.duration(n)
|
||||
default:
|
||||
h.setErr(&db.ProtocolError{
|
||||
Err: fmt.Sprintf("Received unknown struct tag: %d", t),
|
||||
})
|
||||
return nil
|
||||
}
|
||||
case packstream.PackedByteArray:
|
||||
return h.unp.ByteArray()
|
||||
case packstream.PackedArray:
|
||||
return h.array()
|
||||
case packstream.PackedMap:
|
||||
return h.amap()
|
||||
case packstream.PackedNil:
|
||||
return nil
|
||||
case packstream.PackedTrue:
|
||||
return true
|
||||
case packstream.PackedFalse:
|
||||
return false
|
||||
default:
|
||||
h.setErr(&db.ProtocolError{
|
||||
Err: fmt.Sprintf("Received unknown packstream value type: %d", valueType),
|
||||
})
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Trashes current value
|
||||
func (h *hydrator) trash() {
|
||||
// TODO Less consuming implementation
|
||||
h.value()
|
||||
}
|
||||
|
||||
func (h *hydrator) node(num uint32) interface{} {
|
||||
h.assertLength("node", 3, num)
|
||||
if h.getErr() != nil {
|
||||
return nil
|
||||
}
|
||||
n := dbtype.Node{}
|
||||
h.unp.Next()
|
||||
n.Id = h.unp.Int()
|
||||
h.unp.Next()
|
||||
n.Labels = h.strings()
|
||||
h.unp.Next()
|
||||
n.Props = h.amap()
|
||||
return n
|
||||
}
|
||||
|
||||
func (h *hydrator) relationship(n uint32) interface{} {
|
||||
h.assertLength("relationship", 5, n)
|
||||
if h.getErr() != nil {
|
||||
return nil
|
||||
}
|
||||
r := dbtype.Relationship{}
|
||||
h.unp.Next()
|
||||
r.Id = h.unp.Int()
|
||||
h.unp.Next()
|
||||
r.StartId = h.unp.Int()
|
||||
h.unp.Next()
|
||||
r.EndId = h.unp.Int()
|
||||
h.unp.Next()
|
||||
r.Type = h.unp.String()
|
||||
h.unp.Next()
|
||||
r.Props = h.amap()
|
||||
return r
|
||||
}
|
||||
|
||||
func (h *hydrator) relationnode(n uint32) interface{} {
|
||||
h.assertLength("relationnode", 3, n)
|
||||
if h.getErr() != nil {
|
||||
return nil
|
||||
}
|
||||
r := relNode{}
|
||||
h.unp.Next()
|
||||
r.id = h.unp.Int()
|
||||
h.unp.Next()
|
||||
r.name = h.unp.String()
|
||||
h.unp.Next()
|
||||
r.props = h.amap()
|
||||
return &r
|
||||
}
|
||||
|
||||
func (h *hydrator) path(n uint32) interface{} {
|
||||
h.assertLength("path", 3, n)
|
||||
if h.getErr() != nil {
|
||||
return nil
|
||||
}
|
||||
// Array of nodes
|
||||
h.unp.Next()
|
||||
num := h.unp.Int()
|
||||
nodes := make([]dbtype.Node, num)
|
||||
for i := range nodes {
|
||||
h.unp.Next()
|
||||
node, ok := h.value().(dbtype.Node)
|
||||
if !ok {
|
||||
h.setErr(&db.ProtocolError{
|
||||
MessageType: "path",
|
||||
Field: "nodes",
|
||||
Err: "value could not be cast to Node",
|
||||
})
|
||||
return nil
|
||||
}
|
||||
nodes[i] = node
|
||||
}
|
||||
// Array of relnodes
|
||||
h.unp.Next()
|
||||
num = h.unp.Int()
|
||||
rnodes := make([]*relNode, num)
|
||||
for i := range rnodes {
|
||||
h.unp.Next()
|
||||
rnode, ok := h.value().(*relNode)
|
||||
if !ok {
|
||||
h.setErr(&db.ProtocolError{
|
||||
MessageType: "path",
|
||||
Field: "rnodes",
|
||||
Err: "value could be not cast to *relNode",
|
||||
})
|
||||
return nil
|
||||
}
|
||||
rnodes[i] = rnode
|
||||
}
|
||||
// Array of indexes
|
||||
h.unp.Next()
|
||||
num = h.unp.Int()
|
||||
indexes := make([]int, num)
|
||||
for i := range indexes {
|
||||
h.unp.Next()
|
||||
indexes[i] = int(h.unp.Int())
|
||||
}
|
||||
|
||||
if (len(indexes) & 0x01) == 1 {
|
||||
h.setErr(&db.ProtocolError{
|
||||
MessageType: "path",
|
||||
Field: "indices",
|
||||
Err: fmt.Sprintf("there should be an even number of indices, found %d", len(indexes)),
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
return buildPath(nodes, rnodes, indexes)
|
||||
}
|
||||
|
||||
func (h *hydrator) point2d(n uint32) interface{} {
|
||||
p := dbtype.Point2D{}
|
||||
h.unp.Next()
|
||||
p.SpatialRefId = uint32(h.unp.Int())
|
||||
h.unp.Next()
|
||||
p.X = h.unp.Float()
|
||||
h.unp.Next()
|
||||
p.Y = h.unp.Float()
|
||||
return p
|
||||
}
|
||||
|
||||
func (h *hydrator) point3d(n uint32) interface{} {
|
||||
p := dbtype.Point3D{}
|
||||
h.unp.Next()
|
||||
p.SpatialRefId = uint32(h.unp.Int())
|
||||
h.unp.Next()
|
||||
p.X = h.unp.Float()
|
||||
h.unp.Next()
|
||||
p.Y = h.unp.Float()
|
||||
h.unp.Next()
|
||||
p.Z = h.unp.Float()
|
||||
return p
|
||||
}
|
||||
|
||||
func (h *hydrator) dateTimeOffset(n uint32) interface{} {
|
||||
h.unp.Next()
|
||||
secs := h.unp.Int()
|
||||
h.unp.Next()
|
||||
nans := h.unp.Int()
|
||||
h.unp.Next()
|
||||
offs := h.unp.Int()
|
||||
t := time.Unix(secs, nans).UTC()
|
||||
l := time.FixedZone("Offset", int(offs))
|
||||
return time.Date(t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second(), t.Nanosecond(), l)
|
||||
}
|
||||
|
||||
func (h *hydrator) dateTimeNamedZone(n uint32) interface{} {
|
||||
h.unp.Next()
|
||||
secs := h.unp.Int()
|
||||
h.unp.Next()
|
||||
nans := h.unp.Int()
|
||||
h.unp.Next()
|
||||
zone := h.unp.String()
|
||||
t := time.Unix(secs, nans).UTC()
|
||||
l, err := time.LoadLocation(zone)
|
||||
if err != nil {
|
||||
h.setErr(&db.ProtocolError{
|
||||
MessageType: "dateTimeNamedZone",
|
||||
Field: "location",
|
||||
Err: err.Error(),
|
||||
})
|
||||
return nil
|
||||
}
|
||||
return time.Date(t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second(), t.Nanosecond(), l)
|
||||
}
|
||||
|
||||
func (h *hydrator) localDateTime(n uint32) interface{} {
|
||||
h.unp.Next()
|
||||
secs := h.unp.Int()
|
||||
h.unp.Next()
|
||||
nans := h.unp.Int()
|
||||
t := time.Unix(secs, nans).UTC()
|
||||
t = time.Date(t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second(), t.Nanosecond(), time.Local)
|
||||
return dbtype.LocalDateTime(t)
|
||||
}
|
||||
|
||||
func (h *hydrator) date(n uint32) interface{} {
|
||||
h.unp.Next()
|
||||
days := h.unp.Int()
|
||||
secs := days * 86400
|
||||
return dbtype.Date(time.Unix(secs, 0).UTC())
|
||||
}
|
||||
|
||||
func (h *hydrator) time(n uint32) interface{} {
|
||||
h.unp.Next()
|
||||
nans := h.unp.Int()
|
||||
h.unp.Next()
|
||||
offs := h.unp.Int()
|
||||
secs := nans / int64(time.Second)
|
||||
nans -= secs * int64(time.Second)
|
||||
l := time.FixedZone("Offset", int(offs))
|
||||
t := time.Date(0, 0, 0, 0, 0, int(secs), int(nans), l)
|
||||
return dbtype.Time(t)
|
||||
}
|
||||
|
||||
func (h *hydrator) localTime(n uint32) interface{} {
|
||||
h.unp.Next()
|
||||
nans := h.unp.Int()
|
||||
secs := nans / int64(time.Second)
|
||||
nans -= secs * int64(time.Second)
|
||||
t := time.Date(0, 0, 0, 0, 0, int(secs), int(nans), time.Local)
|
||||
return dbtype.LocalTime(t)
|
||||
}
|
||||
|
||||
func (h *hydrator) duration(n uint32) interface{} {
|
||||
h.unp.Next()
|
||||
mon := h.unp.Int()
|
||||
h.unp.Next()
|
||||
day := h.unp.Int()
|
||||
h.unp.Next()
|
||||
sec := h.unp.Int()
|
||||
h.unp.Next()
|
||||
nan := h.unp.Int()
|
||||
return dbtype.Duration{Months: mon, Days: day, Seconds: sec, Nanos: int(nan)}
|
||||
}
|
||||
|
||||
func parseNotifications(notificationsx []interface{}) []db.Notification {
|
||||
var notifications []db.Notification
|
||||
if notificationsx != nil {
|
||||
notifications = make([]db.Notification, 0, len(notificationsx))
|
||||
for _, x := range notificationsx {
|
||||
notificationx, ok := x.(map[string]interface{})
|
||||
if ok {
|
||||
notifications = append(notifications, parseNotification(notificationx))
|
||||
}
|
||||
}
|
||||
}
|
||||
return notifications
|
||||
}
|
||||
|
||||
func parsePlanOpIdArgsChildren(planx map[string]interface{}) (string, []string, map[string]interface{}, []interface{}) {
|
||||
operator, _ := planx["operatorType"].(string)
|
||||
identifiersx, _ := planx["identifiers"].([]interface{})
|
||||
arguments, _ := planx["args"].(map[string]interface{})
|
||||
|
||||
identifiers := make([]string, len(identifiersx))
|
||||
for i, id := range identifiersx {
|
||||
identifiers[i], _ = id.(string)
|
||||
}
|
||||
|
||||
childrenx, _ := planx["children"].([]interface{})
|
||||
|
||||
return operator, identifiers, arguments, childrenx
|
||||
}
|
||||
|
||||
func parsePlan(planx map[string]interface{}) *db.Plan {
|
||||
op, ids, args, childrenx := parsePlanOpIdArgsChildren(planx)
|
||||
plan := &db.Plan{
|
||||
Operator: op,
|
||||
Arguments: args,
|
||||
Identifiers: ids,
|
||||
}
|
||||
|
||||
plan.Children = make([]db.Plan, 0, len(childrenx))
|
||||
for _, c := range childrenx {
|
||||
childPlanx, _ := c.(map[string]interface{})
|
||||
if len(childPlanx) > 0 {
|
||||
childPlan := parsePlan(childPlanx)
|
||||
if childPlan != nil {
|
||||
plan.Children = append(plan.Children, *childPlan)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return plan
|
||||
}
|
||||
|
||||
func parseProfile(profilex map[string]interface{}) *db.ProfiledPlan {
|
||||
op, ids, args, childrenx := parsePlanOpIdArgsChildren(profilex)
|
||||
plan := &db.ProfiledPlan{
|
||||
Operator: op,
|
||||
Arguments: args,
|
||||
Identifiers: ids,
|
||||
}
|
||||
|
||||
plan.DbHits, _ = profilex["dbHits"].(int64)
|
||||
plan.Records, _ = profilex["rows"].(int64)
|
||||
|
||||
plan.Children = make([]db.ProfiledPlan, 0, len(childrenx))
|
||||
for _, c := range childrenx {
|
||||
childPlanx, _ := c.(map[string]interface{})
|
||||
if len(childPlanx) > 0 {
|
||||
childPlan := parseProfile(childPlanx)
|
||||
if childPlan != nil {
|
||||
if pageCacheMisses, ok := childPlanx["pageCacheMisses"]; ok {
|
||||
childPlan.PageCacheMisses = pageCacheMisses.(int64)
|
||||
}
|
||||
if pageCacheHits, ok := childPlanx["pageCacheHits"]; ok {
|
||||
childPlan.PageCacheHits = pageCacheHits.(int64)
|
||||
}
|
||||
if pageCacheHitRatio, ok := childPlanx["pageCacheHitRatio"]; ok {
|
||||
childPlan.PageCacheHitRatio = pageCacheHitRatio.(float64)
|
||||
}
|
||||
if planTime, ok := childPlanx["time"]; ok {
|
||||
childPlan.Time = planTime.(int64)
|
||||
}
|
||||
plan.Children = append(plan.Children, *childPlan)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return plan
|
||||
}
|
||||
|
||||
func parseNotification(m map[string]interface{}) db.Notification {
|
||||
n := db.Notification{}
|
||||
n.Code, _ = m["code"].(string)
|
||||
n.Description = m["description"].(string)
|
||||
n.Severity, _ = m["severity"].(string)
|
||||
n.Title, _ = m["title"].(string)
|
||||
posx, exists := m["position"].(map[string]interface{})
|
||||
if exists {
|
||||
pos := &db.InputPosition{}
|
||||
i, _ := posx["column"].(int64)
|
||||
pos.Column = int(i)
|
||||
i, _ = posx["line"].(int64)
|
||||
pos.Line = int(i)
|
||||
i, _ = posx["offset"].(int64)
|
||||
pos.Offset = int(i)
|
||||
n.Position = pos
|
||||
}
|
||||
|
||||
return n
|
||||
}
|
@ -1,47 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) "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 bolt
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/neo4j/neo4j-go-driver/v5/neo4j/log"
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
type incoming struct {
|
||||
buf []byte // Reused buffer
|
||||
hyd hydrator
|
||||
connReadTimeout time.Duration
|
||||
logger log.Logger
|
||||
logName string
|
||||
logId string
|
||||
}
|
||||
|
||||
func (i *incoming) next(ctx context.Context, rd net.Conn) (interface{}, error) {
|
||||
// Get next message from transport layer
|
||||
var err error
|
||||
var msg []byte
|
||||
i.buf, msg, err = dechunkMessage(ctx, rd, i.buf, i.connReadTimeout, i.logger, i.logName, i.logId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return i.hyd.hydrate(msg)
|
||||
}
|
@ -1,41 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) "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 bolt
|
||||
|
||||
// Message struct tags
|
||||
// Shared between bolt versions
|
||||
const (
|
||||
msgReset byte = 0x0f
|
||||
msgRun byte = 0x10
|
||||
msgDiscardAll byte = 0x2f
|
||||
msgDiscardN = msgDiscardAll // Different name >= 4.0
|
||||
msgPullAll byte = 0x3f
|
||||
msgPullN = msgPullAll // Different name >= 4.0
|
||||
msgRecord byte = 0x71
|
||||
msgSuccess byte = 0x70
|
||||
msgIgnored byte = 0x7e
|
||||
msgFailure byte = 0x7f
|
||||
msgHello byte = 0x01
|
||||
msgGoodbye byte = 0x02
|
||||
msgBegin byte = 0x11
|
||||
msgCommit byte = 0x12
|
||||
msgRollback byte = 0x13
|
||||
msgRoute byte = 0x66 // > 4.2
|
||||
)
|
@ -1,413 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) "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 bolt
|
||||
|
||||
import (
|
||||
"context"
|
||||
idb "github.com/neo4j/neo4j-go-driver/v5/neo4j/internal/db"
|
||||
"github.com/neo4j/neo4j-go-driver/v5/neo4j/log"
|
||||
"io"
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
"github.com/neo4j/neo4j-go-driver/v5/neo4j/db"
|
||||
"github.com/neo4j/neo4j-go-driver/v5/neo4j/dbtype"
|
||||
"github.com/neo4j/neo4j-go-driver/v5/neo4j/internal/packstream"
|
||||
)
|
||||
|
||||
type outgoing struct {
|
||||
chunker chunker
|
||||
packer packstream.Packer
|
||||
onErr func(err error)
|
||||
boltLogger log.BoltLogger
|
||||
logId string
|
||||
}
|
||||
|
||||
func (o *outgoing) begin() {
|
||||
o.chunker.beginMessage()
|
||||
o.packer.Begin(o.chunker.buf)
|
||||
}
|
||||
|
||||
func (o *outgoing) end() {
|
||||
buf, err := o.packer.End()
|
||||
o.chunker.buf = buf
|
||||
o.chunker.endMessage()
|
||||
if err != nil {
|
||||
o.onErr(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (o *outgoing) appendHello(hello map[string]interface{}) {
|
||||
if o.boltLogger != nil {
|
||||
o.boltLogger.LogClientMessage(o.logId, "HELLO %s", loggableDictionary(hello))
|
||||
}
|
||||
o.begin()
|
||||
o.packer.StructHeader(byte(msgHello), 1)
|
||||
o.packMap(hello)
|
||||
o.end()
|
||||
}
|
||||
|
||||
func (o *outgoing) appendBegin(meta map[string]interface{}) {
|
||||
if o.boltLogger != nil {
|
||||
o.boltLogger.LogClientMessage(o.logId, "BEGIN %s", loggableDictionary(meta))
|
||||
}
|
||||
o.begin()
|
||||
o.packer.StructHeader(byte(msgBegin), 1)
|
||||
o.packMap(meta)
|
||||
o.end()
|
||||
}
|
||||
|
||||
func (o *outgoing) appendCommit() {
|
||||
if o.boltLogger != nil {
|
||||
o.boltLogger.LogClientMessage(o.logId, "COMMIT")
|
||||
}
|
||||
o.begin()
|
||||
o.packer.StructHeader(byte(msgCommit), 0)
|
||||
o.end()
|
||||
}
|
||||
|
||||
func (o *outgoing) appendRollback() {
|
||||
if o.boltLogger != nil {
|
||||
o.boltLogger.LogClientMessage(o.logId, "ROLLBACK")
|
||||
}
|
||||
o.begin()
|
||||
o.packer.StructHeader(byte(msgRollback), 0)
|
||||
o.end()
|
||||
}
|
||||
|
||||
func (o *outgoing) appendRun(cypher string, params, meta map[string]interface{}) {
|
||||
if o.boltLogger != nil {
|
||||
o.boltLogger.LogClientMessage(o.logId, "RUN %q %s %s", cypher, loggableDictionary(params), loggableDictionary(meta))
|
||||
}
|
||||
o.begin()
|
||||
o.packer.StructHeader(byte(msgRun), 3)
|
||||
o.packer.String(cypher)
|
||||
o.packMap(params)
|
||||
o.packMap(meta)
|
||||
o.end()
|
||||
}
|
||||
|
||||
func (o *outgoing) appendPullN(n int) {
|
||||
if o.boltLogger != nil {
|
||||
o.boltLogger.LogClientMessage(o.logId, "PULL %s", loggableDictionary{"n": n})
|
||||
}
|
||||
o.begin()
|
||||
o.packer.StructHeader(byte(msgPullN), 1)
|
||||
o.packer.MapHeader(1)
|
||||
o.packer.String("n")
|
||||
o.packer.Int(n)
|
||||
o.end()
|
||||
}
|
||||
|
||||
func (o *outgoing) appendPullNQid(n int, qid int64) {
|
||||
if o.boltLogger != nil {
|
||||
o.boltLogger.LogClientMessage(o.logId, "PULL %s", loggableDictionary{"n": n, "qid": qid})
|
||||
}
|
||||
o.begin()
|
||||
o.packer.StructHeader(byte(msgPullN), 1)
|
||||
o.packer.MapHeader(2)
|
||||
o.packer.String("n")
|
||||
o.packer.Int(n)
|
||||
o.packer.String("qid")
|
||||
o.packer.Int64(qid)
|
||||
o.end()
|
||||
}
|
||||
|
||||
func (o *outgoing) appendDiscardN(n int) {
|
||||
if o.boltLogger != nil {
|
||||
o.boltLogger.LogClientMessage(o.logId, "DISCARD %s", loggableDictionary{"n": n})
|
||||
}
|
||||
o.begin()
|
||||
o.packer.StructHeader(byte(msgDiscardN), 1)
|
||||
o.packer.MapHeader(1)
|
||||
o.packer.String("n")
|
||||
o.packer.Int(n)
|
||||
o.end()
|
||||
}
|
||||
|
||||
func (o *outgoing) appendDiscardNQid(n int, qid int64) {
|
||||
if o.boltLogger != nil {
|
||||
o.boltLogger.LogClientMessage(o.logId, "DISCARD %s", loggableDictionary{"n": n, "qid": qid})
|
||||
}
|
||||
o.begin()
|
||||
o.packer.StructHeader(byte(msgDiscardN), 1)
|
||||
o.packer.MapHeader(2)
|
||||
o.packer.String("n")
|
||||
o.packer.Int(n)
|
||||
o.packer.String("qid")
|
||||
o.packer.Int64(qid)
|
||||
o.end()
|
||||
}
|
||||
|
||||
func (o *outgoing) appendPullAll() {
|
||||
if o.boltLogger != nil {
|
||||
o.boltLogger.LogClientMessage(o.logId, "PULL ALL")
|
||||
}
|
||||
o.begin()
|
||||
o.packer.StructHeader(byte(msgPullAll), 0)
|
||||
o.end()
|
||||
}
|
||||
|
||||
// Only valid for V4.3
|
||||
func (o *outgoing) appendRouteToV43(context map[string]string, bookmarks []string, database string) {
|
||||
if o.boltLogger != nil {
|
||||
o.boltLogger.LogClientMessage(o.logId, "ROUTE %s %s %q", loggableStringDictionary(context), loggableStringList(bookmarks), database)
|
||||
}
|
||||
o.begin()
|
||||
o.packer.StructHeader(byte(msgRoute), 3)
|
||||
o.packer.MapHeader(len(context))
|
||||
for k, v := range context {
|
||||
o.packer.String(k)
|
||||
o.packer.String(v)
|
||||
}
|
||||
o.packer.ArrayHeader(len(bookmarks))
|
||||
for _, bookmark := range bookmarks {
|
||||
o.packer.String(bookmark)
|
||||
}
|
||||
if database == idb.DefaultDatabase {
|
||||
o.packer.Nil()
|
||||
} else {
|
||||
o.packer.String(database)
|
||||
}
|
||||
o.end()
|
||||
}
|
||||
|
||||
func (o *outgoing) appendRoute(context map[string]string, bookmarks []string, what map[string]interface{}) {
|
||||
if o.boltLogger != nil {
|
||||
o.boltLogger.LogClientMessage(o.logId, "ROUTE %s %s %s", loggableStringDictionary(context), loggableStringList(bookmarks), loggableDictionary(what))
|
||||
}
|
||||
o.begin()
|
||||
o.packer.StructHeader(byte(msgRoute), 3)
|
||||
o.packer.MapHeader(len(context))
|
||||
for k, v := range context {
|
||||
o.packer.String(k)
|
||||
o.packer.String(v)
|
||||
}
|
||||
o.packer.ArrayHeader(len(bookmarks))
|
||||
for _, bookmark := range bookmarks {
|
||||
o.packer.String(bookmark)
|
||||
}
|
||||
o.packMap(what)
|
||||
o.end()
|
||||
}
|
||||
|
||||
func (o *outgoing) appendReset() {
|
||||
if o.boltLogger != nil {
|
||||
o.boltLogger.LogClientMessage(o.logId, "RESET")
|
||||
}
|
||||
o.begin()
|
||||
o.packer.StructHeader(byte(msgReset), 0)
|
||||
o.end()
|
||||
}
|
||||
|
||||
func (o *outgoing) appendGoodbye() {
|
||||
if o.boltLogger != nil {
|
||||
o.boltLogger.LogClientMessage(o.logId, "GOODBYE")
|
||||
}
|
||||
o.begin()
|
||||
o.packer.StructHeader(byte(msgGoodbye), 0)
|
||||
o.end()
|
||||
}
|
||||
|
||||
// For tests
|
||||
func (o *outgoing) appendX(tag byte, fields ...interface{}) {
|
||||
o.begin()
|
||||
o.packer.StructHeader(tag, len(fields))
|
||||
for _, f := range fields {
|
||||
o.packX(f)
|
||||
}
|
||||
o.end()
|
||||
}
|
||||
|
||||
func (o *outgoing) send(ctx context.Context, wr io.Writer) {
|
||||
err := o.chunker.send(ctx, wr)
|
||||
if err != nil {
|
||||
o.onErr(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (o *outgoing) packMap(m map[string]interface{}) {
|
||||
o.packer.MapHeader(len(m))
|
||||
for k, v := range m {
|
||||
o.packer.String(k)
|
||||
o.packX(v)
|
||||
}
|
||||
}
|
||||
|
||||
func (o *outgoing) packStruct(x interface{}) {
|
||||
switch v := x.(type) {
|
||||
case *dbtype.Point2D:
|
||||
o.packer.StructHeader('X', 3)
|
||||
o.packer.Uint32(v.SpatialRefId)
|
||||
o.packer.Float64(v.X)
|
||||
o.packer.Float64(v.Y)
|
||||
case dbtype.Point2D:
|
||||
o.packer.StructHeader('X', 3)
|
||||
o.packer.Uint32(v.SpatialRefId)
|
||||
o.packer.Float64(v.X)
|
||||
o.packer.Float64(v.Y)
|
||||
case *dbtype.Point3D:
|
||||
o.packer.StructHeader('Y', 4)
|
||||
o.packer.Uint32(v.SpatialRefId)
|
||||
o.packer.Float64(v.X)
|
||||
o.packer.Float64(v.Y)
|
||||
o.packer.Float64(v.Z)
|
||||
case dbtype.Point3D:
|
||||
o.packer.StructHeader('Y', 4)
|
||||
o.packer.Uint32(v.SpatialRefId)
|
||||
o.packer.Float64(v.X)
|
||||
o.packer.Float64(v.Y)
|
||||
o.packer.Float64(v.Z)
|
||||
case time.Time:
|
||||
zone, offset := v.Zone()
|
||||
secs := v.Unix() + int64(offset)
|
||||
nanos := v.Nanosecond()
|
||||
if zone == "Offset" {
|
||||
o.packer.StructHeader('F', 3)
|
||||
o.packer.Int64(secs)
|
||||
o.packer.Int(nanos)
|
||||
o.packer.Int(offset)
|
||||
} else {
|
||||
o.packer.StructHeader('f', 3)
|
||||
o.packer.Int64(secs)
|
||||
o.packer.Int(nanos)
|
||||
o.packer.String(v.Location().String())
|
||||
}
|
||||
case dbtype.LocalDateTime:
|
||||
t := time.Time(v)
|
||||
_, offset := t.Zone()
|
||||
secs := t.Unix() + int64(offset)
|
||||
o.packer.StructHeader('d', 2)
|
||||
o.packer.Int64(secs)
|
||||
o.packer.Int(t.Nanosecond())
|
||||
case dbtype.Date:
|
||||
t := time.Time(v)
|
||||
secs := t.Unix()
|
||||
_, offset := t.Zone()
|
||||
secs += int64(offset)
|
||||
days := secs / (60 * 60 * 24)
|
||||
o.packer.StructHeader('D', 1)
|
||||
o.packer.Int64(days)
|
||||
case dbtype.Time:
|
||||
t := time.Time(v)
|
||||
_, tzOffsetSecs := t.Zone()
|
||||
d := t.Sub(
|
||||
time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, t.Location()))
|
||||
o.packer.StructHeader('T', 2)
|
||||
o.packer.Int64(d.Nanoseconds())
|
||||
o.packer.Int(tzOffsetSecs)
|
||||
case dbtype.LocalTime:
|
||||
t := time.Time(v)
|
||||
nanos := int64(time.Hour)*int64(t.Hour()) +
|
||||
int64(time.Minute)*int64(t.Minute()) +
|
||||
int64(time.Second)*int64(t.Second()) +
|
||||
int64(t.Nanosecond())
|
||||
o.packer.StructHeader('t', 1)
|
||||
o.packer.Int64(nanos)
|
||||
case dbtype.Duration:
|
||||
o.packer.StructHeader('E', 4)
|
||||
o.packer.Int64(v.Months)
|
||||
o.packer.Int64(v.Days)
|
||||
o.packer.Int64(v.Seconds)
|
||||
o.packer.Int(v.Nanos)
|
||||
default:
|
||||
o.onErr(&db.UnsupportedTypeError{Type: reflect.TypeOf(x)})
|
||||
}
|
||||
}
|
||||
|
||||
func (o *outgoing) packX(x interface{}) {
|
||||
if x == nil {
|
||||
o.packer.Nil()
|
||||
return
|
||||
}
|
||||
|
||||
v := reflect.ValueOf(x)
|
||||
switch v.Kind() {
|
||||
case reflect.Bool:
|
||||
o.packer.Bool(v.Bool())
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
o.packer.Int64(v.Int())
|
||||
case reflect.Uint8, reflect.Uint16, reflect.Uint32:
|
||||
o.packer.Uint32(uint32(v.Uint()))
|
||||
case reflect.Uint64, reflect.Uint:
|
||||
o.packer.Uint64(v.Uint())
|
||||
case reflect.Float32, reflect.Float64:
|
||||
o.packer.Float64(v.Float())
|
||||
case reflect.String:
|
||||
o.packer.String(v.String())
|
||||
case reflect.Ptr:
|
||||
if v.IsNil() {
|
||||
o.packer.Nil()
|
||||
return
|
||||
}
|
||||
// Inspect what the pointer points to
|
||||
i := reflect.Indirect(v)
|
||||
switch i.Kind() {
|
||||
case reflect.Struct:
|
||||
o.packStruct(x)
|
||||
default:
|
||||
o.packX(i.Interface())
|
||||
}
|
||||
case reflect.Struct:
|
||||
o.packStruct(x)
|
||||
case reflect.Slice:
|
||||
// Optimizations
|
||||
switch s := x.(type) {
|
||||
case []byte:
|
||||
o.packer.Bytes(s) // Not just optimization
|
||||
case []int:
|
||||
o.packer.Ints(s)
|
||||
case []int64:
|
||||
o.packer.Int64s(s)
|
||||
case []string:
|
||||
o.packer.Strings(s)
|
||||
case []float64:
|
||||
o.packer.Float64s(s)
|
||||
default:
|
||||
num := v.Len()
|
||||
o.packer.ArrayHeader(num)
|
||||
for i := 0; i < num; i++ {
|
||||
o.packX(v.Index(i).Interface())
|
||||
}
|
||||
}
|
||||
case reflect.Map:
|
||||
// Optimizations
|
||||
switch m := x.(type) {
|
||||
case map[string]int:
|
||||
o.packer.IntMap(m)
|
||||
case map[string]string:
|
||||
o.packer.StringMap(m)
|
||||
default:
|
||||
t := reflect.TypeOf(x)
|
||||
if t.Key().Kind() != reflect.String {
|
||||
o.onErr(&db.UnsupportedTypeError{Type: reflect.TypeOf(x)})
|
||||
return
|
||||
}
|
||||
o.packer.MapHeader(v.Len())
|
||||
// TODO Use MapRange when min Go version is >= 1.12
|
||||
for _, ki := range v.MapKeys() {
|
||||
o.packer.String(ki.String())
|
||||
o.packX(v.MapIndex(ki).Interface())
|
||||
}
|
||||
}
|
||||
default:
|
||||
o.onErr(&db.UnsupportedTypeError{Type: reflect.TypeOf(x)})
|
||||
}
|
||||
}
|
75
vendor/github.com/neo4j/neo4j-go-driver/v5/neo4j/internal/bolt/parseroutingtable.go
generated
vendored
75
vendor/github.com/neo4j/neo4j-go-driver/v5/neo4j/internal/bolt/parseroutingtable.go
generated
vendored
@ -1,75 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) "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 bolt
|
||||
|
||||
import (
|
||||
"github.com/neo4j/neo4j-go-driver/v5/neo4j/db"
|
||||
idb "github.com/neo4j/neo4j-go-driver/v5/neo4j/internal/db"
|
||||
)
|
||||
|
||||
// Parses a record assumed to contain a routing table into common DB API routing table struct
|
||||
// Returns nil if error while parsing
|
||||
func parseRoutingTableRecord(rec *db.Record) *idb.RoutingTable {
|
||||
ttl, ok := rec.Values[0].(int64)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
listOfX, ok := rec.Values[1].([]interface{})
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
table := &idb.RoutingTable{
|
||||
TimeToLive: int(ttl),
|
||||
}
|
||||
|
||||
for _, x := range listOfX {
|
||||
// Each x should be a map consisting of addresses and the role
|
||||
m, ok := x.(map[string]interface{})
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
addressesX, ok := m["addresses"].([]interface{})
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
addresses := make([]string, len(addressesX))
|
||||
for i, addrX := range addressesX {
|
||||
addr, ok := addrX.(string)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
addresses[i] = addr
|
||||
}
|
||||
role, ok := m["role"].(string)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
switch role {
|
||||
case "READ":
|
||||
table.Readers = addresses
|
||||
case "WRITE":
|
||||
table.Writers = addresses
|
||||
case "ROUTE":
|
||||
table.Routers = addresses
|
||||
}
|
||||
}
|
||||
return table
|
||||
}
|
@ -1,81 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) "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 bolt
|
||||
|
||||
import (
|
||||
"github.com/neo4j/neo4j-go-driver/v5/neo4j/dbtype"
|
||||
)
|
||||
|
||||
// Intermediate representation of part of path
|
||||
type relNode struct {
|
||||
id int64
|
||||
name string
|
||||
props map[string]interface{}
|
||||
}
|
||||
|
||||
// buildPath builds a path from Bolt representation
|
||||
func buildPath(nodes []dbtype.Node, relNodes []*relNode, indexes []int) dbtype.Path {
|
||||
num := len(indexes) / 2
|
||||
if num == 0 {
|
||||
var path dbtype.Path
|
||||
if len(nodes) > 0 {
|
||||
// there could be a single disconnected node
|
||||
path.Nodes = nodes
|
||||
}
|
||||
return path
|
||||
}
|
||||
rels := make([]dbtype.Relationship, 0, num)
|
||||
|
||||
i := 0
|
||||
n1 := nodes[0]
|
||||
for num > 0 {
|
||||
relni := indexes[i]
|
||||
i++
|
||||
n2i := indexes[i]
|
||||
i++
|
||||
num--
|
||||
var reln *relNode
|
||||
var n1start bool
|
||||
if relni < 0 {
|
||||
reln = relNodes[(relni*-1)-1]
|
||||
} else {
|
||||
reln = relNodes[relni-1]
|
||||
n1start = true
|
||||
}
|
||||
n2 := nodes[n2i]
|
||||
|
||||
rel := dbtype.Relationship{
|
||||
Id: reln.id,
|
||||
Type: reln.name,
|
||||
Props: reln.props,
|
||||
}
|
||||
if n1start {
|
||||
rel.StartId = n1.Id
|
||||
rel.EndId = n2.Id
|
||||
} else {
|
||||
rel.StartId = n2.Id
|
||||
rel.EndId = n1.Id
|
||||
}
|
||||
rels = append(rels, rel)
|
||||
n1 = n2
|
||||
}
|
||||
|
||||
return dbtype.Path{Nodes: nodes, Relationships: rels}
|
||||
}
|
@ -1,154 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) "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 bolt
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
"errors"
|
||||
idb "github.com/neo4j/neo4j-go-driver/v5/neo4j/internal/db"
|
||||
"time"
|
||||
|
||||
"github.com/neo4j/neo4j-go-driver/v5/neo4j/db"
|
||||
)
|
||||
|
||||
type stream struct {
|
||||
keys []string
|
||||
fifo list.List
|
||||
sum *db.Summary
|
||||
err error
|
||||
qid int64
|
||||
fetchSize int
|
||||
key int64
|
||||
}
|
||||
|
||||
// Acts on buffered data, first return value indicates if buffering
|
||||
// is active or not.
|
||||
func (s *stream) bufferedNext() (bool, *db.Record, *db.Summary, error) {
|
||||
e := s.fifo.Front()
|
||||
if e != nil {
|
||||
s.fifo.Remove(e)
|
||||
return true, e.Value.(*db.Record), nil, nil
|
||||
}
|
||||
if s.err != nil {
|
||||
return true, nil, nil, s.err
|
||||
}
|
||||
if s.sum != nil {
|
||||
return true, nil, s.sum, nil
|
||||
}
|
||||
|
||||
return false, nil, nil, nil
|
||||
}
|
||||
|
||||
// Delayed error until fifo emptied
|
||||
func (s *stream) Err() error {
|
||||
if s.fifo.Len() > 0 {
|
||||
return nil
|
||||
}
|
||||
return s.err
|
||||
}
|
||||
|
||||
func (s *stream) push(rec *db.Record) {
|
||||
s.fifo.PushBack(rec)
|
||||
}
|
||||
|
||||
// Only need to keep track of current stream. Client keeps track of other
|
||||
// open streams and a key in each stream is used to validate if it belongs to
|
||||
// current bolt connection or not.
|
||||
type openstreams struct {
|
||||
curr *stream
|
||||
num int
|
||||
key int64
|
||||
}
|
||||
|
||||
var (
|
||||
invalidStream = errors.New("Invalid stream handle")
|
||||
)
|
||||
|
||||
// Adds a new open stream and sets it as current.
|
||||
// There should NOT be a current stream .
|
||||
func (o *openstreams) attach(s *stream) {
|
||||
if o.curr != nil {
|
||||
return
|
||||
}
|
||||
// Track number of open streams and set the stream as current
|
||||
o.num++
|
||||
o.curr = s
|
||||
s.key = o.key
|
||||
}
|
||||
|
||||
// Detaches the current stream from being current and
|
||||
// removes it from set of open streams it is no longer open.
|
||||
// The stream should be either in failed state or completed.
|
||||
func (o *openstreams) detach(sum *db.Summary, err error) {
|
||||
if o.curr == nil {
|
||||
return
|
||||
}
|
||||
|
||||
o.curr.sum = sum
|
||||
o.curr.err = err
|
||||
o.remove(o.curr)
|
||||
}
|
||||
|
||||
// Streams can be paused when they have received a "has_more" response from server
|
||||
// Pauses the current stream
|
||||
func (o *openstreams) pause() {
|
||||
o.curr = nil
|
||||
}
|
||||
|
||||
// When resuming a stream a new PULL message needs to be sent.
|
||||
func (o *openstreams) resume(s *stream) {
|
||||
if o.curr != nil {
|
||||
return
|
||||
}
|
||||
o.curr = s
|
||||
}
|
||||
|
||||
// Removes the stream by disabling its key and removing it from the count of streams.
|
||||
// If the stream is current the current is set to nil.
|
||||
func (o *openstreams) remove(s *stream) {
|
||||
o.num--
|
||||
s.key = 0
|
||||
if o.curr == s {
|
||||
o.curr = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (o *openstreams) reset() {
|
||||
o.num = 0
|
||||
o.curr = nil
|
||||
o.key = time.Now().UnixNano()
|
||||
}
|
||||
|
||||
// Checks that the handle represents a stream but not necessarily a stream belonging
|
||||
// to this set of open streams.
|
||||
func (o openstreams) getUnsafe(h idb.StreamHandle) (*stream, error) {
|
||||
stream, ok := h.(*stream)
|
||||
if !ok || stream == nil {
|
||||
return nil, invalidStream
|
||||
}
|
||||
return stream, nil
|
||||
}
|
||||
|
||||
func (o openstreams) isSafe(s *stream) error {
|
||||
if s.key == o.key {
|
||||
return nil
|
||||
}
|
||||
return invalidStream
|
||||
}
|
97
vendor/github.com/neo4j/neo4j-go-driver/v5/neo4j/internal/connector/connector.go
generated
vendored
97
vendor/github.com/neo4j/neo4j-go-driver/v5/neo4j/internal/connector/connector.go
generated
vendored
@ -1,97 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) "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 connector is responsible for connecting to a database server.
|
||||
package connector
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"errors"
|
||||
"github.com/neo4j/neo4j-go-driver/v5/neo4j/internal/db"
|
||||
"io"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/neo4j/neo4j-go-driver/v5/neo4j/internal/bolt"
|
||||
"github.com/neo4j/neo4j-go-driver/v5/neo4j/log"
|
||||
)
|
||||
|
||||
type Connector struct {
|
||||
SkipEncryption bool
|
||||
SkipVerify bool
|
||||
RootCAs *x509.CertPool
|
||||
DialTimeout time.Duration
|
||||
SocketKeepAlive bool
|
||||
Auth map[string]interface{}
|
||||
Log log.Logger
|
||||
UserAgent string
|
||||
RoutingContext map[string]string
|
||||
Network string
|
||||
}
|
||||
|
||||
func (c Connector) Connect(ctx context.Context, address string, boltLogger log.BoltLogger) (db.Connection, error) {
|
||||
dialer := net.Dialer{Timeout: c.DialTimeout}
|
||||
if !c.SocketKeepAlive {
|
||||
dialer.KeepAlive = -1 * time.Second // Turns keep-alive off
|
||||
}
|
||||
|
||||
conn, err := dialer.DialContext(ctx, c.Network, address)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// TLS not requested, perform Bolt handshake
|
||||
if c.SkipEncryption {
|
||||
return bolt.Connect(ctx, address, conn, c.Auth, c.UserAgent, c.RoutingContext, c.Log, boltLogger)
|
||||
}
|
||||
|
||||
// TLS requested, continue with handshake
|
||||
serverName, _, err := net.SplitHostPort(address)
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
return nil, err
|
||||
}
|
||||
config := tls.Config{InsecureSkipVerify: c.SkipVerify, RootCAs: c.RootCAs, ServerName: serverName}
|
||||
tlsconn := tls.Client(conn, &config)
|
||||
err = tlsconn.HandshakeContext(ctx)
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
// Give a bit nicer error message
|
||||
err = errors.New("Remote end closed the connection, check that TLS is enabled on the server")
|
||||
}
|
||||
conn.Close()
|
||||
return nil, &TlsError{inner: err}
|
||||
}
|
||||
// Perform Bolt handshake
|
||||
return bolt.Connect(ctx, address, tlsconn, c.Auth, c.UserAgent, c.RoutingContext, c.Log, boltLogger)
|
||||
}
|
||||
|
||||
// TlsError encapsulates all errors related to TLS connection creation
|
||||
// This is needed since the tls package does not provide a common error type
|
||||
// à la net.Error, and a common type is needed to properly classify the error
|
||||
// for Testkit
|
||||
type TlsError struct {
|
||||
inner error
|
||||
}
|
||||
|
||||
func (e *TlsError) Error() string {
|
||||
return e.inner.Error()
|
||||
}
|
@ -1,130 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) "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 db defines generic database functionality.
|
||||
package db
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/neo4j/neo4j-go-driver/v5/neo4j/db"
|
||||
"github.com/neo4j/neo4j-go-driver/v5/neo4j/log"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Definitions of these should correspond to public API
|
||||
type AccessMode int
|
||||
|
||||
const (
|
||||
WriteMode AccessMode = 0
|
||||
ReadMode AccessMode = 1
|
||||
)
|
||||
|
||||
type (
|
||||
TxHandle uint64
|
||||
StreamHandle interface{}
|
||||
)
|
||||
|
||||
type Command struct {
|
||||
Cypher string
|
||||
Params map[string]interface{}
|
||||
FetchSize int
|
||||
}
|
||||
|
||||
type TxConfig struct {
|
||||
Mode AccessMode
|
||||
Bookmarks []string
|
||||
Timeout time.Duration
|
||||
ImpersonatedUser string
|
||||
Meta map[string]interface{}
|
||||
}
|
||||
|
||||
// Connection defines an abstract database server connection.
|
||||
type Connection interface {
|
||||
TxBegin(ctx context.Context, txConfig TxConfig) (TxHandle, error)
|
||||
TxRollback(ctx context.Context, tx TxHandle) error
|
||||
TxCommit(ctx context.Context, tx TxHandle) error
|
||||
Run(ctx context.Context, cmd Command, txConfig TxConfig) (StreamHandle, error)
|
||||
RunTx(ctx context.Context, tx TxHandle, cmd Command) (StreamHandle, error)
|
||||
// Keys for the specified stream.
|
||||
Keys(streamHandle StreamHandle) ([]string, error)
|
||||
// Next moves to next item in the stream.
|
||||
// If error is nil, either Record or Summary has a value, if Record is nil there are no more records.
|
||||
// If error is non nil, neither Record or Summary has a value.
|
||||
Next(ctx context.Context, streamHandle StreamHandle) (*db.Record, *db.Summary, error)
|
||||
// Consume discards all records on the stream and returns the summary otherwise it will return the error.
|
||||
Consume(ctx context.Context, streamHandle StreamHandle) (*db.Summary, error)
|
||||
// Buffer buffers all records on the stream, records, summary and error will be received through call to Next
|
||||
// The Connection implementation should preserve/buffer streams automatically if needed when new
|
||||
// streams are created and the server doesn't support multiple streams. Use Buffer to force
|
||||
// buffering before calling Reset to get all records and the bookmark.
|
||||
Buffer(ctx context.Context, streamHandle StreamHandle) error
|
||||
// Bookmark returns the bookmark from last committed transaction or last finished auto-commit transaction.
|
||||
// Note that if there is an ongoing auto-commit transaction (stream active) the bookmark
|
||||
// from that is not included, use Buffer or Consume to end the stream with a bookmark.
|
||||
// Empty string if no bookmark.
|
||||
Bookmark() string
|
||||
// ServerName returns the name of the remote server
|
||||
ServerName() string
|
||||
// ServerVersion returns the server version on pattern Neo4j/1.2.3
|
||||
ServerVersion() string
|
||||
// IsAlive returns true if the connection is fully functional.
|
||||
// Implementation of this should be passive, no pinging or similair since it might be
|
||||
// called rather frequently.
|
||||
IsAlive() bool
|
||||
// HasFailed returns true if the connection has received a recoverable error (``FAILURE``).
|
||||
HasFailed() bool
|
||||
// Birthdate returns the point in time when this connection was established.
|
||||
Birthdate() time.Time
|
||||
// Reset resets connection to same state as directly after a connect.
|
||||
// Active streams will be discarded and the bookmark will be lost.
|
||||
Reset(ctx context.Context)
|
||||
ForceReset(ctx context.Context) error
|
||||
// Close closes the database connection as well as any underlying connection.
|
||||
// The instance should not be used after being closed.
|
||||
Close(ctx context.Context)
|
||||
// GetRoutingTable gets the routing table for specified database name or the default database if
|
||||
// database equals DefaultDatabase. If the underlying connection does not support
|
||||
// multiple databases, DefaultDatabase should be used as database.
|
||||
// If user impersonation is used (impersonatedUser != "") and default database is used
|
||||
// the database name in the returned routing table will contain the actual name of the
|
||||
// configured default database for the impersonated user. If no impersonation is used
|
||||
// database name in routing table will be set to the name of the requested database.
|
||||
GetRoutingTable(ctx context.Context, context map[string]string, bookmarks []string, database, impersonatedUser string) (*RoutingTable, error)
|
||||
// SetBoltLogger sets Bolt message logger on already initialized connections
|
||||
SetBoltLogger(boltLogger log.BoltLogger)
|
||||
}
|
||||
|
||||
type RoutingTable struct {
|
||||
TimeToLive int
|
||||
DatabaseName string
|
||||
Routers []string
|
||||
Readers []string
|
||||
Writers []string
|
||||
}
|
||||
|
||||
// Marker for using the default database instance.
|
||||
const DefaultDatabase = ""
|
||||
|
||||
// DatabaseSelector allows to select a database if the database server connection supports selecting which database instance on the server
|
||||
// to connect to. Prior to Neo4j 4 there was only one database per server.
|
||||
type DatabaseSelector interface {
|
||||
// SelectDatabase should be called immediately after Reset. Not allowed to call multiple times with different
|
||||
// databases without a reset in-between.
|
||||
SelectDatabase(database string)
|
||||
}
|
44
vendor/github.com/neo4j/neo4j-go-driver/v5/neo4j/internal/packstream/errors.go
generated
vendored
44
vendor/github.com/neo4j/neo4j-go-driver/v5/neo4j/internal/packstream/errors.go
generated
vendored
@ -1,44 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) "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 packstream handles serialization of data sent to database server and
|
||||
// deserialization of data received from database server.
|
||||
package packstream
|
||||
|
||||
type OverflowError struct {
|
||||
msg string
|
||||
}
|
||||
|
||||
func (e *OverflowError) Error() string {
|
||||
return e.msg
|
||||
}
|
||||
|
||||
type IoError struct{}
|
||||
|
||||
func (e *IoError) Error() string {
|
||||
return "IO error"
|
||||
}
|
||||
|
||||
type UnpackError struct {
|
||||
msg string
|
||||
}
|
||||
|
||||
func (e *UnpackError) Error() string {
|
||||
return e.msg
|
||||
}
|
242
vendor/github.com/neo4j/neo4j-go-driver/v5/neo4j/internal/packstream/packer.go
generated
vendored
242
vendor/github.com/neo4j/neo4j-go-driver/v5/neo4j/internal/packstream/packer.go
generated
vendored
@ -1,242 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) "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 packstream
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"math"
|
||||
)
|
||||
|
||||
type Packer struct {
|
||||
buf []byte
|
||||
err error
|
||||
}
|
||||
|
||||
func (p *Packer) Begin(buf []byte) {
|
||||
p.buf = buf
|
||||
p.err = nil
|
||||
}
|
||||
|
||||
func (p *Packer) End() ([]byte, error) {
|
||||
return p.buf, p.err
|
||||
|
||||
}
|
||||
|
||||
func (p *Packer) setErr(err error) {
|
||||
if p.err == nil {
|
||||
p.err = err
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Packer) StructHeader(tag byte, num int) {
|
||||
if num > 0x0f {
|
||||
p.setErr(&OverflowError{msg: "Trying to pack struct with too many fields"})
|
||||
return
|
||||
}
|
||||
|
||||
p.buf = append(p.buf, 0xb0+byte(num), byte(tag))
|
||||
}
|
||||
|
||||
func (p *Packer) Int64(i int64) {
|
||||
switch {
|
||||
case int64(-0x10) <= i && i < int64(0x80):
|
||||
p.buf = append(p.buf, byte(i))
|
||||
case int64(-0x80) <= i && i < int64(-0x10):
|
||||
p.buf = append(p.buf, 0xc8, byte(i))
|
||||
case int64(-0x8000) <= i && i < int64(0x8000):
|
||||
buf := [3]byte{0xc9}
|
||||
binary.BigEndian.PutUint16(buf[1:], uint16(i))
|
||||
p.buf = append(p.buf, buf[:]...)
|
||||
case int64(-0x80000000) <= i && i < int64(0x80000000):
|
||||
buf := [5]byte{0xca}
|
||||
binary.BigEndian.PutUint32(buf[1:], uint32(i))
|
||||
p.buf = append(p.buf, buf[:]...)
|
||||
default:
|
||||
buf := [9]byte{0xcb}
|
||||
binary.BigEndian.PutUint64(buf[1:], uint64(i))
|
||||
p.buf = append(p.buf, buf[:]...)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Packer) Int32(i int32) {
|
||||
p.Int64(int64(i))
|
||||
}
|
||||
|
||||
func (p *Packer) Int16(i int16) {
|
||||
p.Int64(int64(i))
|
||||
}
|
||||
|
||||
func (p *Packer) Int8(i int8) {
|
||||
p.Int64(int64(i))
|
||||
}
|
||||
|
||||
func (p *Packer) Int(i int) {
|
||||
p.Int64(int64(i))
|
||||
}
|
||||
|
||||
func (p *Packer) Uint64(i uint64) {
|
||||
p.checkOverflowInt(i)
|
||||
p.Int64(int64(i))
|
||||
}
|
||||
|
||||
func (p *Packer) Uint32(i uint32) {
|
||||
p.Int64(int64(i))
|
||||
}
|
||||
|
||||
func (p *Packer) Uint16(i uint16) {
|
||||
p.Int64(int64(i))
|
||||
}
|
||||
|
||||
func (p *Packer) Uint8(i uint8) {
|
||||
p.Int64(int64(i))
|
||||
}
|
||||
|
||||
func (p *Packer) Float64(f float64) {
|
||||
buf := [9]byte{0xc1}
|
||||
binary.BigEndian.PutUint64(buf[1:], math.Float64bits(f))
|
||||
p.buf = append(p.buf, buf[:]...)
|
||||
}
|
||||
|
||||
func (p *Packer) Float32(f float32) {
|
||||
p.Float64(float64(f))
|
||||
}
|
||||
|
||||
func (p *Packer) listHeader(ll int, shortOffset, longOffset byte) {
|
||||
l := int64(ll)
|
||||
hdr := make([]byte, 0, 1+4)
|
||||
if l < 0x10 {
|
||||
hdr = append(hdr, shortOffset+byte(l))
|
||||
} else {
|
||||
switch {
|
||||
case l < 0x100:
|
||||
hdr = append(hdr, []byte{longOffset, byte(l)}...)
|
||||
case l < 0x10000:
|
||||
hdr = hdr[:1+2]
|
||||
hdr[0] = longOffset + 1
|
||||
binary.BigEndian.PutUint16(hdr[1:], uint16(l))
|
||||
case l < math.MaxUint32:
|
||||
hdr = hdr[:1+4]
|
||||
hdr[0] = longOffset + 2
|
||||
binary.BigEndian.PutUint32(hdr[1:], uint32(l))
|
||||
default:
|
||||
p.err = &OverflowError{msg: fmt.Sprintf("Trying to pack too large list of size %d ", l)}
|
||||
return
|
||||
}
|
||||
}
|
||||
p.buf = append(p.buf, hdr...)
|
||||
}
|
||||
|
||||
func (p *Packer) String(s string) {
|
||||
p.listHeader(len(s), 0x80, 0xd0)
|
||||
p.buf = append(p.buf, []byte(s)...)
|
||||
}
|
||||
|
||||
func (p *Packer) Strings(ss []string) {
|
||||
p.listHeader(len(ss), 0x90, 0xd4)
|
||||
for _, s := range ss {
|
||||
p.String(s)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Packer) Ints(ii []int) {
|
||||
p.listHeader(len(ii), 0x90, 0xd4)
|
||||
for _, i := range ii {
|
||||
p.Int(i)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Packer) Int64s(ii []int64) {
|
||||
p.listHeader(len(ii), 0x90, 0xd4)
|
||||
for _, i := range ii {
|
||||
p.Int64(i)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Packer) Float64s(ii []float64) {
|
||||
p.listHeader(len(ii), 0x90, 0xd4)
|
||||
for _, i := range ii {
|
||||
p.Float64(i)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Packer) ArrayHeader(l int) {
|
||||
p.listHeader(l, 0x90, 0xd4)
|
||||
}
|
||||
|
||||
func (p *Packer) MapHeader(l int) {
|
||||
p.listHeader(l, 0xa0, 0xd8)
|
||||
}
|
||||
|
||||
func (p *Packer) IntMap(m map[string]int) {
|
||||
p.listHeader(len(m), 0xa0, 0xd8)
|
||||
for k, v := range m {
|
||||
p.String(k)
|
||||
p.Int(v)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Packer) StringMap(m map[string]string) {
|
||||
p.listHeader(len(m), 0xa0, 0xd8)
|
||||
for k, v := range m {
|
||||
p.String(k)
|
||||
p.String(v)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Packer) Bytes(b []byte) {
|
||||
hdr := make([]byte, 0, 1+4)
|
||||
l := int64(len(b))
|
||||
switch {
|
||||
case l < 0x100:
|
||||
hdr = append(hdr, 0xcc, byte(l))
|
||||
case l < 0x10000:
|
||||
hdr = hdr[:1+2]
|
||||
hdr[0] = 0xcd
|
||||
binary.BigEndian.PutUint16(hdr[1:], uint16(l))
|
||||
case l < 0x100000000:
|
||||
hdr = hdr[:1+4]
|
||||
hdr[0] = 0xce
|
||||
binary.BigEndian.PutUint32(hdr[1:], uint32(l))
|
||||
default:
|
||||
p.err = &OverflowError{msg: fmt.Sprintf("Trying to pack too large byte array of size %d", l)}
|
||||
return
|
||||
}
|
||||
p.buf = append(p.buf, hdr...)
|
||||
p.buf = append(p.buf, b...)
|
||||
}
|
||||
|
||||
func (p *Packer) Bool(b bool) {
|
||||
if b {
|
||||
p.buf = append(p.buf, 0xc3)
|
||||
return
|
||||
}
|
||||
p.buf = append(p.buf, 0xc2)
|
||||
}
|
||||
|
||||
func (p *Packer) Nil() {
|
||||
p.buf = append(p.buf, 0xc0)
|
||||
}
|
||||
|
||||
func (p *Packer) checkOverflowInt(i uint64) {
|
||||
if i > math.MaxInt64 {
|
||||
p.err = &OverflowError{msg: "Trying to pack uint64 that doesn't fit into int64"}
|
||||
}
|
||||
}
|
254
vendor/github.com/neo4j/neo4j-go-driver/v5/neo4j/internal/packstream/unpacker.go
generated
vendored
254
vendor/github.com/neo4j/neo4j-go-driver/v5/neo4j/internal/packstream/unpacker.go
generated
vendored
@ -1,254 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) "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 packstream
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"math"
|
||||
)
|
||||
|
||||
const (
|
||||
PackedUndef = iota // Undefined must be zero!
|
||||
PackedInt
|
||||
PackedFloat
|
||||
PackedStr
|
||||
PackedStruct
|
||||
PackedByteArray
|
||||
PackedArray
|
||||
PackedMap
|
||||
PackedNil
|
||||
PackedTrue
|
||||
PackedFalse
|
||||
)
|
||||
|
||||
type Unpacker struct {
|
||||
buf []byte
|
||||
off uint32
|
||||
len uint32
|
||||
mrk marker
|
||||
Err error
|
||||
Curr int // Packed type
|
||||
}
|
||||
|
||||
func (u *Unpacker) Reset(buf []byte) {
|
||||
u.buf = buf
|
||||
u.off = 0
|
||||
u.len = uint32(len(buf))
|
||||
u.Err = nil
|
||||
u.mrk.typ = PackedUndef
|
||||
u.Curr = PackedUndef
|
||||
}
|
||||
|
||||
func (u *Unpacker) setErr(err error) {
|
||||
if u.Err == nil {
|
||||
u.Err = err
|
||||
}
|
||||
}
|
||||
|
||||
func (u *Unpacker) Next() {
|
||||
i := u.pop()
|
||||
u.mrk = markers[i]
|
||||
u.Curr = u.mrk.typ
|
||||
}
|
||||
|
||||
func (u *Unpacker) Len() uint32 {
|
||||
if u.mrk.numlenbytes == 0 {
|
||||
return uint32(u.mrk.shortlen)
|
||||
}
|
||||
return u.readlen(uint32(u.mrk.numlenbytes))
|
||||
}
|
||||
|
||||
func (u *Unpacker) Int() int64 {
|
||||
n := u.mrk.numlenbytes
|
||||
if n == 0 {
|
||||
return int64(u.mrk.shortlen)
|
||||
}
|
||||
|
||||
end := u.off + uint32(n)
|
||||
if end > u.len {
|
||||
u.setErr(&IoError{})
|
||||
return 0
|
||||
}
|
||||
i := int64(0)
|
||||
switch n {
|
||||
case 1:
|
||||
i = int64(int8(u.buf[u.off]))
|
||||
case 2:
|
||||
i = int64(int16(binary.BigEndian.Uint16(u.buf[u.off:])))
|
||||
case 4:
|
||||
i = int64(int32(binary.BigEndian.Uint32(u.buf[u.off:])))
|
||||
case 8:
|
||||
i = int64(binary.BigEndian.Uint64(u.buf[u.off:]))
|
||||
default:
|
||||
u.setErr(&UnpackError{msg: fmt.Sprintf("Illegal int length: %d", n)})
|
||||
return 0
|
||||
}
|
||||
u.off = end
|
||||
return i
|
||||
}
|
||||
|
||||
func (u *Unpacker) Float() float64 {
|
||||
buf := u.read(8)
|
||||
if u.Err != nil {
|
||||
return math.NaN()
|
||||
}
|
||||
return math.Float64frombits(binary.BigEndian.Uint64(buf))
|
||||
}
|
||||
|
||||
func (u *Unpacker) StructTag() byte {
|
||||
return u.pop()
|
||||
}
|
||||
|
||||
func (u *Unpacker) String() string {
|
||||
n := uint32(u.mrk.numlenbytes)
|
||||
if n == 0 {
|
||||
n = uint32(u.mrk.shortlen)
|
||||
} else {
|
||||
n = u.readlen(n)
|
||||
}
|
||||
return string(u.read(n))
|
||||
}
|
||||
|
||||
func (u *Unpacker) Bool() bool {
|
||||
switch u.Curr {
|
||||
case PackedTrue:
|
||||
return true
|
||||
case PackedFalse:
|
||||
return false
|
||||
default:
|
||||
u.setErr(&UnpackError{msg: "Illegal value for bool"})
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (u *Unpacker) ByteArray() []byte {
|
||||
n := u.Len()
|
||||
buf := u.read(n)
|
||||
if u.Err != nil || n == 0 {
|
||||
}
|
||||
out := make([]byte, n)
|
||||
copy(out, buf)
|
||||
return out
|
||||
}
|
||||
|
||||
func (u *Unpacker) pop() byte {
|
||||
if u.off < u.len {
|
||||
x := u.buf[u.off]
|
||||
u.off += 1
|
||||
return x
|
||||
}
|
||||
u.setErr(&IoError{})
|
||||
return 0
|
||||
}
|
||||
|
||||
func (u *Unpacker) read(n uint32) []byte {
|
||||
start := u.off
|
||||
end := u.off + n
|
||||
if end > u.len {
|
||||
u.setErr(&IoError{})
|
||||
return []byte{}
|
||||
}
|
||||
u.off = end
|
||||
return u.buf[start:end]
|
||||
}
|
||||
|
||||
func (u *Unpacker) readlen(n uint32) uint32 {
|
||||
end := u.off + n
|
||||
if end > u.len {
|
||||
u.setErr(&IoError{})
|
||||
return 0
|
||||
}
|
||||
l := uint32(0)
|
||||
switch n {
|
||||
case 1:
|
||||
l = uint32(u.buf[u.off])
|
||||
case 2:
|
||||
l = uint32(binary.BigEndian.Uint16(u.buf[u.off:]))
|
||||
case 4:
|
||||
l = uint32(binary.BigEndian.Uint32(u.buf[u.off:]))
|
||||
default:
|
||||
u.setErr(&UnpackError{msg: fmt.Sprintf("Illegal length: %d (%d)", n, u.Curr)})
|
||||
}
|
||||
u.off = end
|
||||
return l
|
||||
}
|
||||
|
||||
type marker struct {
|
||||
typ int
|
||||
shortlen int8
|
||||
numlenbytes byte
|
||||
}
|
||||
|
||||
var markers [0x100]marker
|
||||
|
||||
func init() {
|
||||
i := 0
|
||||
// Tiny int
|
||||
for ; i < 0x80; i++ {
|
||||
markers[i] = marker{typ: PackedInt, shortlen: int8(i)}
|
||||
}
|
||||
// Tiny string
|
||||
for ; i < 0x90; i++ {
|
||||
markers[i] = marker{typ: PackedStr, shortlen: int8(i - 0x80)}
|
||||
}
|
||||
// Tiny array
|
||||
for ; i < 0xa0; i++ {
|
||||
markers[i] = marker{typ: PackedArray, shortlen: int8(i - 0x90)}
|
||||
}
|
||||
// Tiny map
|
||||
for ; i < 0xb0; i++ {
|
||||
markers[i] = marker{typ: PackedMap, shortlen: int8(i - 0xa0)}
|
||||
}
|
||||
// Struct
|
||||
for ; i < 0xc0; i++ {
|
||||
markers[i] = marker{typ: PackedStruct, shortlen: int8(i - 0xb0)}
|
||||
}
|
||||
|
||||
markers[0xc0] = marker{typ: PackedNil}
|
||||
markers[0xc1] = marker{typ: PackedFloat, numlenbytes: 8}
|
||||
markers[0xc2] = marker{typ: PackedFalse}
|
||||
markers[0xc3] = marker{typ: PackedTrue}
|
||||
|
||||
markers[0xc8] = marker{typ: PackedInt, numlenbytes: 1}
|
||||
markers[0xc9] = marker{typ: PackedInt, numlenbytes: 2}
|
||||
markers[0xca] = marker{typ: PackedInt, numlenbytes: 4}
|
||||
markers[0xcb] = marker{typ: PackedInt, numlenbytes: 8}
|
||||
|
||||
markers[0xcc] = marker{typ: PackedByteArray, numlenbytes: 1}
|
||||
markers[0xcd] = marker{typ: PackedByteArray, numlenbytes: 2}
|
||||
markers[0xce] = marker{typ: PackedByteArray, numlenbytes: 4}
|
||||
|
||||
markers[0xd0] = marker{typ: PackedStr, numlenbytes: 1}
|
||||
markers[0xd1] = marker{typ: PackedStr, numlenbytes: 2}
|
||||
markers[0xd2] = marker{typ: PackedStr, numlenbytes: 4}
|
||||
|
||||
markers[0xd4] = marker{typ: PackedArray, numlenbytes: 1}
|
||||
markers[0xd5] = marker{typ: PackedArray, numlenbytes: 2}
|
||||
markers[0xd6] = marker{typ: PackedArray, numlenbytes: 4}
|
||||
|
||||
markers[0xd8] = marker{typ: PackedMap, numlenbytes: 1}
|
||||
markers[0xd9] = marker{typ: PackedMap, numlenbytes: 2}
|
||||
markers[0xda] = marker{typ: PackedMap, numlenbytes: 4}
|
||||
|
||||
for i = 0xf0; i < 0x100; i++ {
|
||||
markers[i] = marker{typ: PackedInt, shortlen: int8(i - 0x100)}
|
||||
}
|
||||
}
|
@ -1,48 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) "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 pool
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type PoolTimeout struct {
|
||||
err error
|
||||
servers []string
|
||||
}
|
||||
|
||||
func (e *PoolTimeout) Error() string {
|
||||
return fmt.Sprintf("Timeout while waiting for connection to any of [%s]: %s", e.servers, e.err)
|
||||
}
|
||||
|
||||
type PoolFull struct {
|
||||
servers []string
|
||||
}
|
||||
|
||||
func (e *PoolFull) Error() string {
|
||||
return fmt.Sprintf("No idle connections on any of [%s]", e.servers)
|
||||
}
|
||||
|
||||
type PoolClosed struct {
|
||||
}
|
||||
|
||||
func (e *PoolClosed) Error() string {
|
||||
return "Pool closed"
|
||||
}
|
@ -1,411 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) "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 pool handles the database connection pool.
|
||||
package pool
|
||||
|
||||
// Thread safe
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/neo4j/neo4j-go-driver/v5/neo4j/internal/bolt"
|
||||
"github.com/neo4j/neo4j-go-driver/v5/neo4j/internal/db"
|
||||
"sort"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/neo4j/neo4j-go-driver/v5/neo4j/log"
|
||||
)
|
||||
|
||||
type Connect func(context.Context, string, log.BoltLogger) (db.Connection, error)
|
||||
|
||||
type qitem struct {
|
||||
servers []string
|
||||
wakeup chan bool
|
||||
conn db.Connection
|
||||
}
|
||||
|
||||
type Pool struct {
|
||||
maxSize int
|
||||
maxAge time.Duration
|
||||
connect Connect
|
||||
servers map[string]*server
|
||||
serversMut sync.Mutex
|
||||
queueMut sync.Mutex
|
||||
queue list.List
|
||||
now func() time.Time
|
||||
closed bool
|
||||
log log.Logger
|
||||
logId string
|
||||
}
|
||||
|
||||
type serverPenalty struct {
|
||||
name string
|
||||
penalty uint32
|
||||
}
|
||||
|
||||
func New(maxSize int, maxAge time.Duration, connect Connect, logger log.Logger, logId string) *Pool {
|
||||
// Means infinite life, simplifies checking later on
|
||||
if maxAge <= 0 {
|
||||
maxAge = 1<<63 - 1
|
||||
}
|
||||
|
||||
p := &Pool{
|
||||
maxSize: maxSize,
|
||||
maxAge: maxAge,
|
||||
connect: connect,
|
||||
servers: make(map[string]*server),
|
||||
now: time.Now,
|
||||
logId: logId,
|
||||
log: logger,
|
||||
}
|
||||
p.log.Infof(log.Pool, p.logId, "Created")
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *Pool) Close(ctx context.Context) {
|
||||
p.closed = true
|
||||
// Cancel everything in the queue by just emptying at and let all callers timeout
|
||||
p.queueMut.Lock()
|
||||
p.queue.Init()
|
||||
p.queueMut.Unlock()
|
||||
// Go through each server and close all connections to it
|
||||
p.serversMut.Lock()
|
||||
for n, s := range p.servers {
|
||||
s.closeAll(ctx)
|
||||
delete(p.servers, n)
|
||||
}
|
||||
p.serversMut.Unlock()
|
||||
p.log.Infof(log.Pool, p.logId, "Closed")
|
||||
}
|
||||
|
||||
func (p *Pool) anyExistingConnectionsOnServers(serverNames []string) bool {
|
||||
p.serversMut.Lock()
|
||||
defer p.serversMut.Unlock()
|
||||
for _, s := range serverNames {
|
||||
b := p.servers[s]
|
||||
if b != nil {
|
||||
if b.size() > 0 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// For testing
|
||||
func (p *Pool) queueSize() int {
|
||||
p.queueMut.Lock()
|
||||
defer p.queueMut.Unlock()
|
||||
return p.queue.Len()
|
||||
}
|
||||
|
||||
// For testing
|
||||
func (p *Pool) getServers() map[string]*server {
|
||||
p.serversMut.Lock()
|
||||
defer p.serversMut.Unlock()
|
||||
servers := make(map[string]*server)
|
||||
for k, v := range p.servers {
|
||||
servers[k] = v
|
||||
}
|
||||
return servers
|
||||
}
|
||||
|
||||
// Prune all old connection on all the servers, this makes sure that servers
|
||||
// gets removed from the map at some point in time. If there is a noticed
|
||||
// failed connect still active we should wait a while with removal to get
|
||||
// prioritization right.
|
||||
func (p *Pool) CleanUp(ctx context.Context) {
|
||||
p.serversMut.Lock()
|
||||
defer p.serversMut.Unlock()
|
||||
now := p.now()
|
||||
for n, s := range p.servers {
|
||||
s.removeIdleOlderThan(ctx, now, p.maxAge)
|
||||
if s.size() == 0 && !s.hasFailedConnect(now) {
|
||||
delete(p.servers, n)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Pool) tryBorrow(ctx context.Context, serverName string, boltLogger log.BoltLogger) (db.Connection, error) {
|
||||
// For now, lock complete servers map to avoid over connecting but with the downside
|
||||
// that long connect times will block connects to other servers as well. To fix this
|
||||
// we would need to add a pending connect to the server and lock per server.
|
||||
p.serversMut.Lock()
|
||||
defer p.serversMut.Unlock()
|
||||
|
||||
srv := p.servers[serverName]
|
||||
if srv != nil {
|
||||
// Try to get an existing idle connection
|
||||
if c := srv.getIdle(); c != nil {
|
||||
c.SetBoltLogger(boltLogger)
|
||||
return c, nil
|
||||
}
|
||||
if srv.size() >= p.maxSize {
|
||||
return nil, &PoolFull{servers: []string{serverName}}
|
||||
}
|
||||
} else {
|
||||
// Make sure that there is a server in the map
|
||||
srv = &server{}
|
||||
p.servers[serverName] = srv
|
||||
}
|
||||
|
||||
// No idle connection, try to connect
|
||||
p.log.Infof(log.Pool, p.logId, "Connecting to %s", serverName)
|
||||
c, err := p.connect(ctx, serverName, boltLogger)
|
||||
if err != nil {
|
||||
// Failed to connect, keep track that it was bad for a while
|
||||
srv.notifyFailedConnect(p.now())
|
||||
p.log.Warnf(log.Pool, p.logId, "Failed to connect to %s: %s", serverName, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Ok, got a connection, register the connection
|
||||
srv.registerBusy(c)
|
||||
srv.notifySuccesfulConnect()
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (p *Pool) getPenaltiesForServers(ctx context.Context, serverNames []string) []serverPenalty {
|
||||
p.serversMut.Lock()
|
||||
defer p.serversMut.Unlock()
|
||||
|
||||
// Retrieve penalty for each server
|
||||
penalties := make([]serverPenalty, len(serverNames))
|
||||
now := p.now()
|
||||
for i, n := range serverNames {
|
||||
s := p.servers[n]
|
||||
penalties[i].name = n
|
||||
if s != nil {
|
||||
// Make sure that we don't get a too old connection
|
||||
s.removeIdleOlderThan(ctx, now, p.maxAge)
|
||||
penalties[i].penalty = s.calculatePenalty(now)
|
||||
} else {
|
||||
penalties[i].penalty = newConnectionPenalty
|
||||
}
|
||||
}
|
||||
return penalties
|
||||
}
|
||||
|
||||
func (p *Pool) tryAnyIdle(serverNames []string) db.Connection {
|
||||
p.serversMut.Lock()
|
||||
defer p.serversMut.Unlock()
|
||||
for _, serverName := range serverNames {
|
||||
srv := p.servers[serverName]
|
||||
if srv != nil {
|
||||
// Try to get an existing idle connection
|
||||
conn := srv.getIdle()
|
||||
if conn != nil {
|
||||
return conn
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Borrow tries to borrow an existing database connection or tries to create a new one
|
||||
// if none exists. The wait flag indicates if the caller wants to wait for a connection
|
||||
// to be returned if there aren't any idle connection available.
|
||||
func (p *Pool) Borrow(ctx context.Context, serverNames []string, wait bool, boltLogger log.BoltLogger) (db.Connection, error) {
|
||||
if p.closed {
|
||||
return nil, &PoolClosed{}
|
||||
}
|
||||
p.log.Debugf(log.Pool, p.logId, "Trying to borrow connection from %s", serverNames)
|
||||
|
||||
// Retrieve penalty for each server
|
||||
penalties := p.getPenaltiesForServers(ctx, serverNames)
|
||||
// Sort server penalties by lowest penalty
|
||||
sort.Slice(penalties, func(i, j int) bool {
|
||||
return penalties[i].penalty < penalties[j].penalty
|
||||
})
|
||||
|
||||
var err error
|
||||
var conn db.Connection
|
||||
for _, s := range penalties {
|
||||
conn, err = p.tryBorrow(ctx, s.name, boltLogger)
|
||||
if err == nil {
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
if bolt.IsTimeoutError(err) {
|
||||
p.log.Warnf(log.Pool, p.logId, "Borrow time-out")
|
||||
return nil, &PoolTimeout{servers: serverNames, err: err}
|
||||
}
|
||||
}
|
||||
|
||||
// If there are no connections for any of the servers, there is no point in waiting for anything
|
||||
// to be returned.
|
||||
if !p.anyExistingConnectionsOnServers(serverNames) {
|
||||
p.log.Warnf(log.Pool, p.logId, "No server connection available to any of %v", serverNames)
|
||||
if err == nil {
|
||||
err = errors.New(fmt.Sprintf("No server connection available to any of %v", serverNames))
|
||||
}
|
||||
// Intentionally return last error from last connection attempt to make it easier to
|
||||
// see connection errors for users.
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !wait {
|
||||
return nil, &PoolFull{servers: serverNames}
|
||||
}
|
||||
|
||||
// Wait for a matching connection to be returned from another thread.
|
||||
p.queueMut.Lock()
|
||||
// Ok, now that we own the queue we can add the item there but between getting the lock
|
||||
// and above check for an existing connection another thread might have returned a connection
|
||||
// so check again to avoid potentially starving this thread.
|
||||
conn = p.tryAnyIdle(serverNames)
|
||||
if conn != nil {
|
||||
p.queueMut.Unlock()
|
||||
return conn, nil
|
||||
}
|
||||
// Add a waiting request to the queue and unlock the queue to let other threads that returns
|
||||
// their connections access the queue.
|
||||
q := &qitem{
|
||||
servers: serverNames,
|
||||
wakeup: make(chan bool),
|
||||
}
|
||||
e := p.queue.PushBack(q)
|
||||
p.queueMut.Unlock()
|
||||
|
||||
p.log.Warnf(log.Pool, p.logId, "Borrow queued")
|
||||
// Wait for either a wake-up signal that indicates that we got a connection or a timeout.
|
||||
select {
|
||||
case <-q.wakeup:
|
||||
return q.conn, nil
|
||||
case <-ctx.Done():
|
||||
p.queueMut.Lock()
|
||||
p.queue.Remove(e)
|
||||
p.queueMut.Unlock()
|
||||
if q.conn != nil {
|
||||
return q.conn, nil
|
||||
}
|
||||
p.log.Warnf(log.Pool, p.logId, "Borrow time-out")
|
||||
return nil, &PoolTimeout{err: ctx.Err(), servers: serverNames}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Pool) unreg(ctx context.Context, serverName string, c db.Connection,
|
||||
now time.Time) {
|
||||
p.serversMut.Lock()
|
||||
defer p.serversMut.Unlock()
|
||||
|
||||
defer func() {
|
||||
// Close connection in another thread to avoid potential long blocking operation during close.
|
||||
go c.Close(ctx)
|
||||
}()
|
||||
|
||||
server := p.servers[serverName]
|
||||
// Check for strange condition of not finding the server.
|
||||
if server == nil {
|
||||
p.log.Warnf(log.Pool, p.logId, "Server %s not found", serverName)
|
||||
return
|
||||
}
|
||||
|
||||
server.unregisterBusy(c)
|
||||
if server.size() == 0 && !server.hasFailedConnect(now) {
|
||||
delete(p.servers, serverName)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Pool) removeIdleOlderThanOnServer(ctx context.Context, serverName string, now time.Time, maxAge time.Duration) {
|
||||
p.serversMut.Lock()
|
||||
defer p.serversMut.Unlock()
|
||||
server := p.servers[serverName]
|
||||
if server == nil {
|
||||
return
|
||||
}
|
||||
server.removeIdleOlderThan(ctx, now, maxAge)
|
||||
}
|
||||
|
||||
func (p *Pool) Return(ctx context.Context, c db.Connection) {
|
||||
if p.closed {
|
||||
p.log.Warnf(log.Pool, p.logId, "Trying to return connection to closed pool")
|
||||
return
|
||||
}
|
||||
|
||||
// Get the name of the server that the connection belongs to.
|
||||
serverName := c.ServerName()
|
||||
isAlive := c.IsAlive()
|
||||
p.log.Debugf(log.Pool, p.logId, "Returning connection to %s {alive:%t}", serverName, isAlive)
|
||||
|
||||
// If the connection is dead, remove all other idle connections on the same server that older
|
||||
// or of the same age as the dead connection, otherwise perform normal cleanup of old connections
|
||||
maxAge := p.maxAge
|
||||
now := p.now()
|
||||
age := now.Sub(c.Birthdate())
|
||||
if !isAlive {
|
||||
// Since this connection has died all other connections that connected before this one
|
||||
// might also be bad, remove the idle ones.
|
||||
if age < maxAge {
|
||||
maxAge = age
|
||||
}
|
||||
}
|
||||
p.removeIdleOlderThanOnServer(ctx, serverName, now, maxAge)
|
||||
|
||||
// Prepare connection for being used by someone else if is alive.
|
||||
// Since reset could find the connection to be in a bad state or non-recoverable state,
|
||||
// make sure again that it really is alive.
|
||||
if isAlive {
|
||||
c.Reset(ctx)
|
||||
isAlive = c.IsAlive()
|
||||
}
|
||||
|
||||
c.SetBoltLogger(nil)
|
||||
|
||||
// Shouldn't return a too old or dead connection back to the pool
|
||||
if !isAlive || age >= p.maxAge {
|
||||
p.unreg(ctx, serverName, c, now)
|
||||
p.log.Infof(log.Pool, p.logId, "Unregistering dead or too old connection to %s", serverName)
|
||||
// Returning here could cause a waiting thread to wait until it times out, to do it
|
||||
// properly we could wake up threads that waits on the server and wake them up if there
|
||||
// are no more connections to wait for.
|
||||
return
|
||||
}
|
||||
|
||||
// Check if there is anyone in the queue waiting for a connection to this server.
|
||||
p.queueMut.Lock()
|
||||
for e := p.queue.Front(); e != nil; e = e.Next() {
|
||||
qitem := e.Value.(*qitem)
|
||||
// Check requested servers
|
||||
for _, rserver := range qitem.servers {
|
||||
if rserver == serverName {
|
||||
qitem.conn = c
|
||||
p.queue.Remove(e)
|
||||
p.queueMut.Unlock()
|
||||
qitem.wakeup <- true
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
p.queueMut.Unlock()
|
||||
|
||||
// Just put it back in the list of idle connections for this server
|
||||
p.serversMut.Lock()
|
||||
defer p.serversMut.Unlock()
|
||||
server := p.servers[serverName]
|
||||
if server != nil { // Strange when server not found
|
||||
server.returnBusy(c)
|
||||
} else {
|
||||
p.log.Warnf(log.Pool, p.logId, "Server %s not found", serverName)
|
||||
}
|
||||
}
|
@ -1,166 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) "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 pool
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
"context"
|
||||
"github.com/neo4j/neo4j-go-driver/v5/neo4j/internal/db"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Represents a server with a number of connections that either is in use (borrowed) or
|
||||
// is ready for use.
|
||||
// Not thread safe
|
||||
type server struct {
|
||||
idle list.List
|
||||
busy list.List
|
||||
failedConnectAt time.Time
|
||||
roundRobin uint32
|
||||
}
|
||||
|
||||
var sharedRoundRobin uint32
|
||||
|
||||
const rememberFailedConnectDuration = 3 * time.Minute
|
||||
|
||||
// Returns a idle connection if any
|
||||
func (s *server) getIdle() db.Connection {
|
||||
// Remove from idle list and add to busy list
|
||||
e := s.idle.Front()
|
||||
if e != nil {
|
||||
c := s.idle.Remove(e)
|
||||
s.busy.PushFront(c)
|
||||
// Update round-robin counter every time we give away a connection and keep track
|
||||
// of our own round-robin index
|
||||
s.roundRobin = atomic.AddUint32(&sharedRoundRobin, 1)
|
||||
return c.(db.Connection)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *server) notifyFailedConnect(now time.Time) {
|
||||
s.failedConnectAt = now
|
||||
}
|
||||
|
||||
func (s *server) notifySuccesfulConnect() {
|
||||
s.failedConnectAt = time.Time{}
|
||||
}
|
||||
|
||||
func (s *server) hasFailedConnect(now time.Time) bool {
|
||||
if s.failedConnectAt.IsZero() {
|
||||
return false
|
||||
}
|
||||
return now.Sub(s.failedConnectAt) < rememberFailedConnectDuration
|
||||
}
|
||||
|
||||
const newConnectionPenalty = uint32(1 << 8)
|
||||
|
||||
// Calculates a penalty value for how this server compares to other servers
|
||||
// when there is more than one server to choose from. The lower penalty the better choice.
|
||||
func (s *server) calculatePenalty(now time.Time) uint32 {
|
||||
penalty := uint32(0)
|
||||
|
||||
// If a connect to the server has failed recently, add a penalty
|
||||
if s.hasFailedConnect(now) {
|
||||
penalty = 1 << 31
|
||||
}
|
||||
// The more busy connections, the higher penalty
|
||||
numBusy := uint32(s.busy.Len())
|
||||
if numBusy > 0xff {
|
||||
numBusy = 0xff
|
||||
}
|
||||
penalty |= numBusy << 16
|
||||
// If there are no idle connections, add a penalty as the cost of connect would
|
||||
// add to the transaction time
|
||||
if s.idle.Len() == 0 {
|
||||
penalty |= newConnectionPenalty
|
||||
}
|
||||
// Use last round-robin value as lowest priority penalty, so when all other is equal we will
|
||||
// make sure to spread usage among the servers. And yes it will wrap around once in a while
|
||||
// but since number of busy servers weights higher it will even out pretty fast.
|
||||
penalty |= (s.roundRobin & 0xff)
|
||||
|
||||
return penalty
|
||||
}
|
||||
|
||||
// Returns a busy connection, makes it idle
|
||||
func (s *server) returnBusy(c db.Connection) {
|
||||
s.unregisterBusy(c)
|
||||
s.idle.PushFront(c)
|
||||
}
|
||||
|
||||
// Number of idle connections
|
||||
func (s server) numIdle() int {
|
||||
return s.idle.Len()
|
||||
}
|
||||
|
||||
// Adds a db to busy list
|
||||
func (s *server) registerBusy(c db.Connection) {
|
||||
// Update round-robin to indicate when this server was last used.
|
||||
s.roundRobin = atomic.AddUint32(&sharedRoundRobin, 1)
|
||||
s.busy.PushFront(c)
|
||||
}
|
||||
|
||||
func (s *server) unregisterBusy(c db.Connection) {
|
||||
found := false
|
||||
for e := s.busy.Front(); e != nil && !found; e = e.Next() {
|
||||
x := e.Value.(db.Connection)
|
||||
found = x == c
|
||||
if found {
|
||||
s.busy.Remove(e)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *server) size() int {
|
||||
return s.busy.Len() + s.idle.Len()
|
||||
}
|
||||
|
||||
func (s *server) removeIdleOlderThan(ctx context.Context, now time.Time, maxAge time.Duration) {
|
||||
e := s.idle.Front()
|
||||
for e != nil {
|
||||
n := e.Next()
|
||||
c := e.Value.(db.Connection)
|
||||
|
||||
age := now.Sub(c.Birthdate())
|
||||
if age >= maxAge {
|
||||
s.idle.Remove(e)
|
||||
go c.Close(ctx)
|
||||
}
|
||||
|
||||
e = n
|
||||
}
|
||||
}
|
||||
|
||||
func (s *server) closeAll(ctx context.Context) {
|
||||
closeAndEmptyConnections(ctx, s.idle)
|
||||
// Closing the busy connections could mean here that we do close from another thread.
|
||||
closeAndEmptyConnections(ctx, s.busy)
|
||||
}
|
||||
|
||||
func closeAndEmptyConnections(ctx context.Context, l list.List) {
|
||||
for e := l.Front(); e != nil; e = e.Next() {
|
||||
c := e.Value.(db.Connection)
|
||||
c.Close(ctx)
|
||||
}
|
||||
l.Init()
|
||||
}
|
@ -1,81 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) "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 racingio
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
)
|
||||
|
||||
type RacingReader interface {
|
||||
Read(ctx context.Context, bytes []byte) (int, error)
|
||||
ReadFull(ctx context.Context, bytes []byte) (int, error)
|
||||
}
|
||||
|
||||
func NewRacingReader(reader io.Reader) RacingReader {
|
||||
return &racingReader{reader: reader}
|
||||
}
|
||||
|
||||
type racingReader struct {
|
||||
reader io.Reader
|
||||
}
|
||||
|
||||
func (rr *racingReader) Read(ctx context.Context, bytes []byte) (int, error) {
|
||||
return rr.race(ctx, bytes, read)
|
||||
}
|
||||
|
||||
func (rr *racingReader) ReadFull(ctx context.Context, bytes []byte) (int, error) {
|
||||
return rr.race(ctx, bytes, readFull)
|
||||
}
|
||||
|
||||
func (rr *racingReader) race(ctx context.Context, bytes []byte, readFn func(io.Reader, []byte) (int, error)) (int, error) {
|
||||
if err := ctx.Err(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
resultChan := make(chan *ioResult, 1)
|
||||
defer close(resultChan)
|
||||
go func() {
|
||||
n, err := readFn(rr.reader, bytes)
|
||||
defer func() {
|
||||
// When the read operation completes, the outer function may have returned already.
|
||||
// In that situation, the channel will have been closed and the result emission will crash.
|
||||
// Let's just swallow the panic that may happen and ignore it
|
||||
_ = recover()
|
||||
}()
|
||||
resultChan <- &ioResult{
|
||||
n: n,
|
||||
err: err,
|
||||
}
|
||||
}()
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return 0, ctx.Err()
|
||||
case result := <-resultChan:
|
||||
return result.n, result.err
|
||||
}
|
||||
}
|
||||
|
||||
func read(reader io.Reader, bytes []byte) (int, error) {
|
||||
return reader.Read(bytes)
|
||||
}
|
||||
|
||||
func readFull(reader io.Reader, bytes []byte) (int, error) {
|
||||
return io.ReadFull(reader, bytes)
|
||||
}
|
@ -1,69 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) "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 racingio
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
)
|
||||
|
||||
type RacingWriter interface {
|
||||
Write(ctx context.Context, bytes []byte) (int, error)
|
||||
}
|
||||
|
||||
func NewRacingWriter(writer io.Writer) RacingWriter {
|
||||
return &racingWriter{writer: writer}
|
||||
}
|
||||
|
||||
type racingWriter struct {
|
||||
writer io.Writer
|
||||
}
|
||||
|
||||
type ioResult struct {
|
||||
n int
|
||||
err error
|
||||
}
|
||||
|
||||
func (rw *racingWriter) Write(ctx context.Context, bytes []byte) (int, error) {
|
||||
if err := ctx.Err(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
resultChan := make(chan *ioResult, 1)
|
||||
defer close(resultChan)
|
||||
go func() {
|
||||
n, err := rw.writer.Write(bytes)
|
||||
defer func() {
|
||||
// When the write operation completes, the outer function may have returned already.
|
||||
// In that situation, the channel will have been closed and the result emission will crash.
|
||||
// Let's just swallow the panic that may happen and ignore it
|
||||
_ = recover()
|
||||
}()
|
||||
resultChan <- &ioResult{
|
||||
n: n,
|
||||
err: err,
|
||||
}
|
||||
}()
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return 0, ctx.Err()
|
||||
case result := <-resultChan:
|
||||
return result.n, result.err
|
||||
}
|
||||
}
|
@ -1,156 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) "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 retry handles retry operations.
|
||||
package retry
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
idb "github.com/neo4j/neo4j-go-driver/v5/neo4j/internal/db"
|
||||
"time"
|
||||
|
||||
"github.com/neo4j/neo4j-go-driver/v5/neo4j/db"
|
||||
"github.com/neo4j/neo4j-go-driver/v5/neo4j/log"
|
||||
)
|
||||
|
||||
type Router interface {
|
||||
Invalidate(database string)
|
||||
}
|
||||
|
||||
type CommitFailedDeadError struct {
|
||||
inner error
|
||||
}
|
||||
|
||||
func (e *CommitFailedDeadError) Error() string {
|
||||
return fmt.Sprintf("Connection lost during commit: %s", e.inner)
|
||||
}
|
||||
|
||||
type State struct {
|
||||
LastErrWasRetryable bool
|
||||
LastErr error
|
||||
stop bool
|
||||
Errs []error
|
||||
Causes []string
|
||||
MaxTransactionRetryTime time.Duration
|
||||
Log log.Logger
|
||||
LogName string
|
||||
LogId string
|
||||
Now func() time.Time
|
||||
Sleep func(time.Duration)
|
||||
Throttle Throttler
|
||||
MaxDeadConnections int
|
||||
Router Router
|
||||
DatabaseName string
|
||||
|
||||
start time.Time
|
||||
cause string
|
||||
deadErrors int
|
||||
skipSleep bool
|
||||
}
|
||||
|
||||
func (s *State) OnFailure(conn idb.Connection, err error, isCommitting bool) {
|
||||
s.LastErr = err
|
||||
s.cause = ""
|
||||
s.skipSleep = false
|
||||
|
||||
// Check timeout
|
||||
if s.start.IsZero() {
|
||||
s.start = s.Now()
|
||||
}
|
||||
if s.Now().Sub(s.start) > s.MaxTransactionRetryTime {
|
||||
s.stop = true
|
||||
s.cause = "Timeout"
|
||||
return
|
||||
}
|
||||
|
||||
// Reset after determined to evaluate this error
|
||||
s.LastErrWasRetryable = false
|
||||
|
||||
// Failed to connect
|
||||
if conn == nil {
|
||||
s.LastErrWasRetryable = true
|
||||
s.cause = "No available connection"
|
||||
return
|
||||
}
|
||||
|
||||
// Check if the connection died, if it died during commit it is not safe to retry.
|
||||
if !conn.IsAlive() {
|
||||
if isCommitting {
|
||||
s.stop = true
|
||||
// The error is most probably io.EOF so enrich the error
|
||||
// to make this error more recognizable.
|
||||
s.LastErr = &CommitFailedDeadError{inner: s.LastErr}
|
||||
return
|
||||
}
|
||||
|
||||
s.deadErrors += 1
|
||||
s.stop = s.deadErrors > s.MaxDeadConnections
|
||||
s.LastErrWasRetryable = true
|
||||
s.cause = "Connection lost"
|
||||
s.skipSleep = true
|
||||
return
|
||||
}
|
||||
|
||||
if dbErr, isDbErr := err.(*db.Neo4jError); isDbErr {
|
||||
if dbErr.IsRetriableCluster() {
|
||||
// Force routing tables to be updated before trying again
|
||||
s.Router.Invalidate(s.DatabaseName)
|
||||
s.cause = "Cluster error"
|
||||
s.LastErrWasRetryable = true
|
||||
return
|
||||
}
|
||||
|
||||
if dbErr.IsRetriableTransient() {
|
||||
s.cause = "Transient error"
|
||||
s.LastErrWasRetryable = true
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
s.stop = true
|
||||
}
|
||||
|
||||
func (s *State) Continue() bool {
|
||||
// No error happened yet
|
||||
if !s.stop && s.LastErr == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
// Track the error and the cause
|
||||
s.Errs = append(s.Errs, s.LastErr)
|
||||
if s.cause != "" {
|
||||
s.Causes = append(s.Causes, s.cause)
|
||||
}
|
||||
|
||||
// Retry after optional sleep
|
||||
if !s.stop {
|
||||
if s.skipSleep {
|
||||
s.Log.Debugf(s.LogName, s.LogId, "Retrying transaction (%s): %s", s.cause, s.LastErr)
|
||||
} else {
|
||||
s.Throttle = s.Throttle.next()
|
||||
sleepTime := s.Throttle.delay()
|
||||
s.Log.Debugf(s.LogName, s.LogId,
|
||||
"Retrying transaction (%s): %s [after %s]", s.cause, s.LastErr, sleepTime)
|
||||
s.Sleep(sleepTime)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
@ -1,38 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) "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 retry
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Throttler time.Duration
|
||||
|
||||
func (t Throttler) next() Throttler {
|
||||
delay := time.Duration(t)
|
||||
const delayJitter = 0.2
|
||||
jitter := float64(delay) * delayJitter
|
||||
return Throttler(delay - time.Duration(jitter) + time.Duration(2*jitter*rand.Float64()))
|
||||
}
|
||||
|
||||
func (t Throttler) delay() time.Duration {
|
||||
return time.Duration(t)
|
||||
}
|
@ -1,47 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) "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 router
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/neo4j/neo4j-go-driver/v5/neo4j/db"
|
||||
)
|
||||
|
||||
type ReadRoutingTableError struct {
|
||||
err error
|
||||
server string
|
||||
}
|
||||
|
||||
func (e *ReadRoutingTableError) Error() string {
|
||||
if e.err != nil || len(e.server) > 0 {
|
||||
return fmt.Sprintf("Unable to retrieve routing table from %s: %s", e.server, e.err)
|
||||
}
|
||||
return "Unable to retrieve routing table, no router provided"
|
||||
}
|
||||
|
||||
func wrapError(server string, err error) error {
|
||||
// Preserve error originating from the database, wrap other errors
|
||||
_, isNeo4jErr := err.(*db.Neo4jError)
|
||||
if isNeo4jErr {
|
||||
return err
|
||||
}
|
||||
return &ReadRoutingTableError{server: server, err: err}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue