From 509d4592d17b071e4eb6c897c6826237877270c4 Mon Sep 17 00:00:00 2001 From: Wangjunqi123 Date: Thu, 19 Oct 2023 17:50:34 +0800 Subject: [PATCH] add neo4j funtion --- server/agentmanager/topoclient.go | 4 - server/dao/neo4j.go | 113 ++ server/go.mod | 3 +- server/go.sum | 72 +- .../neo4j/neo4j-go-driver/v4/LICENSE | 201 ++++ .../neo4j/neo4j-go-driver/v4/neo4j/aliases.go | 98 ++ .../neo4j-go-driver/v4/neo4j/auth_tokens.go | 104 ++ .../neo4j/neo4j-go-driver/v4/neo4j/config.go | 180 +++ .../neo4j-go-driver/v4/neo4j/consolelogger.go | 52 + .../neo4j-go-driver/v4/neo4j/db/connection.go | 126 ++ .../neo4j-go-driver/v4/neo4j/db/errors.go | 134 +++ .../neo4j-go-driver/v4/neo4j/db/record.go | 51 + .../neo4j-go-driver/v4/neo4j/db/summary.go | 142 +++ .../neo4j-go-driver/v4/neo4j/dbtype/graph.go | 46 + .../v4/neo4j/dbtype/spatial.go | 49 + .../v4/neo4j/dbtype/temporal.go | 89 ++ .../neo4j-go-driver/v4/neo4j/directrouter.go | 49 + .../neo4j/neo4j-go-driver/v4/neo4j/driver.go | 305 +++++ .../neo4j/neo4j-go-driver/v4/neo4j/error.go | 156 +++ .../v4/neo4j/internal/bolt/bolt3.go | 776 ++++++++++++ .../v4/neo4j/internal/bolt/bolt4.go | 1046 +++++++++++++++++ .../v4/neo4j/internal/bolt/bolt_logging.go | 119 ++ .../v4/neo4j/internal/bolt/chunker.go | 107 ++ .../v4/neo4j/internal/bolt/connect.go | 105 ++ .../v4/neo4j/internal/bolt/dechunker.go | 87 ++ .../v4/neo4j/internal/bolt/hydrator.go | 899 ++++++++++++++ .../v4/neo4j/internal/bolt/incoming.go | 41 + .../v4/neo4j/internal/bolt/messages.go | 41 + .../v4/neo4j/internal/bolt/outgoing.go | 443 +++++++ .../neo4j/internal/bolt/parseroutingtable.go | 74 ++ .../v4/neo4j/internal/bolt/path.go | 81 ++ .../v4/neo4j/internal/bolt/stream.go | 153 +++ .../v4/neo4j/internal/connector/connector.go | 125 ++ .../v4/neo4j/internal/packstream/errors.go | 44 + .../v4/neo4j/internal/packstream/packer.go | 242 ++++ .../v4/neo4j/internal/packstream/unpacker.go | 254 ++++ .../v4/neo4j/internal/pool/errors.go | 48 + .../v4/neo4j/internal/pool/pool.go | 419 +++++++ .../v4/neo4j/internal/pool/server.go | 166 +++ .../v4/neo4j/internal/racingio/reader.go | 93 ++ .../v4/neo4j/internal/retry/state.go | 155 +++ .../v4/neo4j/internal/retry/throttler.go | 38 + .../v4/neo4j/internal/router/errors.go | 47 + .../v4/neo4j/internal/router/readtable.go | 60 + .../v4/neo4j/internal/router/router.go | 242 ++++ .../v4/neo4j/log/bolt_logger.go | 34 + .../neo4j-go-driver/v4/neo4j/log/console.go | 69 ++ .../neo4j-go-driver/v4/neo4j/log/logger.go | 68 ++ .../neo4j-go-driver/v4/neo4j/log/void.go | 35 + .../neo4j/neo4j-go-driver/v4/neo4j/result.go | 168 +++ .../v4/neo4j/result_helpers.go | 80 ++ .../neo4j-go-driver/v4/neo4j/resultsummary.go | 504 ++++++++ .../neo4j/neo4j-go-driver/v4/neo4j/session.go | 529 +++++++++ .../neo4j-go-driver/v4/neo4j/transaction.go | 131 +++ .../v4/neo4j/transaction_config.go | 70 ++ .../neo4j-go-driver/v4/neo4j/useragent.go | 22 + server/vendor/modules.txt | 15 +- 57 files changed, 9597 insertions(+), 7 deletions(-) create mode 100644 server/dao/neo4j.go create mode 100644 server/vendor/github.com/neo4j/neo4j-go-driver/v4/LICENSE create mode 100644 server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/aliases.go create mode 100644 server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/auth_tokens.go create mode 100644 server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/config.go create mode 100644 server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/consolelogger.go create mode 100644 server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/db/connection.go create mode 100644 server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/db/errors.go create mode 100644 server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/db/record.go create mode 100644 server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/db/summary.go create mode 100644 server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/dbtype/graph.go create mode 100644 server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/dbtype/spatial.go create mode 100644 server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/dbtype/temporal.go create mode 100644 server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/directrouter.go create mode 100644 server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/driver.go create mode 100644 server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/error.go create mode 100644 server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/bolt/bolt3.go create mode 100644 server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/bolt/bolt4.go create mode 100644 server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/bolt/bolt_logging.go create mode 100644 server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/bolt/chunker.go create mode 100644 server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/bolt/connect.go create mode 100644 server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/bolt/dechunker.go create mode 100644 server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/bolt/hydrator.go create mode 100644 server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/bolt/incoming.go create mode 100644 server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/bolt/messages.go create mode 100644 server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/bolt/outgoing.go create mode 100644 server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/bolt/parseroutingtable.go create mode 100644 server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/bolt/path.go create mode 100644 server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/bolt/stream.go create mode 100644 server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/connector/connector.go create mode 100644 server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/packstream/errors.go create mode 100644 server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/packstream/packer.go create mode 100644 server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/packstream/unpacker.go create mode 100644 server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/pool/errors.go create mode 100644 server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/pool/pool.go create mode 100644 server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/pool/server.go create mode 100644 server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/racingio/reader.go create mode 100644 server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/retry/state.go create mode 100644 server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/retry/throttler.go create mode 100644 server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/router/errors.go create mode 100644 server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/router/readtable.go create mode 100644 server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/router/router.go create mode 100644 server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/log/bolt_logger.go create mode 100644 server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/log/console.go create mode 100644 server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/log/logger.go create mode 100644 server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/log/void.go create mode 100644 server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/result.go create mode 100644 server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/result_helpers.go create mode 100644 server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/resultsummary.go create mode 100644 server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/session.go create mode 100644 server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/transaction.go create mode 100644 server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/transaction_config.go create mode 100644 server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/useragent.go diff --git a/server/agentmanager/topoclient.go b/server/agentmanager/topoclient.go index 146fa11..885aeb4 100644 --- a/server/agentmanager/topoclient.go +++ b/server/agentmanager/topoclient.go @@ -158,10 +158,6 @@ func (t *Topoclient) InitPluginClient() { } } -func (t *Topoclient) InitJanusGraph() { - -} - func (t *Topoclient) InitErrorControl(errch <-chan error, errgroup *sync.WaitGroup) { switch conf.Global_config.Logopts.Driver { case "stdout": diff --git a/server/dao/neo4j.go b/server/dao/neo4j.go new file mode 100644 index 0000000..18c29e4 --- /dev/null +++ b/server/dao/neo4j.go @@ -0,0 +1,113 @@ +package dao + +import ( + "fmt" + + "github.com/neo4j/neo4j-go-driver/v4/neo4j" +) + +type Neo4jclient struct { + addr string + username string + password string + DB string + + driver neo4j.Driver +} + +func CreateNeo4j(url, user, pass, db string) *Neo4jclient { + return &Neo4jclient{ + addr: url, + username: user, + password: pass, + DB: db, + } +} + +func (n *Neo4jclient) Create_driver() (neo4j.Driver, error) { + return neo4j.NewDriver(n.addr, neo4j.BasicAuth(n.username, n.password, "")) +} + +func (n *Neo4jclient) Close_driver() error { + return n.driver.Close() +} + +func (n *Neo4jclient) Entity_create(cypher string) error { + + session := n.driver.NewSession(neo4j.SessionConfig{AccessMode: neo4j.AccessModeWrite, DatabaseName: n.DB}) + defer session.Close() + + _, err := session.WriteTransaction(func(tx neo4j.Transaction) (interface{}, error) { + result, err := tx.Run(cypher, nil) + if err != nil { + return nil, err + } + return result.Consume() + }) + + return err +} + +func (n *Neo4jclient) Node_query(cypher string) ([]neo4j.Node, error) { + + var list []neo4j.Node + session := n.driver.NewSession(neo4j.SessionConfig{AccessMode: neo4j.AccessModeRead, DatabaseName: n.DB}) + defer session.Close() + _, err := session.ReadTransaction(func(tx neo4j.Transaction) (interface{}, error) { + result, err := tx.Run(cypher, nil) + if err != nil { + fmt.Println("NodeQuery failed: ", err) + return nil, err + } + + for result.Next() { + record := result.Record() + if value, ok := record.Get("n"); ok { + node := value.(neo4j.Node) + list = append(list, node) + } + } + if err = result.Err(); err != nil { + fmt.Println("iterate node result failed: ", err) + return nil, err + } + + return list, result.Err() + }) + + if err != nil { + fmt.Println("node Readtransaction error:", err) + } + return list, err +} + +func (n *Neo4jclient) Relation_query(cypher string) ([]neo4j.Relationship, error) { + + var list []neo4j.Relationship + session := n.driver.NewSession(neo4j.SessionConfig{AccessMode: neo4j.AccessModeRead, DatabaseName: n.DB}) + defer session.Close() + _, err := session.ReadTransaction(func(tx neo4j.Transaction) (interface{}, error) { + result, err := tx.Run(cypher, nil) + if err != nil { + fmt.Println("RelationshipQuery failed: ", err) + return nil, err + } + for result.Next() { + record := result.Record() + if value, ok := record.Get("r"); ok { + relationship := value.(neo4j.Relationship) + list = append(list, relationship) + } + } + if err = result.Err(); err != nil { + fmt.Println("iterate relation result failed: ", err) + return nil, err + } + return list, result.Err() + }) + + if err != nil { + fmt.Println("relation Readtransaction error:", err) + } + return list, err +} diff --git a/server/go.mod b/server/go.mod index beaa761..ca85ff1 100644 --- a/server/go.mod +++ b/server/go.mod @@ -7,6 +7,7 @@ require ( github.com/gin-contrib/timeout v0.0.3 github.com/gin-gonic/gin v1.7.7 github.com/mitchellh/mapstructure v1.5.0 + github.com/neo4j/neo4j-go-driver/v4 v4.4.7 github.com/pkg/errors v0.9.1 github.com/shirou/gopsutil v3.21.11+incompatible ) @@ -17,7 +18,7 @@ require ( github.com/go-ole/go-ole v1.2.6 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect - github.com/golang/protobuf v1.5.0 // indirect + github.com/golang/protobuf v1.5.2 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/kr/text v0.2.0 // indirect github.com/leodido/go-urn v1.2.4 // indirect diff --git a/server/go.sum b/server/go.sum index ec44aa1..e707f17 100644 --- a/server/go.sum +++ b/server/go.sum @@ -4,6 +4,9 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU= github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= @@ -26,12 +29,25 @@ github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91 github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= github.com/go-playground/validator/v10 v10.14.1 h1:9c50NUPC30zyuKprjL3vNZ0m5oG+jU0zvx4AqHGnv4k= github.com/go-playground/validator/v10 v10.14.1/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.5.0 h1:LUVKkCeviFUMKqHa4tXIIij/lbhnMbP7Fn5wKdKkRh4= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/jonboulle/clockwork v0.4.0 h1:p4Cf1aMWXnXAUh8lVfewRBx1zaTSYKrKMF2g3ST4RZ4= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= @@ -59,6 +75,17 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/neo4j/neo4j-go-driver/v4 v4.4.7 h1:6D0DPI7VOVF6zB8eubY1lav7RI7dZ2mytnr3fj369Ow= +github.com/neo4j/neo4j-go-driver/v4 v4.4.7/go.mod h1:NexOfrm4c317FVjekrhVV8pHBXgtMG5P6GeweJWCyo4= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= +github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= +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.16.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -74,6 +101,7 @@ github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSS github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= @@ -88,37 +116,79 @@ github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVM github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw= github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= +golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/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-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/server/vendor/github.com/neo4j/neo4j-go-driver/v4/LICENSE b/server/vendor/github.com/neo4j/neo4j-go-driver/v4/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/server/vendor/github.com/neo4j/neo4j-go-driver/v4/LICENSE @@ -0,0 +1,201 @@ + 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. diff --git a/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/aliases.go b/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/aliases.go new file mode 100644 index 0000000..f229a90 --- /dev/null +++ b/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/aliases.go @@ -0,0 +1,98 @@ +/* + * 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} +} diff --git a/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/auth_tokens.go b/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/auth_tokens.go new file mode 100644 index 0000000..2ca4728 --- /dev/null +++ b/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/auth_tokens.go @@ -0,0 +1,104 @@ +/* + * 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} +} diff --git a/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/config.go b/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/config.go new file mode 100644 index 0000000..4416312 --- /dev/null +++ b/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/config.go @@ -0,0 +1,180 @@ +/* + * 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) +} diff --git a/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/consolelogger.go b/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/consolelogger.go new file mode 100644 index 0000000..7c807ea --- /dev/null +++ b/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/consolelogger.go @@ -0,0 +1,52 @@ +/* + * 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{} +} diff --git a/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/db/connection.go b/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/db/connection.go new file mode 100644 index 0000000..aac86cc --- /dev/null +++ b/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/db/connection.go @@ -0,0 +1,126 @@ +/* + * 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) +} diff --git a/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/db/errors.go b/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/db/errors.go new file mode 100644 index 0000000..f2fe749 --- /dev/null +++ b/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/db/errors.go @@ -0,0 +1,134 @@ +/* + * 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) +} diff --git a/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/db/record.go b/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/db/record.go new file mode 100644 index 0000000..8cb2fc4 --- /dev/null +++ b/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/db/record.go @@ -0,0 +1,51 @@ +/* + * 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] +} diff --git a/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/db/summary.go b/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/db/summary.go new file mode 100644 index 0000000..4148ea1 --- /dev/null +++ b/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/db/summary.go @@ -0,0 +1,142 @@ +/* + * 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 +} diff --git a/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/dbtype/graph.go b/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/dbtype/graph.go new file mode 100644 index 0000000..1344187 --- /dev/null +++ b/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/dbtype/graph.go @@ -0,0 +1,46 @@ +/* + * 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 +} diff --git a/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/dbtype/spatial.go b/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/dbtype/spatial.go new file mode 100644 index 0000000..592d21a --- /dev/null +++ b/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/dbtype/spatial.go @@ -0,0 +1,49 @@ +/* + * 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) +} diff --git a/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/dbtype/temporal.go b/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/dbtype/temporal.go new file mode 100644 index 0000000..fc01be3 --- /dev/null +++ b/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/dbtype/temporal.go @@ -0,0 +1,89 @@ +/* + * 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 +} diff --git a/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/directrouter.go b/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/directrouter.go new file mode 100644 index 0000000..6d1380d --- /dev/null +++ b/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/directrouter.go @@ -0,0 +1,49 @@ +/* + * 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() { +} diff --git a/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/driver.go b/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/driver.go new file mode 100644 index 0000000..8af3973 --- /dev/null +++ b/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/driver.go @@ -0,0 +1,305 @@ +/* + * 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:// + 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 +} diff --git a/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/error.go b/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/error.go new file mode 100644 index 0000000..e01f532 --- /dev/null +++ b/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/error.go @@ -0,0 +1,156 @@ +/* + * 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 +} diff --git a/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/bolt/bolt3.go b/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/bolt/bolt3.go new file mode 100644 index 0000000..a6a2829 --- /dev/null +++ b/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/bolt/bolt3.go @@ -0,0 +1,776 @@ +/* + * 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 + 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, + }, + 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, + useUtc: false, + } + 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.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.currStream = &stream{keys: succ.fields, tfirst: succ.tfirst} + // Change state to streaming + if b.state == bolt3_ready { + b.state = bolt3_streaming + } else { + b.state = bolt3_streamingtx + } + + 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 + } + } + // Add some extras to the summary + sum.Agent = b.serverVersion + sum.Major = 3 + sum.Minor = b.minor + sum.ServerName = b.serverName + sum.TFirst = b.currStream.tfirst + b.currStream.sum = sum + b.currStream = nil + 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 +} diff --git a/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/bolt/bolt4.go b/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/bolt/bolt4.go new file mode 100644 index 0000000..6dc8975 --- /dev/null +++ b/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/bolt/bolt4.go @@ -0,0 +1,1046 @@ +/* + * 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 ( + bolt4_ready = iota // Ready for use + bolt4_streaming // Receiving result from auto commit query + bolt4_pendingtx // Transaction has been requested but not applied + bolt4_tx // Transaction pending + bolt4_streamingtx // Receiving result from a query within a transaction + bolt4_failed // Recoverable error, needs reset + bolt4_dead // Non recoverable protocol or connection error + bolt4_unauthorized // Initial state, not sent hello message with authentication +) + +// Default fetch size +const bolt4_fetchsize = 1000 + +type internalTx4 struct { + mode db.AccessMode + bookmarks []string + timeout time.Duration + txMeta map[string]interface{} + databaseName string + impersonatedUser string +} + +func (i *internalTx4) 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 + } + if i.databaseName != db.DefaultDatabase { + meta["db"] = i.databaseName + } + if i.impersonatedUser != "" { + meta["imp_user"] = i.impersonatedUser + } + return meta +} + +type bolt4 struct { + state int + txId db.TxHandle + streams openstreams + conn net.Conn + serverName string + out outgoing + in incoming + connId string + logId string + serverVersion string + pendingTx internalTx4 // Stashed away when tx started explicitly + hasPendingTx bool + bookmark string // Last bookmark + birthDate time.Time + log log.Logger + databaseName string + err error // Last fatal error + minor int + lastQid int64 // Last seen qid +} + +func NewBolt4(serverName string, conn net.Conn, logger log.Logger, boltLog log.BoltLogger) *bolt4 { + b := &bolt4{ + state: bolt4_unauthorized, + conn: conn, + serverName: serverName, + birthDate: time.Now(), + log: logger, + streams: openstreams{}, + in: incoming{ + buf: make([]byte, 4096), + hyd: hydrator{ + boltLogger: boltLog, + }, + connReadTimeout: -1, + }, + } + b.out = outgoing{ + chunker: newChunker(), + packer: packstream.Packer{}, + onErr: func(err error) { b.setError(err, true) }, + boltLogger: boltLog, + } + + return b +} + +func (b *bolt4) checkStreams() { + if b.streams.num <= 0 { + // Perform state transition from streaming, if in that state otherwise keep the current + // state as we are in some kind of bad shape + switch b.state { + case bolt4_streamingtx: + b.state = bolt4_tx + case bolt4_streaming: + b.state = bolt4_ready + } + } +} + +func (b *bolt4) ServerName() string { + return b.serverName +} + +func (b *bolt4) ServerVersion() string { + return b.serverVersion +} + +// Sets b.err and b.state to bolt4_failed or bolt4_dead when fatal is true. +func (b *bolt4) setError(err error, fatal bool) { + // Has no effect, can reduce nested ifs + if err == nil { + return + } + + // No previous error + if b.err == nil { + b.err = err + b.state = bolt4_failed + } + + // Increase severity even if it was a previous error + if fatal { + b.state = bolt4_dead + } + + // Forward error to current stream if there is one + if b.streams.curr != nil { + b.streams.detach(nil, err) + b.checkStreams() + } + + // Do not log big cypher statements as errors + neo4jErr, casted := err.(*db.Neo4jError) + if casted && neo4jErr.Classification() == "ClientError" { + b.log.Debugf(log.Bolt4, b.logId, "%s", err) + } else { + b.log.Error(log.Bolt4, b.logId, err) + } +} + +func (b *bolt4) receiveMsg() interface{} { + // Potentially dangerous to receive when an error has occurred, could hang. + // Important, a lot of code has been simplified relying on this check. + if b.err != nil { + return nil + } + + msg, err := b.in.next(b.conn) + b.setError(err, true) + 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 *bolt4) receiveSuccess() *success { + msg := b.receiveMsg() + if b.err != nil { + return nil + } + + switch v := msg.(type) { + case *success: + if v.qid > -1 { + b.lastQid = v.qid + } + return v + case *db.Neo4jError: + b.setError(v, isFatalError(v)) + return nil + default: + // Unexpected message received + b.setError(errors.New("Expected success or database error"), true) + return nil + } +} + +func (b *bolt4) connect(minor int, auth map[string]interface{}, userAgent string, routingContext map[string]string) error { + if err := b.assertState(bolt4_unauthorized); err != nil { + return err + } + + // Prepare hello message + hello := map[string]interface{}{ + "user_agent": userAgent, + } + // On bolt >= 4.1 add routing to enable/disable routing + if minor >= 1 { + if routingContext != nil { + hello["routing"] = routingContext + } + } + checkUtcPatch := minor >= 3 + if checkUtcPatch { + hello["patch_bolt"] = []string{"utc"} + } + // Merge authentication keys into hello, avoid overwriting existing keys + for k, v := range auth { + _, exists := hello[k] + if !exists { + hello[k] = v + } + } + + // Send hello message and wait for confirmation + b.out.appendHello(hello) + b.out.send(b.conn) + succ := b.receiveSuccess() + if b.err != nil { + return b.err + } + + b.connId = succ.connectionId + b.serverVersion = succ.server + if checkUtcPatch { + useUtc := false + for _, patch := range succ.patches { + if patch == "utc" { + useUtc = true + break + } + } + b.in.hyd.useUtc = useUtc + b.out.useUtc = useUtc + } + + // Construct log identity + connectionLogId := fmt.Sprintf("%s@%s", b.connId, b.serverName) + b.logId = connectionLogId + b.in.hyd.logId = connectionLogId + b.out.logId = connectionLogId + + b.initializeReadTimeoutHint(succ.configurationHints) + // Transition into ready state + b.state = bolt4_ready + b.minor = minor + b.streams.reset() + b.log.Infof(log.Bolt4, b.logId, "Connected") + return nil +} + +func (b *bolt4) checkImpersonationAndVersion(impersonatedUser string) error { + if impersonatedUser != "" && b.minor < 4 { + return &db.FeatureNotSupportedError{Server: b.serverName, Feature: "user impersonation", Reason: "requires at least server v4.4"} + } + return nil +} + +func (b *bolt4) TxBegin(txConfig db.TxConfig) (db.TxHandle, error) { + // Ok, to begin transaction while streaming auto-commit, just empty the stream and continue. + if b.state == bolt4_streaming { + if b.bufferStream(); b.err != nil { + return 0, b.err + } + } + // Makes all outstanding streams invalid + b.streams.reset() + + if err := b.assertState(bolt4_ready); err != nil { + return 0, err + } + + if err := b.checkImpersonationAndVersion(txConfig.ImpersonatedUser); err != nil { + return 0, err + } + + tx := internalTx4{ + mode: txConfig.Mode, + bookmarks: txConfig.Bookmarks, + timeout: txConfig.Timeout, + txMeta: txConfig.Meta, + databaseName: b.databaseName, + impersonatedUser: txConfig.ImpersonatedUser, + } + + // If there are bookmarks, begin the transaction immediately for backwards compatible + // reasons, otherwise delay it to save a round-trip + if len(tx.bookmarks) > 0 { + b.out.appendBegin(tx.toMeta()) + b.out.send(b.conn) + b.receiveSuccess() + if b.err != nil { + return 0, b.err + } + b.state = bolt4_tx + b.hasPendingTx = false + } else { + // Stash this into pending internal tx + b.pendingTx = tx + b.hasPendingTx = true + b.state = bolt4_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 *bolt4) assertTxHandle(h1, h2 db.TxHandle) error { + if h1 != h2 { + err := errors.New("Invalid transaction handle") + b.log.Error(log.Bolt4, b.logId, err) + return err + } + return nil +} + +// Should NOT set b.err or b.state since the connection is still valid +func (b *bolt4) 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.Bolt4, b.logId, err) + return err +} + +func (b *bolt4) 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 == bolt4_pendingtx { + b.state = bolt4_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.discardAllStreams(); b.err != nil { + return b.err + } + + // Should be in vanilla tx state now + if err := b.assertState(bolt4_tx); err != nil { + return err + } + + // Send request to server to commit + b.out.appendCommit() + b.out.send(b.conn) + 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 = bolt4_ready + return nil +} + +func (b *bolt4) 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 == bolt4_pendingtx { + b.state = bolt4_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.discardAllStreams(); b.err != nil { + return b.err + } + + // Should be in vanilla tx state now + if err := b.assertState(bolt4_tx); err != nil { + return err + } + + // Send rollback request to server + b.out.appendRollback() + b.out.send(b.conn) + if b.receiveSuccess(); b.err != nil { + return b.err + } + + b.state = bolt4_ready + return nil +} + +// Discards all records in current stream if in streaming state and there is a current stream. +func (b *bolt4) discardStream() { + if b.state != bolt4_streaming && b.state != bolt4_streamingtx { + return + } + + stream := b.streams.curr + if stream == nil { + return + } + + discarded := false + for { + _, batch, sum := b.receiveNext() + if batch { + if discarded { + // Response to discard, see below + b.streams.remove(stream) + b.checkStreams() + return + } + // Discard all! After this the next receive will get another batch + // as a response to the discard, we need to keep track of that we + // already sent a discard. + discarded = true + stream.fetchSize = -1 + if b.state == bolt4_streamingtx && stream.qid != b.lastQid { + b.out.appendDiscardNQid(stream.fetchSize, stream.qid) + } else { + b.out.appendDiscardN(stream.fetchSize) + } + b.out.send(b.conn) + } else if sum != nil || b.err != nil { + // Stream is detached in receiveNext + return + } + } +} + +func (b *bolt4) discardAllStreams() { + if b.state != bolt4_streaming && b.state != bolt4_streamingtx { + return + } + + // Discard current + b.discardStream() + b.streams.reset() + b.checkStreams() +} + +// Sends a PULL n request to server. State should be streaming and there should be a current stream. +func (b *bolt4) sendPullN() { + b.assertState(bolt4_streaming, bolt4_streamingtx) + if b.state == bolt4_streaming { + b.out.appendPullN(b.streams.curr.fetchSize) + b.out.send(b.conn) + } else if b.state == bolt4_streamingtx { + fetchSize := b.streams.curr.fetchSize + if b.streams.curr.qid == b.lastQid { + b.out.appendPullN(fetchSize) + } else { + b.out.appendPullNQid(fetchSize, b.streams.curr.qid) + } + b.out.send(b.conn) + } +} + +// Collects all records in current stream if in streaming state and there is a current stream. +func (b *bolt4) bufferStream() { + stream := b.streams.curr + if stream == nil { + return + } + + // Buffer current batch and start infinite batch and/or buffer the infinite batch + for { + rec, batch, _ := b.receiveNext() + if rec != nil { + stream.push(rec) + } else if batch { + stream.fetchSize = -1 + b.sendPullN() + } else { + // Either summary or an error + return + } + } +} + +// Prepares the current stream for being switched out by collecting all records in the current +// stream up until the next batch. Assumes that we are in a streaming state. +func (b *bolt4) pauseStream() { + stream := b.streams.curr + if stream == nil { + return + } + + for { + rec, batch, _ := b.receiveNext() + if rec != nil { + stream.push(rec) + } else if batch { + b.streams.pause() + return + } else { + // Either summary or an error + return + } + } +} + +func (b *bolt4) resumeStream(s *stream) { + b.streams.resume(s) + b.sendPullN() + if b.err != nil { + return + } +} + +func (b *bolt4) run(cypher string, params map[string]interface{}, fetchSize int, tx *internalTx4) (*stream, error) { + // If already streaming, consume the whole thing first + if b.state == bolt4_streaming { + if b.bufferStream(); b.err != nil { + return nil, b.err + } + } else if b.state == bolt4_streamingtx { + if b.pauseStream(); b.err != nil { + return nil, b.err + } + } + + if err := b.assertState(bolt4_tx, bolt4_ready, bolt4_pendingtx, bolt4_streamingtx); err != nil { + return nil, err + } + + // Transaction meta data, used either in lazily started transaction or to run message. + var meta map[string]interface{} + if tx != nil { + meta = tx.toMeta() + } + if b.state == bolt4_pendingtx { + // Append lazy begin transaction message + b.out.appendBegin(meta) + meta = nil // Don't add this to run message again + } + + // Append run message + b.out.appendRun(cypher, params, meta) + + // Ensure that fetchSize is in a valid range + switch { + case fetchSize < 0: + fetchSize = -1 + case fetchSize == 0: + fetchSize = bolt4_fetchsize + } + // Append pull message and send it along with other pending messages + b.out.appendPullN(fetchSize) + b.out.send(b.conn) + + // Process server responses + // Receive confirmation of transaction begin if it was started above + if b.state == bolt4_pendingtx { + if b.receiveSuccess(); b.err != nil { + return nil, b.err + } + b.state = bolt4_tx + } + + // Receive confirmation of run message + succ := b.receiveSuccess() + if b.err != nil { + // If failed with a database error, there will be an ignored response for the + // pull message as well, this will be cleaned up by Reset + return nil, b.err + } + // Create a stream representation, set it to current and track it + stream := &stream{keys: succ.fields, qid: succ.qid, fetchSize: fetchSize, tfirst: succ.tfirst} + // Change state to streaming + if b.state == bolt4_ready { + b.state = bolt4_streaming + } else { + b.state = bolt4_streamingtx + } + + b.streams.attach(stream) + // No need to check streams state, we know we are streaming + + return stream, nil +} + +func (b *bolt4) Run(cmd db.Command, txConfig db.TxConfig) (db.StreamHandle, error) { + if err := b.assertState(bolt4_streaming, bolt4_ready); err != nil { + return nil, err + } + + if err := b.checkImpersonationAndVersion(txConfig.ImpersonatedUser); err != nil { + return 0, err + } + + tx := internalTx4{ + mode: txConfig.Mode, + bookmarks: txConfig.Bookmarks, + timeout: txConfig.Timeout, + txMeta: txConfig.Meta, + databaseName: b.databaseName, + impersonatedUser: txConfig.ImpersonatedUser, + } + stream, err := b.run(cmd.Cypher, cmd.Params, cmd.FetchSize, &tx) + if err != nil { + return nil, err + } + return stream, nil +} + +func (b *bolt4) RunTx(txh db.TxHandle, cmd db.Command) (db.StreamHandle, error) { + if err := b.assertTxHandle(b.txId, txh); err != nil { + return nil, err + } + + tx := &b.pendingTx + if !b.hasPendingTx { + tx = nil + } + stream, err := b.run(cmd.Cypher, cmd.Params, cmd.FetchSize, tx) + b.hasPendingTx = false + if err != nil { + return nil, err + } + return stream, nil +} + +func (b *bolt4) Keys(streamHandle db.StreamHandle) ([]string, error) { + // Don't care about if the stream is the current or even if it belongs to this connection. + // Do NOT set b.err for this error + stream, err := b.streams.getUnsafe(streamHandle) + if err != nil { + return nil, err + } + return stream.keys, nil +} + +// Reads one record from the stream. +func (b *bolt4) Next(streamHandle db.StreamHandle) (*db.Record, *db.Summary, error) { + // Do NOT set b.err for this error + stream, err := b.streams.getUnsafe(streamHandle) + if err != nil { + return nil, nil, err + } + + // Buffered stream or someone else's stream, doesn't matter... + // Summary and error are considered buffered as well. + buf, rec, sum, err := stream.bufferedNext() + if buf { + return rec, sum, err + } + + // Make sure that the stream belongs to this bolt instance otherwise we might mess + // up the internal state machine really bad. If clients stick to streams out of + // transaction scope or after the connection been sent back to the pool we might end + // up here. + if err = b.streams.isSafe(stream); err != nil { + return nil, nil, err + } + + // If the stream isn't the current we must finish what we're doing with the current stream + // and make it the current one. + if stream != b.streams.curr { + b.pauseStream() + if b.err != nil { + return nil, nil, b.err + } + b.resumeStream(stream) + } + + rec, batchCompleted, sum := b.receiveNext() + if batchCompleted { + b.sendPullN() + if b.err != nil { + return nil, nil, b.err + } + rec, _, sum = b.receiveNext() + } + return rec, sum, b.err +} + +func (b *bolt4) Consume(streamHandle db.StreamHandle) (*db.Summary, error) { + // Do NOT set b.err for this error + stream, err := b.streams.getUnsafe(streamHandle) + if err != nil { + return nil, err + } + + // If the stream already is complete we don't care about who it belongs to + if stream.sum != nil || stream.err != nil { + return stream.sum, stream.err + } + + // Make sure the stream is safe (tied to this bolt instance and scope) + if err = b.streams.isSafe(stream); err != nil { + return nil, err + } + + // We should be streaming otherwise it is a an internal error, shouldn't be + // a safe stream while not streaming. + if err = b.assertState(bolt4_streaming, bolt4_streamingtx); err != nil { + return nil, err + } + + // If the stream isn't current, we need to pause the current one. + if stream != b.streams.curr { + b.pauseStream() + if b.err != nil { + return nil, b.err + } + b.resumeStream(stream) + } + + // If the stream is current, discard everything up to next batch and discard the + // stream on the server. + b.discardStream() + return stream.sum, stream.err +} + +func (b *bolt4) Buffer(streamHandle db.StreamHandle) error { + // Do NOT set b.err for this error + stream, err := b.streams.getUnsafe(streamHandle) + if err != nil { + return err + } + + // If the stream already is complete we don't care about who it belongs to + if stream.sum != nil || stream.err != nil { + return stream.Err() + } + + // Make sure the stream is safe + // Do NOT set b.err for this error + if err = b.streams.isSafe(stream); err != nil { + return err + } + + // We should be streaming otherwise it is a an internal error, shouldn't be + // a safe stream while not streaming. + if err = b.assertState(bolt4_streaming, bolt4_streamingtx); err != nil { + return err + } + + // If the stream isn't current, we need to pause the current one. + if stream != b.streams.curr { + b.pauseStream() + if b.err != nil { + return b.err + } + b.resumeStream(stream) + } + + b.bufferStream() + return stream.Err() +} + +// Reads one record from the network and returns either a record, a flag that indicates that +// a PULL N batch completed, a summary indicating end of stream or an error. +// Assumes that there is a current stream and that streaming is active. +func (b *bolt4) receiveNext() (*db.Record, bool, *db.Summary) { + res := b.receiveMsg() + if b.err != nil { + return nil, false, nil + } + + switch x := res.(type) { + case *db.Record: + // A new record + x.Keys = b.streams.curr.keys + return x, false, nil + case *success: + // End of batch or end of stream? + if x.hasMore { + // End of batch + return nil, true, nil + } + // End of stream, parse summary. Current implementation never fails. + sum := x.summary() + // Add some extras to the summary + sum.Agent = b.serverVersion + sum.Major = 4 + sum.Minor = b.minor + sum.ServerName = b.serverName + sum.TFirst = b.streams.curr.tfirst + if len(sum.Bookmark) > 0 { + b.bookmark = sum.Bookmark + } + // Done with this stream + b.streams.detach(sum, nil) + b.checkStreams() + return nil, false, sum + case *db.Neo4jError: + b.setError(x, isFatalError(x)) // Will detach the stream + return nil, false, nil + default: + // Unknown territory + b.setError(errors.New("Unknown response"), true) + return nil, false, nil + } +} + +func (b *bolt4) Bookmark() string { + return b.bookmark +} + +func (b *bolt4) IsAlive() bool { + return b.state != bolt4_dead +} + +func (b *bolt4) Birthdate() time.Time { + return b.birthDate +} + +func (b *bolt4) Reset() { + defer func() { + // Reset internal state + b.txId = 0 + b.bookmark = "" + b.hasPendingTx = false + b.databaseName = db.DefaultDatabase + b.err = nil + b.streams.reset() + }() + + if b.state == bolt4_ready || b.state == bolt4_dead { + // No need for reset + return + } + + // Reset any pending error, should be matching bolt4_failed so + // it should be recoverable. + b.err = nil + + // Send the reset message to the server + b.out.appendReset() + b.out.send(b.conn) + if b.err != nil { + return + } + + for { + msg := b.receiveMsg() + if b.err != nil { + return + } + switch x := msg.(type) { + case *ignored, *db.Record: + // Command ignored + case *success: + if x.isResetResponse() { + // Reset confirmed + b.state = bolt4_ready + return + } + default: + b.state = bolt4_dead + return + } + } +} + +func (b *bolt4) GetRoutingTable(context map[string]string, bookmarks []string, database, impersonatedUser string) (*db.RoutingTable, error) { + if err := b.assertState(bolt4_ready); err != nil { + return nil, err + } + + b.log.Infof(log.Bolt4, b.logId, "Retrieving routing table") + if b.minor > 3 { + extras := map[string]interface{}{} + if database != db.DefaultDatabase { + extras["db"] = database + } + if impersonatedUser != "" { + extras["imp_user"] = impersonatedUser + } + b.out.appendRoute(context, bookmarks, extras) + b.out.send(b.conn) + succ := b.receiveSuccess() + if b.err != nil { + return nil, b.err + } + return succ.routingTable, nil + } + + if err := b.checkImpersonationAndVersion(impersonatedUser); err != nil { + return nil, err + } + + if b.minor > 2 { + b.out.appendRouteToV43(context, bookmarks, database) + b.out.send(b.conn) + succ := b.receiveSuccess() + if b.err != nil { + return nil, b.err + } + // On this version we will not receive the database name + succ.routingTable.DatabaseName = database + return succ.routingTable, nil + } + return b.callGetRoutingTable(context, bookmarks, database) +} + +func (b *bolt4) callGetRoutingTable(context map[string]string, bookmarks []string, database string) (*db.RoutingTable, error) { + // The query should run in system database, preserve current setting and restore it when + // done. + originalDatabaseName := b.databaseName + b.databaseName = "system" + defer func() { b.databaseName = originalDatabaseName }() + + // Query for the users default database or a specific database + runCommand := db.Command{ + Cypher: "CALL dbms.routing.getRoutingTable($context)", + Params: map[string]interface{}{"context": context}, + FetchSize: -1, + } + if database != db.DefaultDatabase { + runCommand.Cypher = "CALL dbms.routing.getRoutingTable($context, $database)" + runCommand.Params["database"] = database + } + txConfig := db.TxConfig{Mode: db.ReadMode, Bookmarks: bookmarks} + streamHandle, err := b.Run(runCommand, txConfig) + if err != nil { + 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 connection in ready state + b.Next(streamHandle) + + table := parseRoutingTableRecord(rec) + if table == nil { + return nil, errors.New("Unable to parse routing table") + } + // On this version we will not recive the database name + table.DatabaseName = database + return table, nil +} + +// Beware, could be called on another thread when driver is closed. +func (b *bolt4) Close() { + b.log.Infof(log.Bolt4, b.logId, "Close") + if b.state != bolt4_dead { + b.out.appendGoodbye() + b.out.send(b.conn) + } + b.conn.Close() + b.state = bolt4_dead +} + +func (b *bolt4) SelectDatabase(database string) { + b.databaseName = database +} + +func (b *bolt4) ForceReset() error { + if b.state == bolt4_ready { + b.out.appendReset() + b.out.send(b.conn) + if b.err != nil { + return b.err + } + b.receiveMsg() + return b.err + } + b.Reset() + return b.err +} + +func (b *bolt4) SetBoltLogger(boltLogger log.BoltLogger) { + b.in.hyd.boltLogger = boltLogger + b.out.boltLogger = boltLogger +} + +const readTimeoutHintName = "connection.recv_timeout_seconds" + +func (b *bolt4) initializeReadTimeoutHint(hints map[string]interface{}) { + readTimeoutHint, ok := hints[readTimeoutHintName] + if !ok { + return + } + readTimeout, ok := readTimeoutHint.(int64) + if !ok { + b.log.Warnf(log.Bolt4, b.logId, `invalid %q value: %v, ignoring hint. Only strictly positive integer values are accepted`, readTimeoutHintName, readTimeoutHint) + return + } + if readTimeout <= 0 { + b.log.Warnf(log.Bolt4, b.logId, `invalid %q integer value: %d. Only strictly positive values are accepted"`, readTimeoutHintName, readTimeout) + return + } + b.log.Infof(log.Bolt4, b.logId, `received "connection.recv_timeout_seconds" hint value of %d second(s)`, readTimeout) + b.in.connReadTimeout = time.Duration(readTimeout) * time.Second +} + +func isFatalError(err *db.Neo4jError) bool { + // Treat expired auth as fatal so that pool is cleaned up of old connections + return err != nil && err.Code == "Status.Security.AuthorizationExpired" +} diff --git a/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/bolt/bolt_logging.go b/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/bolt/bolt_logging.go new file mode 100644 index 0000000..81f4927 --- /dev/null +++ b/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/bolt/bolt_logging.go @@ -0,0 +1,119 @@ +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"] = "" + 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"] = "" + 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()) +} diff --git a/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/bolt/chunker.go b/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/bolt/chunker.go new file mode 100644 index 0000000..7a5364b --- /dev/null +++ b/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/bolt/chunker.go @@ -0,0 +1,107 @@ +/* + * 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 +} diff --git a/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/bolt/connect.go b/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/bolt/connect.go new file mode 100644 index 0000000..8343161 --- /dev/null +++ b/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/bolt/connect.go @@ -0,0 +1,105 @@ +/* + * 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("", " %#010X", handshake[0:4]) + boltLog.LogClientMessage("", " %#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("", " %#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 +} diff --git a/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/bolt/dechunker.go b/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/bolt/dechunker.go new file mode 100644 index 0000000..b168f05 --- /dev/null +++ b/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/bolt/dechunker.go @@ -0,0 +1,87 @@ +/* + * 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" + "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) ([]byte, []byte, error) { + + sizeBuf := []byte{0x00, 0x00} + off := 0 + + reader := rio.NewRacingReader(conn) + + for { + updatedCtx, cancelFunc := newContext(readTimeout) + _, 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) + _, 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) (context.Context, context.CancelFunc) { + ctx := context.Background() + if readTimeout >= 0 { + return context.WithTimeout(ctx, readTimeout) + } + return ctx, nil +} diff --git a/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/bolt/hydrator.go b/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/bolt/hydrator.go new file mode 100644 index 0000000..069ccb6 --- /dev/null +++ b/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/bolt/hydrator.go @@ -0,0 +1,899 @@ +/* + * 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{} + patches []string +} + +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 + useUtc bool +} + +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 + case "patch_bolt": + patches := h.strings() + succ.patches = patches + 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': + if h.useUtc { + return h.unknownStructError(t) + } + return h.dateTimeOffset(n) + case 'I': + if !h.useUtc { + return h.unknownStructError(t) + } + return h.utcDateTimeOffset(n) + case 'f': + if h.useUtc { + return h.unknownStructError(t) + } + return h.dateTimeNamedZone(n) + case 'i': + if !h.useUtc { + return h.unknownStructError(t) + } + return h.utcDateTimeNamedZone(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: + return h.unknownStructError(t) + } + 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() + seconds := h.unp.Int() + h.unp.Next() + nanos := h.unp.Int() + h.unp.Next() + offset := h.unp.Int() + // time.Time in local timezone, e.g. 15th of June 2020, 15:30 in Paris (UTC+2h) + unixTime := time.Unix(seconds, nanos) + // time.Time computed in UTC timezone, e.g. 15th of June 2020, 13:30 in UTC + utcTime := unixTime.UTC() + // time.Time **copied** as-is in the target timezone, e.g. 15th of June 2020, 13:30 in target tz + timeZone := time.FixedZone("Offset", int(offset)) + return time.Date( + utcTime.Year(), + utcTime.Month(), + utcTime.Day(), + utcTime.Hour(), + utcTime.Minute(), + utcTime.Second(), + utcTime.Nanosecond(), + timeZone, + ) +} + +func (h *hydrator) utcDateTimeOffset(n uint32) interface{} { + h.unp.Next() + seconds := h.unp.Int() + h.unp.Next() + nanos := h.unp.Int() + h.unp.Next() + offset := h.unp.Int() + timeZone := time.FixedZone("Offset", int(offset)) + return time.Unix(seconds, nanos).In(timeZone) +} + +func (h *hydrator) dateTimeNamedZone(n uint32) interface{} { + h.unp.Next() + seconds := h.unp.Int() + h.unp.Next() + nanos := h.unp.Int() + h.unp.Next() + zone := h.unp.String() + // time.Time in local timezone, e.g. 15th of June 2020, 15:30 in Paris (UTC+2h) + unixTime := time.Unix(seconds, nanos) + // time.Time computed in UTC timezone, e.g. 15th of June 2020, 13:30 in UTC + utcTime := unixTime.UTC() + // time.Time **copied** as-is in the target timezone, e.g. 15th of June 2020, 13:30 in target tz + l, err := time.LoadLocation(zone) + if err != nil { + h.setErr(&db.ProtocolError{ + MessageType: "dateTimeNamedZone", + Field: "location", + Err: err.Error(), + }) + return nil + } + return time.Date( + utcTime.Year(), + utcTime.Month(), + utcTime.Day(), + utcTime.Hour(), + utcTime.Minute(), + utcTime.Second(), + utcTime.Nanosecond(), + l, + ) +} + +func (h *hydrator) utcDateTimeNamedZone(n uint32) interface{} { + h.unp.Next() + secs := h.unp.Int() + h.unp.Next() + nans := h.unp.Int() + h.unp.Next() + zone := h.unp.String() + timeZone, err := time.LoadLocation(zone) + if err != nil { + h.setErr(&db.ProtocolError{ + MessageType: "utcDateTimeNamedZone", + Field: "location", + Err: err.Error(), + }) + return nil + } + return time.Unix(secs, nans).In(timeZone) +} + +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 +} + +func (h *hydrator) unknownStructError(t byte) interface{} { + h.setErr(&db.ProtocolError{ + Err: fmt.Sprintf("Received unknown struct tag: %d", t), + }) + return nil +} diff --git a/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/bolt/incoming.go b/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/bolt/incoming.go new file mode 100644 index 0000000..a549fd5 --- /dev/null +++ b/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/bolt/incoming.go @@ -0,0 +1,41 @@ +/* + * 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 ( + "net" + "time" +) + +type incoming struct { + buf []byte // Reused buffer + hyd hydrator + connReadTimeout time.Duration +} + +func (i *incoming) next(rd net.Conn) (interface{}, error) { + var err error + var msg []byte + i.buf, msg, err = dechunkMessage(rd, i.buf, i.connReadTimeout) + if err != nil { + return nil, err + } + return i.hyd.hydrate(msg) +} diff --git a/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/bolt/messages.go b/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/bolt/messages.go new file mode 100644 index 0000000..2cd4763 --- /dev/null +++ b/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/bolt/messages.go @@ -0,0 +1,41 @@ +/* + * 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 +) diff --git a/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/bolt/outgoing.go b/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/bolt/outgoing.go new file mode 100644 index 0000000..76c2c8c --- /dev/null +++ b/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/bolt/outgoing.go @@ -0,0 +1,443 @@ +/* + * 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 + useUtc bool +} + +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: + if o.useUtc { + if zone, _ := v.Zone(); zone == "Offset" { + o.packUtcDateTimeWithTzOffset(v) + } else { + o.packUtcDateTimeWithTzName(v) + } + break + } + if zone, _ := v.Zone(); zone == "Offset" { + o.packLegacyDateTimeWithTzOffset(v) + } else { + o.packLegacyDateTimeWithTzName(v) + } + 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)}) + } +} + +// deprecated: remove once 4.x Neo4j all reach EOL +func (o *outgoing) packLegacyDateTimeWithTzOffset(dateTime time.Time) { + _, offset := dateTime.Zone() + o.packer.StructHeader('F', 3) + o.packer.Int64(dateTime.Unix() + int64(offset)) + o.packer.Int(dateTime.Nanosecond()) + o.packer.Int(offset) +} + +// deprecated: remove once 4.x Neo4j all reach EOL +func (o *outgoing) packLegacyDateTimeWithTzName(dateTime time.Time) { + _, offset := dateTime.Zone() + o.packer.StructHeader('f', 3) + o.packer.Int64(dateTime.Unix() + int64(offset)) + o.packer.Int(dateTime.Nanosecond()) + o.packer.String(dateTime.Location().String()) +} + +func (o *outgoing) packUtcDateTimeWithTzOffset(dateTime time.Time) { + _, offset := dateTime.Zone() + o.packer.StructHeader('I', 3) + o.packer.Int64(dateTime.Unix()) + o.packer.Int(dateTime.Nanosecond()) + o.packer.Int(offset) +} + +func (o *outgoing) packUtcDateTimeWithTzName(dateTime time.Time) { + o.packer.StructHeader('i', 3) + o.packer.Int64(dateTime.Unix()) + o.packer.Int(dateTime.Nanosecond()) + o.packer.String(dateTime.Location().String()) +} diff --git a/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/bolt/parseroutingtable.go b/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/bolt/parseroutingtable.go new file mode 100644 index 0000000..3f83df2 --- /dev/null +++ b/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/bolt/parseroutingtable.go @@ -0,0 +1,74 @@ +/* + * 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 +} diff --git a/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/bolt/path.go b/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/bolt/path.go new file mode 100644 index 0000000..1a69ed4 --- /dev/null +++ b/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/bolt/path.go @@ -0,0 +1,81 @@ +/* + * 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} +} diff --git a/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/bolt/stream.go b/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/bolt/stream.go new file mode 100644 index 0000000..a049328 --- /dev/null +++ b/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/bolt/stream.go @@ -0,0 +1,153 @@ +/* + * 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 + tfirst int64 // Time that server started streaming +} + +// 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 +} diff --git a/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/connector/connector.go b/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/connector/connector.go new file mode 100644 index 0000000..b587685 --- /dev/null +++ b/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/connector/connector.go @@ -0,0 +1,125 @@ +/* + * 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 + SupplyConnection func(address string) (net.Conn, error) +} + +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) { + if c.SupplyConnection == nil { + c.SupplyConnection = c.createConnection + } + conn, err := c.SupplyConnection(address) + if err != nil { + return nil, &ConnectError{inner: err} + } + + // TLS not requested + if c.SkipEncryption { + connection, err := bolt.Connect(address, conn, c.Auth, c.UserAgent, c.RoutingContext, c.Log, boltLogger) + if err != nil { + if connErr := conn.Close(); connErr != nil { + c.Log.Warnf(log.Driver, "", "Could not close underlying socket after Bolt handshake error") + } + return nil, err + } + return connection, err + } + + // 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, + MinVersion: tls.VersionTLS12, + } + 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} + } + connection, err := bolt.Connect(address, tlsconn, c.Auth, c.UserAgent, c.RoutingContext, c.Log, boltLogger) + if err != nil { + if connErr := conn.Close(); connErr != nil { + c.Log.Warnf(log.Driver, "", "Could not close underlying socket after Bolt handshake error") + } + return nil, err + } + return connection, nil +} + +func (c Connector) createConnection(address string) (net.Conn, error) { + dialer := net.Dialer{Timeout: c.DialTimeout} + if !c.SocketKeepAlive { + dialer.KeepAlive = -1 * time.Second // Turns keep-alive off + } + return dialer.Dial(c.Network, address) +} diff --git a/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/packstream/errors.go b/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/packstream/errors.go new file mode 100644 index 0000000..8535f88 --- /dev/null +++ b/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/packstream/errors.go @@ -0,0 +1,44 @@ +/* + * 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 +} diff --git a/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/packstream/packer.go b/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/packstream/packer.go new file mode 100644 index 0000000..020b180 --- /dev/null +++ b/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/packstream/packer.go @@ -0,0 +1,242 @@ +/* + * 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"} + } +} diff --git a/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/packstream/unpacker.go b/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/packstream/unpacker.go new file mode 100644 index 0000000..a8e885c --- /dev/null +++ b/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/packstream/unpacker.go @@ -0,0 +1,254 @@ +/* + * 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)} + } +} diff --git a/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/pool/errors.go b/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/pool/errors.go new file mode 100644 index 0000000..0646a4c --- /dev/null +++ b/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/pool/errors.go @@ -0,0 +1,48 @@ +/* + * 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" +} diff --git a/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/pool/pool.go b/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/pool/pool.go new file mode 100644 index 0000000..49b7ef5 --- /dev/null +++ b/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/pool/pool.go @@ -0,0 +1,419 @@ +/* + * 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) + } +} diff --git a/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/pool/server.go b/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/pool/server.go new file mode 100644 index 0000000..30c9d31 --- /dev/null +++ b/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/pool/server.go @@ -0,0 +1,166 @@ +/* + * 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) +} diff --git a/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/racingio/reader.go b/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/racingio/reader.go new file mode 100644 index 0000000..a7218ee --- /dev/null +++ b/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/racingio/reader.go @@ -0,0 +1,93 @@ +/* + * 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) +} diff --git a/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/retry/state.go b/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/retry/state.go new file mode 100644 index 0000000..631b78b --- /dev/null +++ b/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/retry/state.go @@ -0,0 +1,155 @@ +/* + * 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 +} diff --git a/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/retry/throttler.go b/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/retry/throttler.go new file mode 100644 index 0000000..3ec460a --- /dev/null +++ b/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/retry/throttler.go @@ -0,0 +1,38 @@ +/* + * 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) +} diff --git a/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/router/errors.go b/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/router/errors.go new file mode 100644 index 0000000..791af5e --- /dev/null +++ b/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/router/errors.go @@ -0,0 +1,47 @@ +/* + * 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} +} diff --git a/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/router/readtable.go b/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/router/readtable.go new file mode 100644 index 0000000..db9bf29 --- /dev/null +++ b/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/router/readtable.go @@ -0,0 +1,60 @@ +/* + * 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 +} diff --git a/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/router/router.go b/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/router/router.go new file mode 100644 index 0000000..e2172da --- /dev/null +++ b/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/router/router.go @@ -0,0 +1,242 @@ +/* + * 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) + } + } +} diff --git a/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/log/bolt_logger.go b/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/log/bolt_logger.go new file mode 100644 index 0000000..558d162 --- /dev/null +++ b/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/log/bolt_logger.go @@ -0,0 +1,34 @@ +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) +} diff --git a/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/log/console.go b/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/log/console.go new file mode 100644 index 0000000..8edbbac --- /dev/null +++ b/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/log/console.go @@ -0,0 +1,69 @@ +/* + * 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...)) +} diff --git a/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/log/logger.go b/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/log/logger.go new file mode 100644 index 0000000..e6949d5 --- /dev/null +++ b/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/log/logger.go @@ -0,0 +1,68 @@ +/* + * 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) +} diff --git a/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/log/void.go b/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/log/void.go new file mode 100644 index 0000000..f93e761 --- /dev/null +++ b/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/log/void.go @@ -0,0 +1,35 @@ +/* + * 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{}) { +} diff --git a/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/result.go b/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/result.go new file mode 100644 index 0000000..6ac1f67 --- /dev/null +++ b/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/result.go @@ -0,0 +1,168 @@ +/* + * 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 the only remaining record from the stream. + // If none or more than one record is left, an error is returned. + // The result is fully consumed after this call and its summary is immediately available when calling Consume. + 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 +} diff --git a/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/result_helpers.go b/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/result_helpers.go new file mode 100644 index 0000000..45c73c8 --- /dev/null +++ b/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/result_helpers.go @@ -0,0 +1,80 @@ +/* + * 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 +} diff --git a/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/resultsummary.go b/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/resultsummary.go new file mode 100644 index 0000000..664664b --- /dev/null +++ b/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/resultsummary.go @@ -0,0 +1,504 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package neo4j + +import ( + "fmt" + "github.com/neo4j/neo4j-go-driver/v4/neo4j/db" + "time" +) + +// StatementType defines the type of the statement +type StatementType int + +const ( + // StatementTypeUnknown identifies an unknown statement type + StatementTypeUnknown StatementType = 0 + // StatementTypeReadOnly identifies a read-only statement + StatementTypeReadOnly StatementType = 1 + // StatementTypeReadWrite identifies a read-write statement + StatementTypeReadWrite StatementType = 2 + // StatementTypeWriteOnly identifies a write-only statement + StatementTypeWriteOnly StatementType = 3 + // StatementTypeSchemaWrite identifies a schema-write statement + StatementTypeSchemaWrite StatementType = 4 +) + +func (st StatementType) String() string { + switch st { + case StatementTypeReadOnly: + return "r" + case StatementTypeReadWrite: + return "rw" + case StatementTypeWriteOnly: + return "w" + case StatementTypeSchemaWrite: + return "s" + default: + return "" + } +} + +type ResultSummary interface { + // Server returns basic information about the server where the statement is carried out. + Server() ServerInfo + // Deprecated: since 4.4, will be removed in 5.0. Use Query instead + Statement() Statement + // Query returns the query that has been executed. + Query() Query + // StatementType returns type of statement that has been executed. + StatementType() StatementType + // Counters returns statistics counts for the statement. + Counters() Counters + // Plan returns statement plan for the executed statement if available, otherwise null. + Plan() Plan + // Profile returns profiled statement plan for the executed statement if available, otherwise null. + Profile() ProfiledPlan + // Notifications returns a slice of notifications produced while executing the statement. + // The list will be empty if no notifications produced while executing the statement. + Notifications() []Notification + // ResultAvailableAfter returns the time it took for the server to make the result available for consumption. + ResultAvailableAfter() time.Duration + // ResultConsumedAfter returns the time it took the server to consume the result. + ResultConsumedAfter() time.Duration + // Database returns information about the database where the result is obtained from + // Returns nil for Neo4j versions prior to v4. + // Returns the default "neo4j" database for Community Edition servers. + Database() DatabaseInfo +} + +// Counters contains statistics about the changes made to the database made as part +// of the statement execution. +type Counters interface { + // Whether there were any updates at all, eg. any of the counters are greater than 0. + ContainsUpdates() bool + // The number of nodes created. + NodesCreated() int + // The number of nodes deleted. + NodesDeleted() int + // The number of relationships created. + RelationshipsCreated() int + // The number of relationships deleted. + RelationshipsDeleted() int + PropertiesSet() int + // The number of labels added to nodes. + LabelsAdded() int + // The number of labels removed from nodes. + LabelsRemoved() int + // The number of indexes added to the schema. + IndexesAdded() int + // The number of indexes removed from the schema. + IndexesRemoved() int + // The number of constraints added to the schema. + ConstraintsAdded() int + // The number of constraints removed from the schema. + ConstraintsRemoved() int + // The number of system updates + SystemUpdates() int + // ContainsSystemUpdates indicates whether the query contains system updates + ContainsSystemUpdates() bool +} + +type Statement interface { + Query +} + +type Query interface { + // Text returns the statement's text. + Text() string + // Deprecated: since 4.4, will be removed in 5.0. Use Parameters instead + Params() map[string]interface{} + // Parameters returns the statement's parameters. + Parameters() map[string]interface{} +} + +// ServerInfo contains basic information of the server. +type ServerInfo interface { + // Address returns the address of the server. + Address() string + // Version returns the version of Neo4j running at the server. + // Deprecated: since 4.3, this function is deprecated. It will be removed in 5.0. please use Agent, ProtocolVersion, or call the dbms.components procedure instead + Version() string + Agent() string + ProtocolVersion() db.ProtocolVersion +} + +// DatabaseInfo contains basic information of the database the query result has been obtained from. +type DatabaseInfo interface { + Name() string +} + +// Plan describes the actual plan that the database planner produced and used (or will use) to execute your statement. +// This can be extremely helpful in understanding what a statement is doing, and how to optimize it. For more details, +// see the Neo4j Manual. The plan for the statement is a tree of plans - each sub-tree containing zero or more child +// plans. The statement starts with the root plan. Each sub-plan is of a specific operator, which describes what +// that part of the plan does - for instance, perform an index lookup or filter results. +// The Neo4j Manual contains a reference of the available operator types, and these may differ across Neo4j versions. +type Plan interface { + // Operator returns the operation this plan is performing. + Operator() string + // Arguments returns the arguments for the operator used. + // Many operators have arguments defining their specific behavior. This map contains those arguments. + Arguments() map[string]interface{} + // Identifiers returns a list of identifiers used by this plan. Identifiers used by this part of the plan. + // These can be both identifiers introduced by you, or automatically generated. + Identifiers() []string + // Children returns zero or more child plans. A plan is a tree, where each child is another plan. + // The children are where this part of the plan gets its input records - unless this is an operator that + // introduces new records on its own. + Children() []Plan +} + +// ProfiledPlan is the same as a regular Plan - except this plan has been executed, meaning it also +// contains detailed information about how much work each step of the plan incurred on the database. +type ProfiledPlan interface { + // Operator returns the operation this plan is performing. + Operator() string + // Arguments returns the arguments for the operator used. + // Many operators have arguments defining their specific behavior. This map contains those arguments. + Arguments() map[string]interface{} + // Identifiers returns a list of identifiers used by this plan. Identifiers used by this part of the plan. + // These can be both identifiers introduced by you, or automatically generated. + Identifiers() []string + // DbHits returns the number of times this part of the plan touched the underlying data stores/ + DbHits() int64 + // Records returns the number of records this part of the plan produced. + Records() int64 + // Children returns zero or more child plans. A plan is a tree, where each child is another plan. + // The children are where this part of the plan gets its input records - unless this is an operator that + // introduces new records on its own. + Children() []ProfiledPlan + PageCacheMisses() int64 + PageCacheHits() int64 + PageCacheHitRatio() float64 + Time() int64 +} + +// Notification represents notifications generated when executing a statement. +// A notification can be visualized in a client pinpointing problems or other information about the statement. +type Notification interface { + // Code returns a notification code for the discovered issue of this notification. + Code() string + // Title returns a short summary of this notification. + Title() string + // Description returns a longer description of this notification. + Description() string + // Position returns the position in the statement where this notification points to. + // Not all notifications have a unique position to point to and in that case the position would be set to nil. + Position() InputPosition + // Severity returns the severity level of this notification. + Severity() string +} + +// InputPosition contains information about a specific position in a statement +type InputPosition interface { + // Offset returns the character offset referred to by this position; offset numbers start at 0. + Offset() int + // Line returns the line number referred to by this position; line numbers start at 1. + Line() int + // Column returns the column number referred to by this position; column numbers start at 1. + Column() int +} + +type resultSummary struct { + sum *db.Summary + cypher string + params map[string]interface{} +} + +func (s *resultSummary) Agent() string { + return s.sum.Agent +} + +func (s *resultSummary) ProtocolVersion() db.ProtocolVersion { + return db.ProtocolVersion{ + Major: s.sum.Major, + Minor: s.sum.Minor, + } +} + +func (s *resultSummary) Server() ServerInfo { + return s +} + +func (s *resultSummary) Address() string { + return s.sum.ServerName +} + +func (s *resultSummary) Version() string { + return s.sum.Agent +} + +func (s *resultSummary) Statement() Statement { + return s +} + +func (s *resultSummary) Query() Query { + return s +} + +func (s *resultSummary) StatementType() StatementType { + return StatementType(s.sum.StmntType) +} + +func (s *resultSummary) Text() string { + return s.cypher +} + +func (s *resultSummary) Params() map[string]interface{} { + return s.Parameters() +} + +func (s *resultSummary) Parameters() map[string]interface{} { + return s.params +} + +func (s *resultSummary) Counters() Counters { + return s +} + +func (s *resultSummary) ContainsUpdates() bool { + return len(s.sum.Counters) > 0 +} + +func (s *resultSummary) getCounter(n string) int { + if s.sum.Counters == nil { + return 0 + } + return s.sum.Counters[n] +} + +func (s *resultSummary) SystemUpdates() int { + return s.getCounter(db.SystemUpdates) +} + +func (s *resultSummary) ContainsSystemUpdates() bool { + if s.sum.ContainsSystemUpdates != nil { + return *s.sum.ContainsSystemUpdates + } + return s.getCounter(db.SystemUpdates) > 0 +} + +func (s *resultSummary) NodesCreated() int { + return s.getCounter(db.NodesCreated) +} + +func (s *resultSummary) NodesDeleted() int { + return s.getCounter(db.NodesDeleted) +} + +func (s *resultSummary) RelationshipsCreated() int { + return s.getCounter(db.RelationshipsCreated) +} + +func (s *resultSummary) RelationshipsDeleted() int { + return s.getCounter(db.RelationshipsDeleted) +} + +func (s *resultSummary) PropertiesSet() int { + return s.getCounter(db.PropertiesSet) +} + +func (s *resultSummary) LabelsAdded() int { + return s.getCounter(db.LabelsAdded) +} + +func (s *resultSummary) LabelsRemoved() int { + return s.getCounter(db.LabelsRemoved) +} + +func (s *resultSummary) IndexesAdded() int { + return s.getCounter(db.IndexesAdded) +} + +func (s *resultSummary) IndexesRemoved() int { + return s.getCounter(db.IndexesRemoved) +} + +func (s *resultSummary) ConstraintsAdded() int { + return s.getCounter(db.ConstraintsAdded) +} + +func (s *resultSummary) ConstraintsRemoved() int { + return s.getCounter(db.ConstraintsRemoved) +} + +func (s *resultSummary) ResultAvailableAfter() time.Duration { + return time.Duration(s.sum.TFirst) * time.Millisecond +} + +func (s *resultSummary) ResultConsumedAfter() time.Duration { + return time.Duration(s.sum.TLast) * time.Millisecond +} + +func (s *resultSummary) Plan() Plan { + if s.sum.Plan == nil { + return nil + } + return &plan{plan: s.sum.Plan} +} + +func (s *resultSummary) Database() DatabaseInfo { + database := s.sum.Database + if database == "" { + return nil + } + return &databaseInfo{name: database} +} + +type databaseInfo struct { + name string +} + +func (d *databaseInfo) Name() string { + return d.name +} + +type plan struct { + plan *db.Plan +} + +func (p *plan) Operator() string { + return p.plan.Operator +} + +func (p *plan) Arguments() map[string]interface{} { + return p.plan.Arguments +} + +func (p *plan) Identifiers() []string { + return p.plan.Identifiers +} + +func (p *plan) Children() []Plan { + children := make([]Plan, len(p.plan.Children)) + for i, c := range p.plan.Children { + children[i] = &plan{plan: &c} + } + return children +} + +func (s *resultSummary) Profile() ProfiledPlan { + if s.sum.ProfiledPlan == nil { + return nil + } + return &profile{profile: s.sum.ProfiledPlan} +} + +type profile struct { + profile *db.ProfiledPlan +} + +func (p *profile) String() string { + return fmt.Sprintf("%v", *p.profile) +} + +func (p *profile) Operator() string { + return p.profile.Operator +} + +func (p *profile) Arguments() map[string]interface{} { + return p.profile.Arguments +} + +func (p *profile) Identifiers() []string { + return p.profile.Identifiers +} + +func (p *profile) DbHits() int64 { + return p.profile.DbHits +} + +func (p *profile) Records() int64 { + return p.profile.Records +} + +func (p *profile) Children() []ProfiledPlan { + children := make([]ProfiledPlan, len(p.profile.Children)) + for i, c := range p.profile.Children { + child := c + children[i] = &profile{profile: &child} + } + return children +} + +func (p *profile) PageCacheMisses() int64 { + return p.profile.PageCacheMisses +} + +func (p *profile) PageCacheHits() int64 { + return p.profile.PageCacheHits +} + +func (p *profile) PageCacheHitRatio() float64 { + return p.profile.PageCacheHitRatio +} + +func (p *profile) Time() int64 { + return p.profile.Time +} + +func (s *resultSummary) Notifications() []Notification { + if s.sum.Notifications == nil { + return nil + } + notifications := make([]Notification, len(s.sum.Notifications)) + for i := range s.sum.Notifications { + notifications[i] = ¬ification{notification: &s.sum.Notifications[i]} + } + return notifications +} + +type notification struct { + notification *db.Notification +} + +func (n *notification) Code() string { + return n.notification.Code +} + +func (n *notification) Title() string { + return n.notification.Title +} + +func (n *notification) Description() string { + return n.notification.Description +} + +func (n *notification) Severity() string { + return n.notification.Severity +} + +func (n *notification) Position() InputPosition { + if n.notification.Position == nil { + return nil + } + return n +} + +func (n *notification) Offset() int { + return n.notification.Position.Offset +} +func (n *notification) Column() int { + return n.notification.Position.Column +} +func (n *notification) Line() int { + return n.notification.Position.Line +} diff --git a/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/session.go b/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/session.go new file mode 100644 index 0000000..81b30b9 --- /dev/null +++ b/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/session.go @@ -0,0 +1,529 @@ +/* + * 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 +} diff --git a/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/transaction.go b/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/transaction.go new file mode 100644 index 0000000..5636e8f --- /dev/null +++ b/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/transaction.go @@ -0,0 +1,131 @@ +/* + * 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() + } +} diff --git a/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/transaction_config.go b/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/transaction_config.go new file mode 100644 index 0000000..d4b3a87 --- /dev/null +++ b/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/transaction_config.go @@ -0,0 +1,70 @@ +/* + * 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 + } +} diff --git a/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/useragent.go b/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/useragent.go new file mode 100644 index 0000000..79ea25a --- /dev/null +++ b/server/vendor/github.com/neo4j/neo4j-go-driver/v4/neo4j/useragent.go @@ -0,0 +1,22 @@ +/* + * 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" diff --git a/server/vendor/modules.txt b/server/vendor/modules.txt index 54efd2e..41227a6 100644 --- a/server/vendor/modules.txt +++ b/server/vendor/modules.txt @@ -37,7 +37,7 @@ github.com/go-playground/universal-translator # github.com/go-playground/validator/v10 v10.14.1 ## explicit; go 1.18 github.com/go-playground/validator/v10 -# github.com/golang/protobuf v1.5.0 +# github.com/golang/protobuf v1.5.2 ## explicit; go 1.9 github.com/golang/protobuf/proto # github.com/json-iterator/go v1.1.12 @@ -68,6 +68,19 @@ github.com/modern-go/concurrent # github.com/modern-go/reflect2 v1.0.2 ## explicit; go 1.12 github.com/modern-go/reflect2 +# github.com/neo4j/neo4j-go-driver/v4 v4.4.7 +## explicit; go 1.16 +github.com/neo4j/neo4j-go-driver/v4/neo4j +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/bolt +github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/connector +github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/packstream +github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/pool +github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/racingio +github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/retry +github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/router +github.com/neo4j/neo4j-go-driver/v4/neo4j/log # github.com/pkg/errors v0.9.1 ## explicit github.com/pkg/errors -- Gitee