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
data/lib/neo4j/driver/result.rb
CHANGED
|
@@ -18,8 +18,8 @@ module Neo4j
|
|
|
18
18
|
on_summary: nil, on_release: nil)
|
|
19
19
|
@connection = connection
|
|
20
20
|
@keys = keys
|
|
21
|
-
@buffer = buffer
|
|
22
|
-
@handler = handler
|
|
21
|
+
@buffer = buffer # Bolt::RecordBuffer, filled by the reader
|
|
22
|
+
@handler = handler # the StreamHandler registered for this result's PULLs
|
|
23
23
|
@query_text = query_text
|
|
24
24
|
@parameters = parameters
|
|
25
25
|
@run_metadata = run_metadata
|
|
@@ -35,17 +35,17 @@ module Neo4j
|
|
|
35
35
|
# leaves the connection FAILED, so more wire traffic is invalid. nil for
|
|
36
36
|
# auto-commit results (no enclosing transaction).
|
|
37
37
|
@terminated_error = terminated_error
|
|
38
|
-
@failure = nil
|
|
39
|
-
@on_summary = on_summary
|
|
40
|
-
@on_release = on_release
|
|
41
|
-
@records = []
|
|
38
|
+
@failure = nil # this result's own classified stream failure, if any
|
|
39
|
+
@on_summary = on_summary # called with the built Summary when the stream ends in SUCCESS
|
|
40
|
+
@on_release = on_release # called once when the connection is no longer needed
|
|
41
|
+
@records = [] # records pulled into memory by #buffer (not by iteration)
|
|
42
42
|
@had_record = false # any RECORD arrived on this stream — drives the GQL outcome status
|
|
43
43
|
@summary = nil
|
|
44
|
-
@consumed = false
|
|
45
|
-
@discarded = false
|
|
46
|
-
@failed = false
|
|
44
|
+
@consumed = false # stream fully drained (terminal seen)
|
|
45
|
+
@discarded = false # records explicitly released; further access raises
|
|
46
|
+
@failed = false # stream ended with a server FAILURE; connection needs RESET
|
|
47
47
|
@cancelling = false # consume(): abandon the rest — DISCARD the next batch instead of PULL
|
|
48
|
-
@list_mode = false
|
|
48
|
+
@list_mode = false # to_a/list: pull all remaining in one PULL {n: -1}, ignoring fetch_size
|
|
49
49
|
@peeked_record = nil
|
|
50
50
|
end
|
|
51
51
|
|
|
@@ -98,11 +98,11 @@ module Neo4j
|
|
|
98
98
|
record
|
|
99
99
|
end
|
|
100
100
|
|
|
101
|
-
def each(&
|
|
101
|
+
def each(&)
|
|
102
102
|
raise Exceptions::ResultConsumedException if @discarded
|
|
103
103
|
return to_enum(:each) unless block_given?
|
|
104
104
|
|
|
105
|
-
|
|
105
|
+
yield(self.next) while has_next?
|
|
106
106
|
end
|
|
107
107
|
|
|
108
108
|
def to_a
|
|
@@ -40,9 +40,9 @@ module Neo4j
|
|
|
40
40
|
@clock = clock
|
|
41
41
|
@domain_name_resolver = domain_name_resolver
|
|
42
42
|
@routing_context = parse_routing_context(uri)
|
|
43
|
-
@pools = {}
|
|
44
|
-
@routing_tables = {}
|
|
45
|
-
@cursor = Hash.new(0)
|
|
43
|
+
@pools = {} # ServerAddress => ConnectionPool::TimedStack
|
|
44
|
+
@routing_tables = {} # database (str or nil) => RoutingTable
|
|
45
|
+
@cursor = Hash.new(0) # round-robin per (database, role)
|
|
46
46
|
# Per-server authorization-expired generation counters (see
|
|
47
47
|
# Connection#auth_epoch and Direct::ConnectionProvider). Bumped for a
|
|
48
48
|
# server when one of its connections reports AuthorizationExpired, so
|
|
@@ -79,8 +79,8 @@ module Neo4j
|
|
|
79
79
|
@ssr_lock.synchronize do
|
|
80
80
|
if conn.ssr_enabled?
|
|
81
81
|
@ssr_with -= 1 if @ssr_with.positive?
|
|
82
|
-
|
|
83
|
-
@ssr_without -= 1
|
|
82
|
+
elsif @ssr_without.positive?
|
|
83
|
+
@ssr_without -= 1
|
|
84
84
|
end
|
|
85
85
|
end
|
|
86
86
|
end
|
|
@@ -193,7 +193,8 @@ module Neo4j
|
|
|
193
193
|
# routing driver surfaced the wrong error type.
|
|
194
194
|
raise Exceptions::IllegalStateException, 'Driver is closed' if @closed
|
|
195
195
|
|
|
196
|
-
resolved = ensure_routing_table_is_fresh(nil, :read, bookmarks: bookmarks, imp_user: imp_user,
|
|
196
|
+
resolved = ensure_routing_table_is_fresh(nil, :read, bookmarks: bookmarks, imp_user: imp_user,
|
|
197
|
+
auth: auth).database
|
|
197
198
|
# Remember the authoritative name (Optimization:HomeDatabaseCache) so a
|
|
198
199
|
# later same-identity session can guess it and skip discovery.
|
|
199
200
|
cache_home_db(imp_user, auth, resolved)
|
|
@@ -236,7 +237,9 @@ module Neo4j
|
|
|
236
237
|
# server to re-auth. Provider-side, so it runs regardless of who
|
|
237
238
|
# owns the token; the manager notification is skipped for a
|
|
238
239
|
# per-session identity.
|
|
239
|
-
|
|
240
|
+
if error.is_a?(Exceptions::AuthorizationExpiredException)
|
|
241
|
+
@refresh_lock.synchronize { @auth_epochs[address] += 1 }
|
|
242
|
+
end
|
|
240
243
|
return false if session_scoped
|
|
241
244
|
|
|
242
245
|
@auth_manager.handle_security_exception(token, error)
|
|
@@ -316,7 +319,13 @@ module Neo4j
|
|
|
316
319
|
def close
|
|
317
320
|
@refresh_lock.synchronize do
|
|
318
321
|
@closed = true
|
|
319
|
-
@pools.each_value
|
|
322
|
+
@pools.each_value do |pool|
|
|
323
|
+
pool.shutdown do |conn|
|
|
324
|
+
conn.close
|
|
325
|
+
rescue StandardError
|
|
326
|
+
nil
|
|
327
|
+
end
|
|
328
|
+
end
|
|
320
329
|
@pools.clear
|
|
321
330
|
@routing_tables.clear
|
|
322
331
|
end
|
|
@@ -368,7 +377,11 @@ module Neo4j
|
|
|
368
377
|
@refresh_lock.synchronize do
|
|
369
378
|
@routing_tables.each_value { |table| table.forget(address) }
|
|
370
379
|
pool = @pools.delete(address)
|
|
371
|
-
pool&.shutdown
|
|
380
|
+
pool&.shutdown do |conn|
|
|
381
|
+
conn.close
|
|
382
|
+
rescue StandardError
|
|
383
|
+
nil
|
|
384
|
+
end
|
|
372
385
|
end
|
|
373
386
|
end
|
|
374
387
|
|
|
@@ -442,7 +455,7 @@ module Neo4j
|
|
|
442
455
|
raise Exceptions::ServiceUnavailableException.new(
|
|
443
456
|
"Unable to retrieve routing information for database #{database.inspect}: " \
|
|
444
457
|
"tried #{errors.length} router(s) without success" \
|
|
445
|
-
"#{
|
|
458
|
+
"#{" (last: #{last.message})" if last}",
|
|
446
459
|
suppressed: errors
|
|
447
460
|
)
|
|
448
461
|
end
|
|
@@ -529,7 +542,11 @@ module Neo4j
|
|
|
529
542
|
errors << e
|
|
530
543
|
# Connection (if any) is presumed dead; deactivate tears
|
|
531
544
|
# down the pool so the conn's `created` slot is reclaimed.
|
|
532
|
-
|
|
545
|
+
begin
|
|
546
|
+
conn&.close
|
|
547
|
+
rescue StandardError
|
|
548
|
+
nil
|
|
549
|
+
end
|
|
533
550
|
deactivate(router)
|
|
534
551
|
nil
|
|
535
552
|
rescue Exceptions::Neo4jException => e
|
|
@@ -674,7 +691,7 @@ module Neo4j
|
|
|
674
691
|
|
|
675
692
|
def symbolize(value)
|
|
676
693
|
case value
|
|
677
|
-
when Hash
|
|
694
|
+
when Hash then value.transform_keys(&:to_sym).transform_values { symbolize(it) }
|
|
678
695
|
when Array then value.map { symbolize(it) }
|
|
679
696
|
else value
|
|
680
697
|
end
|
|
@@ -683,7 +700,6 @@ module Neo4j
|
|
|
683
700
|
def max_pool_size
|
|
684
701
|
@options[:max_connection_pool_size] || Driver::DEFAULT_MAX_POOL_SIZE
|
|
685
702
|
end
|
|
686
|
-
|
|
687
703
|
end
|
|
688
704
|
end
|
|
689
705
|
end
|
|
@@ -109,7 +109,7 @@ module Neo4j
|
|
|
109
109
|
|
|
110
110
|
def servers_for(access_mode)
|
|
111
111
|
case access_mode
|
|
112
|
-
when :read
|
|
112
|
+
when :read then @readers
|
|
113
113
|
when :write then @writers
|
|
114
114
|
when :route then @routers
|
|
115
115
|
else raise ArgumentError, "Unknown access mode: #{access_mode.inspect}"
|
data/lib/neo4j/driver/session.rb
CHANGED
|
@@ -10,6 +10,7 @@ module Neo4j
|
|
|
10
10
|
# hold a connection — only its in-flight operation does.
|
|
11
11
|
class Session
|
|
12
12
|
include Internal::DurationNormalizer
|
|
13
|
+
|
|
13
14
|
def initialize(connection_provider, options = {}, clock: Internal::Clock.new)
|
|
14
15
|
@connection_provider = connection_provider
|
|
15
16
|
@options = options
|
|
@@ -21,7 +22,7 @@ module Neo4j
|
|
|
21
22
|
# The home database this session pinned after its first resolution
|
|
22
23
|
# (Optimization:HomeDatabaseCache); nil until then / for explicit-db sessions.
|
|
23
24
|
@pinned_database = nil
|
|
24
|
-
@used_guess = false
|
|
25
|
+
@used_guess = false # whether an operation optimistically guessed the home db
|
|
25
26
|
@bookmark_manager = options[:bookmark_manager]
|
|
26
27
|
# The bookmark snapshot we sent on the most recent BEGIN — used
|
|
27
28
|
# as `previous_bookmarks` when forwarding to the manager so it
|
|
@@ -31,12 +32,12 @@ module Neo4j
|
|
|
31
32
|
@bookmarks_used_on_begin = nil
|
|
32
33
|
|
|
33
34
|
# Initialize with provided bookmarks if any
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
35
|
+
return unless options[:bookmarks]
|
|
36
|
+
|
|
37
|
+
initial_bookmarks = options[:bookmarks]
|
|
38
|
+
initial_bookmarks = [initial_bookmarks] unless initial_bookmarks.is_a?(Enumerable)
|
|
39
|
+
initial_bookmarks.each do |bookmark|
|
|
40
|
+
@last_bookmarks << (bookmark.is_a?(Bookmark) ? bookmark : Bookmark.from(bookmark))
|
|
40
41
|
end
|
|
41
42
|
end
|
|
42
43
|
|
|
@@ -50,7 +51,10 @@ module Neo4j
|
|
|
50
51
|
end
|
|
51
52
|
|
|
52
53
|
raise Exceptions::ClientException, 'Session is closed' unless @open
|
|
53
|
-
|
|
54
|
+
if @transaction&.open?
|
|
55
|
+
raise Exceptions::ClientException,
|
|
56
|
+
'You cannot run a query directly on a session while a transaction is open'
|
|
57
|
+
end
|
|
54
58
|
|
|
55
59
|
drain_current_result
|
|
56
60
|
|
|
@@ -90,7 +94,7 @@ module Neo4j
|
|
|
90
94
|
retries_left = auto_commit_retries_enabled? ? 1 : 0
|
|
91
95
|
|
|
92
96
|
buffer = handler = run_response = nil
|
|
93
|
-
telemetry_sent = false
|
|
97
|
+
telemetry_sent = false # TELEMETRY 2 = auto-commit; sent once, never resent on a retry
|
|
94
98
|
telemetry_pending = false # its (opted-in) SUCCESS is still owed
|
|
95
99
|
loop do
|
|
96
100
|
# A fresh stream per attempt — a prior attempt's buffer saw the IGNORED.
|
|
@@ -160,10 +164,10 @@ module Neo4j
|
|
|
160
164
|
keys = (run_response.metadata[:fields] || run_response.metadata['fields'] || []).map(&:to_sym)
|
|
161
165
|
@current_result = Result.new(
|
|
162
166
|
connection, keys, buffer: buffer, handler: handler,
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
+
query_text: query, parameters: parameters, run_metadata: run_response.metadata,
|
|
168
|
+
fetch_size: fetch_size,
|
|
169
|
+
on_summary: method(:harvest_auto_commit_bookmark),
|
|
170
|
+
on_release: -> { @connection_provider.release(connection) }
|
|
167
171
|
)
|
|
168
172
|
end
|
|
169
173
|
|
|
@@ -171,7 +175,7 @@ module Neo4j
|
|
|
171
175
|
raise Exceptions::ClientException, 'Session is closed' unless @open
|
|
172
176
|
if @transaction&.open?
|
|
173
177
|
raise Exceptions::ClientException,
|
|
174
|
-
|
|
178
|
+
'You cannot begin a transaction on a session with an open transaction; either run from within the transaction or use a different session.'
|
|
175
179
|
end
|
|
176
180
|
|
|
177
181
|
drain_current_result
|
|
@@ -201,7 +205,7 @@ module Neo4j
|
|
|
201
205
|
# tx.commit to persist changes (matches Java driver semantics).
|
|
202
206
|
@transaction.rollback if @transaction.open?
|
|
203
207
|
result
|
|
204
|
-
rescue => e
|
|
208
|
+
rescue StandardError => e
|
|
205
209
|
@transaction.rollback if @transaction.open?
|
|
206
210
|
raise e
|
|
207
211
|
ensure
|
|
@@ -212,12 +216,12 @@ module Neo4j
|
|
|
212
216
|
end
|
|
213
217
|
end
|
|
214
218
|
|
|
215
|
-
def execute_read(timeout: nil, metadata: nil, &
|
|
216
|
-
execute_transaction(AccessMode::READ, timeout:, metadata:, &
|
|
219
|
+
def execute_read(timeout: nil, metadata: nil, &)
|
|
220
|
+
execute_transaction(AccessMode::READ, timeout:, metadata:, &)
|
|
217
221
|
end
|
|
218
222
|
|
|
219
|
-
def execute_write(timeout: nil, metadata: nil, &
|
|
220
|
-
execute_transaction(AccessMode::WRITE, timeout:, metadata:, &
|
|
223
|
+
def execute_write(timeout: nil, metadata: nil, &)
|
|
224
|
+
execute_transaction(AccessMode::WRITE, timeout:, metadata:, &)
|
|
221
225
|
end
|
|
222
226
|
|
|
223
227
|
# Close any pending result by asking the server to abandon
|
|
@@ -232,7 +236,7 @@ module Neo4j
|
|
|
232
236
|
|
|
233
237
|
pending_error = nil
|
|
234
238
|
begin
|
|
235
|
-
@transaction&.close
|
|
239
|
+
@transaction&.close # tx releases its own connection
|
|
236
240
|
if @current_result
|
|
237
241
|
connection = @current_result.connection
|
|
238
242
|
begin
|
|
@@ -244,7 +248,7 @@ module Neo4j
|
|
|
244
248
|
# the server's FAILED state; RESET so the next borrower starts
|
|
245
249
|
# from a clean READY state.
|
|
246
250
|
connection.reset! if @current_result.failed?
|
|
247
|
-
@current_result.discard!
|
|
251
|
+
@current_result.discard! # idempotent; releases the connection
|
|
248
252
|
end
|
|
249
253
|
ensure
|
|
250
254
|
@open = false
|
|
@@ -257,9 +261,7 @@ module Neo4j
|
|
|
257
261
|
@open
|
|
258
262
|
end
|
|
259
263
|
|
|
260
|
-
|
|
261
|
-
@last_bookmarks
|
|
262
|
-
end
|
|
264
|
+
attr_reader :last_bookmarks
|
|
263
265
|
|
|
264
266
|
def update_bookmarks(bookmarks)
|
|
265
267
|
# Replace bookmarks (don't accumulate) — matches Java driver behavior.
|
|
@@ -447,19 +449,19 @@ module Neo4j
|
|
|
447
449
|
result.buffer
|
|
448
450
|
rescue Exceptions::Neo4jException
|
|
449
451
|
connection.reset!
|
|
450
|
-
result.discard!
|
|
452
|
+
result.discard! # idempotent; routes through Result#release_connection
|
|
451
453
|
raise
|
|
452
454
|
end
|
|
453
455
|
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
456
|
+
return unless result.failed?
|
|
457
|
+
|
|
458
|
+
connection.reset!
|
|
459
|
+
result.discard!
|
|
460
|
+
|
|
458
461
|
# Successful drain: Result already released its connection from on_success.
|
|
459
462
|
end
|
|
460
463
|
|
|
461
|
-
|
|
462
|
-
def execute_transaction(access_mode, timeout: nil, metadata: nil, &block)
|
|
464
|
+
def execute_transaction(access_mode, timeout: nil, metadata: nil, &)
|
|
463
465
|
raise Exceptions::ClientException, 'Session is closed' unless @open
|
|
464
466
|
|
|
465
467
|
# Drain any pending auto-commit result so its connection is released
|
|
@@ -495,55 +497,53 @@ module Neo4j
|
|
|
495
497
|
).compact
|
|
496
498
|
|
|
497
499
|
loop do
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
raise e
|
|
523
|
-
end
|
|
524
|
-
|
|
525
|
-
# Exponential backoff: 1s, 2s, 4s, ... matching Java driver defaults
|
|
526
|
-
sleep(2 ** (errors.size - 1))
|
|
500
|
+
# api 0 = managed tx; execute_query's session overrides it to 3
|
|
501
|
+
# (DRIVER_EXECUTE_QUERY). nil once the server has acked the report.
|
|
502
|
+
telemetry_api = telemetry_acked ? nil : (@options[:telemetry_api] || 0)
|
|
503
|
+
return run_managed_transaction(op_mode, tx_options, telemetry_api, on_telemetry_ack, &)
|
|
504
|
+
rescue Exceptions::ServiceUnavailableException, Exceptions::SessionExpiredException,
|
|
505
|
+
Exceptions::TransientException,
|
|
506
|
+
# AuthorizationExpired (server authz-cache expiry) is always
|
|
507
|
+
# retryable; SecurityRetryableException is an auth failure the
|
|
508
|
+
# token manager has flagged retryable (token refreshed) — both
|
|
509
|
+
# warrant another attempt on a fresh connection/identity.
|
|
510
|
+
Exceptions::AuthorizationExpiredException, Exceptions::SecurityRetryableException => e
|
|
511
|
+
errors << e
|
|
512
|
+
|
|
513
|
+
if @clock.realtime - start_time >= max_retry_time
|
|
514
|
+
# Retries exhausted — re-raise the LAST error with its real
|
|
515
|
+
# type (SessionExpired / Transient / ServiceUnavailable)
|
|
516
|
+
# rather than flattening everything to ServiceUnavailable;
|
|
517
|
+
# Java's transaction executor likewise rethrows the last
|
|
518
|
+
# error. testkit asserts on the type (e.g. a routed reader
|
|
519
|
+
# interruption must surface as SessionExpired), so the
|
|
520
|
+
# generic wrapper masked it. Earlier attempts ride along as
|
|
521
|
+
# suppressed.
|
|
522
|
+
e.add_suppressed(*errors[0...-1])
|
|
523
|
+
raise e
|
|
527
524
|
end
|
|
525
|
+
|
|
526
|
+
# Exponential backoff: 1s, 2s, 4s, ... matching Java driver defaults
|
|
527
|
+
sleep(2**(errors.size - 1))
|
|
528
528
|
end
|
|
529
529
|
end
|
|
530
530
|
|
|
531
|
-
def run_managed_transaction(op_mode, tx_options, telemetry_api, on_telemetry_ack, &
|
|
531
|
+
def run_managed_transaction(op_mode, tx_options, telemetry_api, on_telemetry_ack, &)
|
|
532
532
|
if @transaction&.open?
|
|
533
533
|
raise Exceptions::ClientException,
|
|
534
|
-
|
|
534
|
+
'You cannot begin a transaction on a session with an open transaction'
|
|
535
535
|
end
|
|
536
536
|
|
|
537
537
|
# TELEMETRY 0 = managed transaction function; api is nil once acked so a
|
|
538
538
|
# retry doesn't repeat it.
|
|
539
539
|
@transaction = open_transaction(op_mode, tx_options, telemetry_api: telemetry_api,
|
|
540
|
-
|
|
540
|
+
telemetry_ack: on_telemetry_ack)
|
|
541
541
|
|
|
542
542
|
begin
|
|
543
543
|
result = yield @transaction
|
|
544
544
|
@transaction.commit if @transaction.open?
|
|
545
545
|
result
|
|
546
|
-
rescue => e
|
|
546
|
+
rescue StandardError => e
|
|
547
547
|
@transaction.rollback if @transaction.open?
|
|
548
548
|
raise e
|
|
549
549
|
ensure
|
|
@@ -563,12 +563,14 @@ module Neo4j
|
|
|
563
563
|
bookmarks = current_bookmarks_for_extra
|
|
564
564
|
tx_options = tx_options.merge(database: begin_db).compact
|
|
565
565
|
Transaction.new(connection, self, bookmarks, tx_options, telemetry_api: telemetry_api,
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
566
|
+
telemetry_ack: telemetry_ack,
|
|
567
|
+
# executeQuery's session reports telemetry api 3 (DRIVER_EXECUTE_QUERY);
|
|
568
|
+
# only that path pipelines BEGIN + RUN + PULL (Optimization:ExecuteQueryPipelining).
|
|
569
|
+
pipelined: @options[:telemetry_api] == 3,
|
|
570
|
+
on_begin: method(:cache_home_db_from),
|
|
571
|
+
on_release: lambda {
|
|
572
|
+
@connection_provider.release(connection)
|
|
573
|
+
})
|
|
572
574
|
end
|
|
573
575
|
end
|
|
574
576
|
end
|
|
@@ -74,6 +74,7 @@ module Neo4j
|
|
|
74
74
|
|
|
75
75
|
def plan
|
|
76
76
|
return @plan if defined?(@plan)
|
|
77
|
+
|
|
77
78
|
@plan =
|
|
78
79
|
if @metadata[:plan]
|
|
79
80
|
Plan.new(@metadata[:plan])
|
|
@@ -87,6 +88,7 @@ module Neo4j
|
|
|
87
88
|
|
|
88
89
|
def profile
|
|
89
90
|
return @profile if defined?(@profile)
|
|
91
|
+
|
|
90
92
|
@profile = @metadata[:profile] ? Profile.new(@metadata[:profile]) : nil
|
|
91
93
|
end
|
|
92
94
|
|
|
@@ -14,13 +14,13 @@ module Neo4j
|
|
|
14
14
|
# executeQuery pipelines BEGIN + RUN + PULL (Optimization:ExecuteQueryPipelining):
|
|
15
15
|
# BEGIN's reply is read only after the first RUN+PULL are flushed, not eagerly.
|
|
16
16
|
@pipelined = pipelined
|
|
17
|
-
@on_begin = on_begin
|
|
18
|
-
@on_release = on_release
|
|
17
|
+
@on_begin = on_begin # called with the BEGIN reply (home-db cache update)
|
|
18
|
+
@on_release = on_release # called once when the connection is no longer needed
|
|
19
19
|
@open = true
|
|
20
20
|
@committed = false
|
|
21
21
|
@rolled_back = false
|
|
22
22
|
@failed = false
|
|
23
|
-
@terminating_error = nil
|
|
23
|
+
@terminating_error = nil # the classified error that terminated this tx (a RUN/commit failure)
|
|
24
24
|
@current_result = nil
|
|
25
25
|
# Every result opened in this tx, in order. Bolt lets multiple stay open
|
|
26
26
|
# and streaming concurrently (qid multiplexing); a new RUN no longer
|
|
@@ -88,8 +88,10 @@ module Neo4j
|
|
|
88
88
|
# the user's own iteration (@current_result.failed?). Message wording
|
|
89
89
|
# stays "rolled back" to match the Java flavor (a shared integration spec
|
|
90
90
|
# asserts it on both impls); only the exception class becomes specific.
|
|
91
|
-
|
|
92
|
-
|
|
91
|
+
if terminated?
|
|
92
|
+
raise Exceptions::TransactionTerminatedException,
|
|
93
|
+
'Cannot run more queries in this transaction, it has been rolled back'
|
|
94
|
+
end
|
|
93
95
|
unless @open
|
|
94
96
|
# Mirror the Java/JRuby messages so the closed-state reason
|
|
95
97
|
# (committed vs rolled back) is reported the same on both impls.
|
|
@@ -138,10 +140,10 @@ module Neo4j
|
|
|
138
140
|
keys = (run_response.metadata[:fields] || run_response.metadata['fields'] || []).map(&:to_sym)
|
|
139
141
|
|
|
140
142
|
@current_result = Result.new(@connection, keys, buffer: buffer, handler: handler,
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
143
|
+
query_text: query, parameters: parameters,
|
|
144
|
+
run_metadata: run_response.metadata, fetch_size: fetch_size,
|
|
145
|
+
qid: run_response.metadata[:qid],
|
|
146
|
+
terminated_error: method(:terminating_error))
|
|
145
147
|
@open_results << @current_result
|
|
146
148
|
@current_result
|
|
147
149
|
end
|
|
@@ -263,6 +265,7 @@ module Neo4j
|
|
|
263
265
|
# executeQuery (and defensively before ROLLBACK if that query never ran).
|
|
264
266
|
def ack_begin!
|
|
265
267
|
return if @begin_acked
|
|
268
|
+
|
|
266
269
|
@begin_acked = true
|
|
267
270
|
|
|
268
271
|
if @telemetry_sent
|
|
@@ -334,7 +337,7 @@ module Neo4j
|
|
|
334
337
|
|
|
335
338
|
def release_connection
|
|
336
339
|
@on_release&.call
|
|
337
|
-
@on_release = nil
|
|
340
|
+
@on_release = nil # idempotent
|
|
338
341
|
end
|
|
339
342
|
end
|
|
340
343
|
end
|
|
@@ -25,12 +25,12 @@ module Neo4j
|
|
|
25
25
|
raise ArgumentError, "Invalid ISO 8601 duration format: #{string}"
|
|
26
26
|
end
|
|
27
27
|
|
|
28
|
-
years = (
|
|
29
|
-
months = (
|
|
30
|
-
days = (
|
|
31
|
-
hours = (
|
|
32
|
-
minutes = (
|
|
33
|
-
seconds_with_fraction = (
|
|
28
|
+
years = (::Regexp.last_match(1) || 0).to_i
|
|
29
|
+
months = (::Regexp.last_match(2) || 0).to_i
|
|
30
|
+
days = (::Regexp.last_match(3) || 0).to_i
|
|
31
|
+
hours = (::Regexp.last_match(4) || 0).to_i
|
|
32
|
+
minutes = (::Regexp.last_match(5) || 0).to_i
|
|
33
|
+
seconds_with_fraction = (::Regexp.last_match(6) || 0).to_f
|
|
34
34
|
|
|
35
35
|
total_months = years * 12 + months
|
|
36
36
|
whole_seconds = seconds_with_fraction.to_i
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Neo4j
|
|
4
|
+
module Driver
|
|
5
|
+
module Types
|
|
6
|
+
# Base for graph entities — a Node or a Relationship. Holds the shared
|
|
7
|
+
# identity (id / element_id) and property map, mirroring
|
|
8
|
+
# org.neo4j.driver.types.Entity, which the JRuby flavour exposes as
|
|
9
|
+
# Types::Entity. Kept as a real superclass (not just to de-duplicate
|
|
10
|
+
# Node/Relationship) because downstream gems and applications type-check
|
|
11
|
+
# against Neo4j::Driver::Types::Entity.
|
|
12
|
+
class Entity
|
|
13
|
+
attr_reader :id, :element_id, :properties
|
|
14
|
+
|
|
15
|
+
# element_id defaults to the stringified id — the Bolt 4.x wire carries
|
|
16
|
+
# no element_id, and callers always expect a value (matches Java).
|
|
17
|
+
def initialize(id, properties, element_id = nil)
|
|
18
|
+
@id = id
|
|
19
|
+
@properties = properties
|
|
20
|
+
@element_id = element_id || id.to_s
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Property lookup. PackStream hydration symbolizes every map key
|
|
24
|
+
# (Unpacker#unpack_map), so properties are symbol-keyed. Coerce the key
|
|
25
|
+
# so a string caller works too — the Java flavour accepts strings.
|
|
26
|
+
def [](key) = @properties[key.to_sym]
|
|
27
|
+
|
|
28
|
+
# Entities compare by identity within their own type (a Node is never
|
|
29
|
+
# equal to a Relationship), matching the Java driver. eql?/hash track
|
|
30
|
+
# == so entities work as Hash/Set keys.
|
|
31
|
+
def ==(other)
|
|
32
|
+
other.is_a?(self.class) && other.id == @id
|
|
33
|
+
end
|
|
34
|
+
alias eql? ==
|
|
35
|
+
|
|
36
|
+
def hash = @id.hash
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|