neo4j-ruby-driver 6.2.1.beta.1 → 6.2.1.beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/neo4j/driver/bolt/connection.rb +72 -35
- data/lib/neo4j/driver/bolt/handshake.rb +14 -10
- data/lib/neo4j/driver/bolt/message/failure.rb +8 -8
- data/lib/neo4j/driver/bolt/message/record.rb +1 -1
- data/lib/neo4j/driver/bolt/message.rb +1 -1
- data/lib/neo4j/driver/bolt/pool.rb +2 -2
- data/lib/neo4j/driver/bolt/protocol/v44.rb +1 -1
- data/lib/neo4j/driver/bolt/record_buffer.rb +15 -8
- data/lib/neo4j/driver/bolt/wire.rb +4 -4
- data/lib/neo4j/driver/direct/connection_provider.rb +5 -1
- data/lib/neo4j/driver/driver.rb +2 -2
- data/lib/neo4j/driver/exceptions/no_such_record_exception.rb +1 -1
- data/lib/neo4j/driver/internal/duration_normalizer.rb +4 -2
- data/lib/neo4j/driver/internal/validator.rb +7 -3
- data/lib/neo4j/driver/packstream/markers.rb +11 -11
- data/lib/neo4j/driver/packstream/packer.rb +5 -3
- data/lib/neo4j/driver/packstream/unpacker.rb +6 -4
- data/lib/neo4j/driver/record.rb +3 -9
- data/lib/neo4j/driver/result.rb +12 -12
- data/lib/neo4j/driver/routing/load_balancer.rb +29 -13
- data/lib/neo4j/driver/routing/routing_table.rb +1 -1
- data/lib/neo4j/driver/session.rb +72 -70
- data/lib/neo4j/driver/summary/database_info.rb +1 -1
- data/lib/neo4j/driver/summary/plan.rb +1 -1
- data/lib/neo4j/driver/summary/result_summary.rb +2 -0
- data/lib/neo4j/driver/transaction.rb +13 -10
- data/lib/neo4j/driver/types/duration.rb +6 -6
- data/lib/neo4j/driver/types/entity.rb +40 -0
- data/lib/neo4j/driver/types/local_date_time.rb +46 -21
- data/lib/neo4j/driver/types/local_time.rb +13 -5
- data/lib/neo4j/driver/types/node.rb +5 -14
- data/lib/neo4j/driver/types/offset_time.rb +16 -9
- data/lib/neo4j/driver/types/path.rb +2 -2
- data/lib/neo4j/driver/types/point.rb +4 -4
- data/lib/neo4j/driver/types/relationship.rb +6 -15
- data/lib/neo4j/driver/types/temporal_value.rb +3 -2
- data/lib/neo4j/driver/types/unbound_relationship.rb +5 -10
- data/lib/neo4j/driver/version.rb +1 -1
- data/lib/neo4j/driver.rb +0 -1
- metadata +4 -72
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 02167af06d7572708bb866aa09a7cf03492c3ea5ff78601db252a874e30c0f16
|
|
4
|
+
data.tar.gz: 5321a39b13e65469393f5485646987e178e68f2bcc9527884a087e64775096e4
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2fb5b47bb808deadef7fcd939ab829e0e5a9b7f13ae7808a0b128a55236b874b112d9385e471f3bb098032a8185bcc039280a692d2d0e6aed213c843045cf420
|
|
7
|
+
data.tar.gz: 62c171b7b0a7f5efafa4905ffc6b200f1d3d59bd06a2f4d24fdedfaefcd61813239fcb784e5eb4b844d085147594f73f5731571d320dfea1c9dc2d1b3d69bca3
|
|
@@ -15,7 +15,7 @@ module Neo4j
|
|
|
15
15
|
# everything into the shared inbox queue for fetch_response to drain.
|
|
16
16
|
# (A streaming request could register a record-routing handler instead.)
|
|
17
17
|
ResponseCollector = Struct.new(:inbox) do
|
|
18
|
-
def on_record(message)
|
|
18
|
+
def on_record(message) = inbox.push(message)
|
|
19
19
|
def on_success(message) = inbox.push(message)
|
|
20
20
|
def on_failure(message) = inbox.push(message)
|
|
21
21
|
def on_ignored(message) = inbox.push(message)
|
|
@@ -96,8 +96,8 @@ module Neo4j
|
|
|
96
96
|
# stopped on close. Drives #advance and routes via the wire.
|
|
97
97
|
@reader = nil
|
|
98
98
|
@reader_mutex = Mutex.new
|
|
99
|
-
@reader_cv = ConditionVariable.new
|
|
100
|
-
@quiescent_cv = ConditionVariable.new
|
|
99
|
+
@reader_cv = ConditionVariable.new # wakes the reader: a reply is expected
|
|
100
|
+
@quiescent_cv = ConditionVariable.new # wakes drainers: in_flight hit 0
|
|
101
101
|
@reader_stopped = false
|
|
102
102
|
@broken_error = nil # set by failure fan-out; raised to inbox poppers
|
|
103
103
|
@server_version = nil
|
|
@@ -129,25 +129,23 @@ module Neo4j
|
|
|
129
129
|
# sharing one budget) wins over this connection's own.
|
|
130
130
|
@read_deadline = deadline || acquisition_deadline
|
|
131
131
|
resolved_addresses.each do |host, port|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
discard_socket
|
|
150
|
-
end
|
|
132
|
+
open_socket(host, port)
|
|
133
|
+
perform_handshake
|
|
134
|
+
perform_hello
|
|
135
|
+
@read_deadline = nil
|
|
136
|
+
@created_at = current_monotonic
|
|
137
|
+
# Connection is READY: hand steady-state reads to the dedicated
|
|
138
|
+
# reader. (Handshake/hello above read synchronously via
|
|
139
|
+
# fetch_response, so a failed connect never spawns a reader.)
|
|
140
|
+
start_reader
|
|
141
|
+
return self
|
|
142
|
+
rescue Exceptions::AuthenticationException
|
|
143
|
+
# Auth is the same regardless of which address we hit — fail fast.
|
|
144
|
+
discard_socket
|
|
145
|
+
raise
|
|
146
|
+
rescue Exceptions::ServiceUnavailableException, IOError, SystemCallError => e
|
|
147
|
+
last_error = e
|
|
148
|
+
discard_socket
|
|
151
149
|
end
|
|
152
150
|
|
|
153
151
|
raise Exceptions::ServiceUnavailableException, 'No addresses to connect to' if last_error.nil?
|
|
@@ -246,12 +244,21 @@ module Neo4j
|
|
|
246
244
|
begin
|
|
247
245
|
@wire&.enqueue(Message.goodbye, @collector)
|
|
248
246
|
bytes = @wire&.take_outbound
|
|
249
|
-
|
|
247
|
+
if bytes && !bytes.empty?
|
|
248
|
+
@write_mutex.synchronize do
|
|
249
|
+
@socket.write(bytes)
|
|
250
|
+
@socket.flush
|
|
251
|
+
end
|
|
252
|
+
end
|
|
250
253
|
rescue StandardError
|
|
251
254
|
# closing anyway
|
|
252
255
|
end
|
|
253
256
|
stop_reader
|
|
254
|
-
|
|
257
|
+
begin
|
|
258
|
+
@socket&.close
|
|
259
|
+
rescue StandardError
|
|
260
|
+
nil
|
|
261
|
+
end
|
|
255
262
|
end
|
|
256
263
|
|
|
257
264
|
def closed?
|
|
@@ -521,7 +528,7 @@ module Neo4j
|
|
|
521
528
|
response = fetch_response
|
|
522
529
|
case response
|
|
523
530
|
when Message::Success then break # PULL summary — end of stream
|
|
524
|
-
when Message::Record
|
|
531
|
+
when Message::Record then row ||= fields.zip(response.fields).to_h
|
|
525
532
|
else response.assert_success! # FAILURE / IGNORED — raises
|
|
526
533
|
end
|
|
527
534
|
end
|
|
@@ -663,7 +670,11 @@ module Neo4j
|
|
|
663
670
|
|
|
664
671
|
def discard_socket
|
|
665
672
|
stop_reader
|
|
666
|
-
|
|
673
|
+
begin
|
|
674
|
+
@socket&.close
|
|
675
|
+
rescue StandardError
|
|
676
|
+
nil
|
|
677
|
+
end
|
|
667
678
|
@socket = nil
|
|
668
679
|
# Reset all per-attempt I/O state so a retry on the next address (or a
|
|
669
680
|
# later reset!/drain loop) doesn't carry a phantom in-flight request or
|
|
@@ -709,7 +720,8 @@ module Neo4j
|
|
|
709
720
|
end
|
|
710
721
|
rescue IOError, SystemCallError => e
|
|
711
722
|
fail_broken(Exceptions::ServiceUnavailableException.new(
|
|
712
|
-
"Connection to #{@address || @uri} broken: #{e.class}: #{e.message}"
|
|
723
|
+
"Connection to #{@address || @uri} broken: #{e.class}: #{e.message}"
|
|
724
|
+
))
|
|
713
725
|
end
|
|
714
726
|
|
|
715
727
|
# The dedicated reader: the sole socket reader for this connection's
|
|
@@ -779,10 +791,18 @@ module Neo4j
|
|
|
779
791
|
# Wake the parked reader (@reader_cv) and any drainer blocked in
|
|
780
792
|
# #wait_quiescent (@quiescent_cv) — stopping is a terminal transition
|
|
781
793
|
# they must observe, else a concurrent reset!/fetch_all/alive? hangs.
|
|
782
|
-
@reader_mutex.synchronize
|
|
794
|
+
@reader_mutex.synchronize do
|
|
795
|
+
@reader_stopped = true
|
|
796
|
+
@reader_cv.broadcast
|
|
797
|
+
@quiescent_cv.broadcast
|
|
798
|
+
end
|
|
783
799
|
return unless reader
|
|
784
800
|
|
|
785
|
-
|
|
801
|
+
begin
|
|
802
|
+
@socket&.close
|
|
803
|
+
rescue StandardError
|
|
804
|
+
nil
|
|
805
|
+
end
|
|
786
806
|
reader.join unless reader == Thread.current
|
|
787
807
|
end
|
|
788
808
|
|
|
@@ -814,7 +834,11 @@ module Neo4j
|
|
|
814
834
|
end
|
|
815
835
|
|
|
816
836
|
def mark_closed_broken
|
|
817
|
-
|
|
837
|
+
begin
|
|
838
|
+
@socket&.close
|
|
839
|
+
rescue StandardError
|
|
840
|
+
nil
|
|
841
|
+
end
|
|
818
842
|
@closed = true
|
|
819
843
|
fire_on_close
|
|
820
844
|
end
|
|
@@ -926,10 +950,16 @@ module Neo4j
|
|
|
926
950
|
# test_should_fail_when_acquisition_timeout_is_reached_first, where
|
|
927
951
|
# acquisition 2s < connection 720s).
|
|
928
952
|
connect_timeout = [timeout&.to_f, remaining_read_budget].compact.min
|
|
929
|
-
tcp_socket =
|
|
953
|
+
tcp_socket = if connect_timeout
|
|
954
|
+
Socket.tcp(bare_host, port,
|
|
955
|
+
connect_timeout: connect_timeout)
|
|
956
|
+
else
|
|
957
|
+
TCPSocket.new(bare_host, port)
|
|
958
|
+
end
|
|
930
959
|
rescue SystemCallError, SocketError => e
|
|
931
960
|
raise Exceptions::ServiceUnavailableException,
|
|
932
|
-
"Unable to connect to #{format_address(host,
|
|
961
|
+
"Unable to connect to #{format_address(host,
|
|
962
|
+
port)}, ensure the database is running and that there is a working network connection to it. (#{e.message})"
|
|
933
963
|
else
|
|
934
964
|
@address = format_address(host, port)
|
|
935
965
|
tcp_socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
|
|
@@ -961,11 +991,19 @@ module Neo4j
|
|
|
961
991
|
ssl.post_connection_check(hostname) if tls.verify_hostname?
|
|
962
992
|
ssl
|
|
963
993
|
rescue OpenSSL::SSL::SSLError => e
|
|
964
|
-
|
|
994
|
+
begin
|
|
995
|
+
tcp_socket.close
|
|
996
|
+
rescue StandardError
|
|
997
|
+
nil
|
|
998
|
+
end
|
|
965
999
|
raise Exceptions::SecurityException,
|
|
966
1000
|
"TLS handshake to #{format_address(hostname, port)} failed: #{e.message}"
|
|
967
1001
|
rescue SystemCallError, IOError => e
|
|
968
|
-
|
|
1002
|
+
begin
|
|
1003
|
+
tcp_socket.close
|
|
1004
|
+
rescue StandardError
|
|
1005
|
+
nil
|
|
1006
|
+
end
|
|
969
1007
|
raise Exceptions::ServiceUnavailableException,
|
|
970
1008
|
"Connection lost during TLS handshake to #{format_address(hostname, port)}: #{e.message}"
|
|
971
1009
|
end
|
|
@@ -1047,7 +1085,6 @@ module Neo4j
|
|
|
1047
1085
|
seconds = hints && hints[:'connection.recv_timeout_seconds']
|
|
1048
1086
|
@recv_timeout = seconds if seconds&.positive?
|
|
1049
1087
|
end
|
|
1050
|
-
|
|
1051
1088
|
end
|
|
1052
1089
|
end
|
|
1053
1090
|
end
|
|
@@ -38,9 +38,9 @@ module Neo4j
|
|
|
38
38
|
# DISCARD_ALL, single-DB, no routing in HELLO).
|
|
39
39
|
SLOTS = [
|
|
40
40
|
MANIFEST_SENTINEL,
|
|
41
|
-
0x00_08_08_05,
|
|
42
|
-
0x00_02_04_04,
|
|
43
|
-
0x00_00_00_03
|
|
41
|
+
0x00_08_08_05, # 5.0–5.8
|
|
42
|
+
0x00_02_04_04, # 4.2–4.4
|
|
43
|
+
0x00_00_00_03 # 3.0
|
|
44
44
|
].freeze
|
|
45
45
|
|
|
46
46
|
# Versions we'd accept from a manifest, highest-preference first.
|
|
@@ -91,15 +91,19 @@ module Neo4j
|
|
|
91
91
|
@socket.flush
|
|
92
92
|
|
|
93
93
|
server_reply = read_int32!('Server closed the connection during handshake')
|
|
94
|
-
|
|
95
|
-
|
|
94
|
+
if server_reply.zero?
|
|
95
|
+
raise Exceptions::ServiceUnavailableException,
|
|
96
|
+
'Server does not support any of the proposed Bolt versions'
|
|
97
|
+
end
|
|
96
98
|
|
|
97
99
|
# An HTTP server on the Bolt port replies with an HTTP status
|
|
98
100
|
# line; its first four bytes spell "HTTP". Mirror Java's helpful
|
|
99
101
|
# error instead of reporting a bogus protocol version.
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
102
|
+
if server_reply == HTTP_REPLY
|
|
103
|
+
raise Exceptions::ClientException,
|
|
104
|
+
'Server responded HTTP. Make sure you are not trying to connect to the http ' \
|
|
105
|
+
'endpoint (HTTP defaults to port 7474 whereas BOLT defaults to port 7687)'
|
|
106
|
+
end
|
|
103
107
|
|
|
104
108
|
server_reply == MANIFEST_SENTINEL ? negotiate_manifest : server_reply
|
|
105
109
|
end
|
|
@@ -170,8 +174,8 @@ module Neo4j
|
|
|
170
174
|
buf = String.new(encoding: Encoding::BINARY)
|
|
171
175
|
while buf.bytesize < n
|
|
172
176
|
case (chunk = @socket.read_nonblock(n - buf.bytesize, exception: false))
|
|
173
|
-
when :wait_readable then
|
|
174
|
-
when :wait_writable then
|
|
177
|
+
when :wait_readable then @socket.wait_readable(remaining_budget) || return
|
|
178
|
+
when :wait_writable then @socket.wait_writable(remaining_budget) || return
|
|
175
179
|
when nil then return
|
|
176
180
|
else buf << chunk
|
|
177
181
|
end
|
|
@@ -18,14 +18,14 @@ module Neo4j
|
|
|
18
18
|
# Code-prefix → driver exception class. Order matters: more specific
|
|
19
19
|
# patterns must come first.
|
|
20
20
|
EXCEPTION_FOR_CODE = [
|
|
21
|
-
[
|
|
22
|
-
[
|
|
23
|
-
[
|
|
24
|
-
[
|
|
25
|
-
[
|
|
26
|
-
[
|
|
27
|
-
[
|
|
28
|
-
[
|
|
21
|
+
[/^Neo\.ClientError\.Security\.Unauthorized/, Exceptions::AuthenticationException],
|
|
22
|
+
[/^Neo\.ClientError\.Security\.AuthorizationExpired/, Exceptions::AuthorizationExpiredException],
|
|
23
|
+
[/^Neo\.ClientError\.Security\.TokenExpired/, Exceptions::TokenExpiredException],
|
|
24
|
+
[/^Neo\.ClientError\.Security/, Exceptions::SecurityException],
|
|
25
|
+
[/^Neo\.ClientError\.Database\.DatabaseNotFound/, Exceptions::FatalDiscoveryException],
|
|
26
|
+
[/^Neo\.ClientError/, Exceptions::ClientException],
|
|
27
|
+
[/^Neo\.TransientError/, Exceptions::TransientException],
|
|
28
|
+
[/^Neo\.DatabaseError/, Exceptions::DatabaseException]
|
|
29
29
|
].freeze
|
|
30
30
|
|
|
31
31
|
# Two `TransientError` codes the server may send that are actually
|
|
@@ -17,7 +17,7 @@ module Neo4j
|
|
|
17
17
|
end
|
|
18
18
|
|
|
19
19
|
def assert_success!
|
|
20
|
-
raise Exceptions::ClientException,
|
|
20
|
+
raise Exceptions::ClientException, 'Unexpected RECORD where SUCCESS was expected'
|
|
21
21
|
end
|
|
22
22
|
|
|
23
23
|
# A RECORD is a streamed row, not a request summary — it never
|
|
@@ -27,7 +27,7 @@ module Neo4j
|
|
|
27
27
|
FAILURE = 0x7F
|
|
28
28
|
|
|
29
29
|
class << self
|
|
30
|
-
#
|
|
30
|
+
# NOTE: HELLO message building is now handled by protocol-specific handlers
|
|
31
31
|
# This method kept for backward compatibility but shouldn't be used directly
|
|
32
32
|
def hello(user_agent:, auth:, routing: nil)
|
|
33
33
|
extra = { user_agent: user_agent }
|
|
@@ -141,8 +141,8 @@ module Neo4j
|
|
|
141
141
|
bump(created: -1, leased: -1)
|
|
142
142
|
end
|
|
143
143
|
|
|
144
|
-
def shutdown(&
|
|
145
|
-
@stack.shutdown(&
|
|
144
|
+
def shutdown(&)
|
|
145
|
+
@stack.shutdown(&)
|
|
146
146
|
# Every connection is closed now — keep the metrics truthful so a
|
|
147
147
|
# post-close snapshot reports nothing live.
|
|
148
148
|
@metrics_lock.synchronize { @created = @leased = 0 }
|
|
@@ -39,12 +39,12 @@ module Neo4j
|
|
|
39
39
|
@low_watermark = low_watermark || [@high_watermark / 2, 1].max
|
|
40
40
|
@mutex = Mutex.new
|
|
41
41
|
@cv = ConditionVariable.new
|
|
42
|
-
@records = []
|
|
43
|
-
@has_more = true
|
|
44
|
-
@pull_in_flight = true
|
|
45
|
-
@ended = false
|
|
46
|
-
@error = nil
|
|
47
|
-
@summary = nil
|
|
42
|
+
@records = [] # incrementally filled by the reader, drained by the cursor
|
|
43
|
+
@has_more = true # server may have more records for this stream
|
|
44
|
+
@pull_in_flight = true # the first PULL is pipelined with RUN — promise unfulfilled
|
|
45
|
+
@ended = false # terminal SUCCESS/IGNORED seen → no more records
|
|
46
|
+
@error = nil # stream failed; re-raised to the cursor after buffered records
|
|
47
|
+
@summary = nil # terminating SUCCESS metadata
|
|
48
48
|
end
|
|
49
49
|
|
|
50
50
|
# --- Producer (reader) side -----------------------------------------
|
|
@@ -52,7 +52,10 @@ module Neo4j
|
|
|
52
52
|
# Append a decoded record and wake a cursor parked in #await. Never blocks
|
|
53
53
|
# (unbounded); the cursor's watermark bounds how much the server ships.
|
|
54
54
|
def push_record(record)
|
|
55
|
-
@mutex.synchronize
|
|
55
|
+
@mutex.synchronize do
|
|
56
|
+
@records.push(record)
|
|
57
|
+
@cv.broadcast
|
|
58
|
+
end
|
|
56
59
|
end
|
|
57
60
|
|
|
58
61
|
# The current batch's terminal SUCCESS arrived — resolve the promise.
|
|
@@ -60,7 +63,11 @@ module Neo4j
|
|
|
60
63
|
# so the cursor may issue the next once it drains past the low watermark.
|
|
61
64
|
# Wake a cursor parked in #await waiting for exactly this.
|
|
62
65
|
def batch_complete(has_more:)
|
|
63
|
-
@mutex.synchronize
|
|
66
|
+
@mutex.synchronize do
|
|
67
|
+
@has_more = has_more
|
|
68
|
+
@pull_in_flight = false
|
|
69
|
+
@cv.broadcast
|
|
70
|
+
end
|
|
64
71
|
end
|
|
65
72
|
|
|
66
73
|
# Final batch (terminating SUCCESS without has_more, or IGNORED): stash the
|
|
@@ -34,9 +34,9 @@ module Neo4j
|
|
|
34
34
|
@protocol = protocol
|
|
35
35
|
@packer = PackStream::Packer.new(@protocol)
|
|
36
36
|
@protocol.configure_packer(@packer)
|
|
37
|
-
@outbound = binary_string
|
|
38
|
-
@inbound = binary_string
|
|
39
|
-
@message = binary_string
|
|
37
|
+
@outbound = binary_string # consumer-only (enqueue/take_outbound)
|
|
38
|
+
@inbound = binary_string # reader-only: received bytes not yet parsed
|
|
39
|
+
@message = binary_string # reader-only: chunks of the in-progress message
|
|
40
40
|
# Response-ordering FIFO. Bolt replies in request order, so each sent
|
|
41
41
|
# request pushes the handler that will receive its response(s); the
|
|
42
42
|
# front handler always owns the next reply. A handler is any response
|
|
@@ -187,7 +187,7 @@ module Neo4j
|
|
|
187
187
|
def register_hydration_handlers(unpacker)
|
|
188
188
|
unpacker.register_hydration_handler(Message::SUCCESS) { |fields| Message::Success.new(fields[0] || {}) }
|
|
189
189
|
unpacker.register_hydration_handler(Message::FAILURE) { |fields| Message::Failure.new(fields[0] || {}) }
|
|
190
|
-
unpacker.register_hydration_handler(Message::RECORD)
|
|
190
|
+
unpacker.register_hydration_handler(Message::RECORD) { |fields| Message::Record.new(fields[0] || []) }
|
|
191
191
|
unpacker.register_hydration_handler(Message::IGNORED) { |_| Message::Ignored.new }
|
|
192
192
|
|
|
193
193
|
# Signature 0x4E - Node
|
data/lib/neo4j/driver/driver.rb
CHANGED
|
@@ -46,13 +46,13 @@ module Neo4j
|
|
|
46
46
|
|
|
47
47
|
begin
|
|
48
48
|
result = yield session
|
|
49
|
-
rescue =>
|
|
49
|
+
rescue StandardError => e
|
|
50
50
|
# Block raised; preserve as primary, attach close-time failures
|
|
51
51
|
# as suppressed (Java try-with-resources semantics).
|
|
52
52
|
begin
|
|
53
53
|
session.close
|
|
54
54
|
rescue Exceptions::Neo4jException => close_error
|
|
55
|
-
|
|
55
|
+
e.add_suppressed(close_error) if e.respond_to?(:add_suppressed)
|
|
56
56
|
end
|
|
57
57
|
raise
|
|
58
58
|
else
|
|
@@ -6,7 +6,7 @@ module Neo4j
|
|
|
6
6
|
class NoSuchRecordException < RuntimeError
|
|
7
7
|
EMPTY = 'Cannot retrieve a single record, because this result is empty.'
|
|
8
8
|
TOO_MANY = 'Expected a result with a single record, but this result ' \
|
|
9
|
-
|
|
9
|
+
'contains at least one more. Ensure your query returns only one record.'
|
|
10
10
|
NO_MORE = 'No more records'
|
|
11
11
|
NO_PEEK_PAST = 'Cannot peek past the last record'
|
|
12
12
|
|
|
@@ -14,8 +14,10 @@ module Neo4j
|
|
|
14
14
|
# Check the sign before rounding: a tiny negative timeout
|
|
15
15
|
# (e.g. -0.4ms) would otherwise round to 0 and slip through.
|
|
16
16
|
ms = timeout.to_f * 1000
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
if ms.negative?
|
|
18
|
+
raise Exceptions::ClientException,
|
|
19
|
+
"Transaction timeout must not be negative, but was #{ms}ms"
|
|
20
|
+
end
|
|
19
21
|
|
|
20
22
|
ms.round
|
|
21
23
|
end
|
|
@@ -16,6 +16,7 @@ module Neo4j
|
|
|
16
16
|
|
|
17
17
|
def self.require_non_nil!(obj, message = nil)
|
|
18
18
|
raise ArgumentError, [message, "can't be nil"].compact.join(' ') if obj.nil?
|
|
19
|
+
|
|
19
20
|
obj
|
|
20
21
|
end
|
|
21
22
|
|
|
@@ -24,14 +25,17 @@ module Neo4j
|
|
|
24
25
|
# object carries its own text, so only bare strings are checked.
|
|
25
26
|
def self.require_query_text!(query)
|
|
26
27
|
raise ArgumentError, 'Cypher query text should not be null' if query.nil?
|
|
27
|
-
|
|
28
|
+
if query.is_a?(String) && query.strip.empty?
|
|
29
|
+
raise ArgumentError,
|
|
30
|
+
'Cypher query text should not be an empty string'
|
|
31
|
+
end
|
|
28
32
|
|
|
29
33
|
query
|
|
30
34
|
end
|
|
31
35
|
|
|
32
36
|
def self.require_non_nil_credentials!(username, password)
|
|
33
|
-
require_non_nil! username,
|
|
34
|
-
require_non_nil! password,
|
|
37
|
+
require_non_nil! username, 'Username'
|
|
38
|
+
require_non_nil! password, 'Password'
|
|
35
39
|
end
|
|
36
40
|
end
|
|
37
41
|
end
|
|
@@ -7,37 +7,37 @@ module Neo4j
|
|
|
7
7
|
# See https://neo4j.com/docs/bolt/current/packstream/ for the full table.
|
|
8
8
|
module Markers
|
|
9
9
|
TINY_STRING = 0x80
|
|
10
|
-
TINY_LIST
|
|
11
|
-
TINY_MAP
|
|
10
|
+
TINY_LIST = 0x90
|
|
11
|
+
TINY_MAP = 0xA0
|
|
12
12
|
TINY_STRUCT = 0xB0
|
|
13
13
|
|
|
14
|
-
NULL
|
|
14
|
+
NULL = 0xC0
|
|
15
15
|
FLOAT_64 = 0xC1
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
BOOLEAN_FALSE = 0xC2
|
|
17
|
+
BOOLEAN_TRUE = 0xC3
|
|
18
18
|
|
|
19
|
-
INT_8
|
|
19
|
+
INT_8 = 0xC8
|
|
20
20
|
INT_16 = 0xC9
|
|
21
21
|
INT_32 = 0xCA
|
|
22
22
|
INT_64 = 0xCB
|
|
23
23
|
|
|
24
|
-
BYTES_8
|
|
24
|
+
BYTES_8 = 0xCC
|
|
25
25
|
BYTES_16 = 0xCD
|
|
26
26
|
BYTES_32 = 0xCE
|
|
27
27
|
|
|
28
|
-
STRING_8
|
|
28
|
+
STRING_8 = 0xD0
|
|
29
29
|
STRING_16 = 0xD1
|
|
30
30
|
STRING_32 = 0xD2
|
|
31
31
|
|
|
32
|
-
LIST_8
|
|
32
|
+
LIST_8 = 0xD4
|
|
33
33
|
LIST_16 = 0xD5
|
|
34
34
|
LIST_32 = 0xD6
|
|
35
35
|
|
|
36
|
-
MAP_8
|
|
36
|
+
MAP_8 = 0xD8
|
|
37
37
|
MAP_16 = 0xD9
|
|
38
38
|
MAP_32 = 0xDA
|
|
39
39
|
|
|
40
|
-
STRUCT_8
|
|
40
|
+
STRUCT_8 = 0xDC
|
|
41
41
|
STRUCT_16 = 0xDD
|
|
42
42
|
|
|
43
43
|
# PackStream V2 (Bolt 6.1+): a UUID is a top-level value — the marker
|
|
@@ -110,7 +110,7 @@ module Neo4j
|
|
|
110
110
|
end
|
|
111
111
|
|
|
112
112
|
def pack_boolean(value)
|
|
113
|
-
@buffer << [value ?
|
|
113
|
+
@buffer << [value ? BOOLEAN_TRUE : BOOLEAN_FALSE].pack('C')
|
|
114
114
|
end
|
|
115
115
|
|
|
116
116
|
def pack_integer(value)
|
|
@@ -246,10 +246,12 @@ module Neo4j
|
|
|
246
246
|
# matching hydration handlers in Bolt::Connection.
|
|
247
247
|
if @use_utc_datetime
|
|
248
248
|
epoch_seconds = value.to_i
|
|
249
|
-
offset_sig
|
|
249
|
+
offset_sig = 0x49
|
|
250
|
+
zone_sig = 0x69
|
|
250
251
|
else
|
|
251
252
|
epoch_seconds = value.to_i + tz_offset
|
|
252
|
-
offset_sig
|
|
253
|
+
offset_sig = 0x46
|
|
254
|
+
zone_sig = 0x66
|
|
253
255
|
end
|
|
254
256
|
zone_name = named_zone_for(value)
|
|
255
257
|
|
|
@@ -29,7 +29,8 @@ module Neo4j
|
|
|
29
29
|
def unpack_uuid_value
|
|
30
30
|
hex = read_bytes(16).unpack1('H*')
|
|
31
31
|
Types::UUID.from_string(
|
|
32
|
-
"#{hex[0, 8]}-#{hex[8, 4]}-#{hex[12, 4]}-#{hex[16, 4]}-#{hex[20, 12]}"
|
|
32
|
+
"#{hex[0, 8]}-#{hex[8, 4]}-#{hex[12, 4]}-#{hex[16, 4]}-#{hex[20, 12]}"
|
|
33
|
+
)
|
|
33
34
|
end
|
|
34
35
|
|
|
35
36
|
# A ProtocolException (not a bare RuntimeError) so the failure reaches
|
|
@@ -49,9 +50,9 @@ module Neo4j
|
|
|
49
50
|
case marker
|
|
50
51
|
when NULL
|
|
51
52
|
nil
|
|
52
|
-
when
|
|
53
|
+
when BOOLEAN_FALSE
|
|
53
54
|
false
|
|
54
|
-
when
|
|
55
|
+
when BOOLEAN_TRUE
|
|
55
56
|
true
|
|
56
57
|
when FLOAT_64
|
|
57
58
|
read_bytes(8).unpack1('G')
|
|
@@ -159,7 +160,8 @@ module Neo4j
|
|
|
159
160
|
|
|
160
161
|
def read_bytes(n)
|
|
161
162
|
data = @io.read(n)
|
|
162
|
-
raise IOError,
|
|
163
|
+
raise IOError, 'Unexpected end of stream' if data.nil? || data.bytesize < n
|
|
164
|
+
|
|
163
165
|
data
|
|
164
166
|
end
|
|
165
167
|
end
|
data/lib/neo4j/driver/record.rb
CHANGED
|
@@ -14,13 +14,7 @@ module Neo4j
|
|
|
14
14
|
end
|
|
15
15
|
end
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
@keys
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
def values
|
|
22
|
-
@values
|
|
23
|
-
end
|
|
17
|
+
attr_reader :keys, :values
|
|
24
18
|
|
|
25
19
|
def [](key)
|
|
26
20
|
case key
|
|
@@ -41,8 +35,8 @@ module Neo4j
|
|
|
41
35
|
@map.dup
|
|
42
36
|
end
|
|
43
37
|
|
|
44
|
-
def each(&
|
|
45
|
-
@map.each(&
|
|
38
|
+
def each(&)
|
|
39
|
+
@map.each(&)
|
|
46
40
|
end
|
|
47
41
|
end
|
|
48
42
|
end
|