- update dorm

master
李光春 2 years ago
parent e4e9bdb08d
commit e3e45542d1

@ -43,8 +43,6 @@ require (
github.com/mitchellh/mapstructure v1.5.0
github.com/mvdan/xurls v1.1.0
github.com/natefinch/lumberjack v2.0.0+incompatible
github.com/neo4j/neo4j-go-driver/v4 v4.4.3
github.com/neo4j/neo4j-go-driver/v5 v5.0.0-preview
github.com/nilorg/sdk v0.0.0-20220617065147-3001fb840741
github.com/patrickmn/go-cache v2.1.0+incompatible
github.com/qiniu/go-sdk/v7 v7.13.0
@ -107,6 +105,7 @@ require (
github.com/edsrzf/mmap-go v1.0.0 // indirect
github.com/emicklei/go-restful v2.9.5+incompatible // indirect
github.com/fatih/color v1.13.0 // indirect
github.com/fsnotify/fsnotify v1.5.1 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/glebarez/go-sqlite v1.17.3 // indirect
github.com/go-logfmt/logfmt v0.5.1 // indirect

@ -294,7 +294,6 @@ github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LB
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-stack/stack v1.8.1 h1:ntEHSVwIt7PNXNpgPmVfMrNhLtgjlmnZha2kOpuRiDw=
github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP3XYfe4=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/go-zookeeper/zk v1.0.2 h1:4mx0EYENAdX/B/rbunjlt5+4RTA/a9SMHBRuSKdGxPM=
github.com/go-zookeeper/zk v1.0.2/go.mod h1:nOB03cncLtlp4t+UAkGSV+9beXP/akpekBwL+UX1Qcw=
github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd/go.mod h1:4duuawTqi2wkkpB4ePgWMaai6/Kc6WEz83bhFwpHzj0=
@ -701,16 +700,11 @@ github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzE
github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
github.com/neo4j/neo4j-go-driver/v4 v4.4.3 h1:uIP106GZwdWjbJY6jxRavHVPeUWKMTcR+cq255EAjXk=
github.com/neo4j/neo4j-go-driver/v4 v4.4.3/go.mod h1:NexOfrm4c317FVjekrhVV8pHBXgtMG5P6GeweJWCyo4=
github.com/neo4j/neo4j-go-driver/v5 v5.0.0-preview h1:TAgaXsSlqRCQVGLn0RAQALOJRxEZ3/SNvvTFG0hBUyk=
github.com/neo4j/neo4j-go-driver/v5 v5.0.0-preview/go.mod h1:B9SY/FQUDv30fwNhXiNO2mo8QzouUKR3DhVH7w0yXaE=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/nilorg/sdk v0.0.0-20220617065147-3001fb840741 h1:oqg84OxQrU/bdn22BOceI5ehavqCY3GsRUyp74UM8Cw=
github.com/nilorg/sdk v0.0.0-20220617065147-3001fb840741/go.mod h1:X1swpPdqguAZaBDoEPyEWHSsJii0YQ1o+3piMv6W3JU=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs=
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
github.com/oklog/ulid/v2 v2.0.2 h1:r4fFzBm+bv0wNKNh5eXTwU7i85y5x+uwkxCUTNVQqLc=
@ -723,16 +717,13 @@ github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
github.com/onsi/ginkgo v1.15.0/go.mod h1:hF8qUzuuC8DJGygJH3726JnCZX4MYbRB8yFfISqnKUg=
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/onsi/gomega v1.10.5/go.mod h1:gza4q3jKQJijlu05nKWRCW/GavJumGt8aNRxWg7mt48=
github.com/onsi/gomega v1.16.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
github.com/onsi/gomega v1.19.0 h1:4ieX6qQjPP/BfC3mpsAtIGGlxTWPeA3Inl/7DtXw1tw=
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
@ -1111,10 +1102,8 @@ golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
@ -1223,7 +1212,6 @@ golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20210902050250-f475640dd07b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211020174200-9d6173849985/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=

@ -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

@ -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)})
}
}

@ -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
}

@ -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)
}

@ -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
}

@ -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"}
}
}

@ -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] = &notification{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

@ -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)})
}
}

@ -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
}

@ -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)
}

@ -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
}

@ -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"}
}
}

@ -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…
Cancel
Save