neo4j-ruby-driver 4.4.0.alpha.5 → 4.4.0.alpha.8

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.
Files changed (64) hide show
  1. checksums.yaml +4 -4
  2. data/lib/neo4j/driver/exceptions/protocol_exception.rb +2 -2
  3. data/lib/neo4j/driver/internal/bolt_server_address.rb +6 -6
  4. data/lib/neo4j/driver/types/time.rb +4 -2
  5. data/ruby/neo4j/driver/config.rb +1 -1
  6. data/ruby/neo4j/driver/graph_database.rb +2 -2
  7. data/ruby/neo4j/driver/internal/async/inbound/inbound_message_dispatcher.rb +2 -3
  8. data/ruby/neo4j/driver/internal/async/network_session.rb +4 -3
  9. data/ruby/neo4j/driver/internal/async/pool/{netty_channel_tracker.rb → channel_tracker.rb} +6 -8
  10. data/ruby/neo4j/driver/internal/async/pool/connection_pool_impl.rb +3 -3
  11. data/ruby/neo4j/driver/internal/async/unmanaged_transaction.rb +18 -20
  12. data/ruby/neo4j/driver/internal/cluster/cluster_composition.rb +10 -20
  13. data/ruby/neo4j/driver/internal/cluster/cluster_composition_lookup_result.rb +2 -2
  14. data/ruby/neo4j/driver/internal/cluster/cluster_routing_table.rb +38 -55
  15. data/ruby/neo4j/driver/internal/cluster/identity_resolver.rb +1 -4
  16. data/ruby/neo4j/driver/internal/cluster/loadbalancing/least_connected_load_balancing_strategy.rb +6 -6
  17. data/ruby/neo4j/driver/internal/cluster/loadbalancing/load_balancer.rb +44 -80
  18. data/ruby/neo4j/driver/internal/cluster/multi_databases_routing_procedure_runner.rb +6 -9
  19. data/ruby/neo4j/driver/internal/cluster/rediscovery_impl.rb +65 -155
  20. data/ruby/neo4j/driver/internal/cluster/route_message_routing_procedure_runner.rb +2 -2
  21. data/ruby/neo4j/driver/internal/cluster/routing_procedure_cluster_composition_provider.rb +8 -12
  22. data/ruby/neo4j/driver/internal/cluster/routing_procedure_response.rb +19 -3
  23. data/ruby/neo4j/driver/internal/cluster/routing_table_handler_impl.rb +46 -67
  24. data/ruby/neo4j/driver/internal/cluster/routing_table_registry_impl.rb +42 -61
  25. data/ruby/neo4j/driver/internal/cluster/single_database_routing_procedure_runner.rb +15 -19
  26. data/ruby/neo4j/driver/internal/cursor/async_result_cursor_impl.rb +31 -19
  27. data/ruby/neo4j/driver/internal/cursor/disposable_async_result_cursor.rb +12 -15
  28. data/ruby/neo4j/driver/internal/database_name_util.rb +3 -3
  29. data/ruby/neo4j/driver/internal/default_bookmark_holder.rb +1 -7
  30. data/ruby/neo4j/driver/internal/direct_connection_provider.rb +1 -1
  31. data/ruby/neo4j/driver/internal/driver_factory.rb +4 -4
  32. data/ruby/neo4j/driver/internal/handlers/commit_tx_response_handler.rb +4 -4
  33. data/ruby/neo4j/driver/internal/handlers/legacy_pull_all_response_handler.rb +90 -51
  34. data/ruby/neo4j/driver/internal/handlers/pulln/auto_pull_response_handler.rb +33 -44
  35. data/ruby/neo4j/driver/internal/handlers/rollback_tx_response_handler.rb +7 -1
  36. data/ruby/neo4j/driver/internal/impersonation_util.rb +2 -2
  37. data/ruby/neo4j/driver/internal/internal_bookmark.rb +1 -1
  38. data/ruby/neo4j/driver/internal/internal_database_name.rb +3 -5
  39. data/ruby/neo4j/driver/internal/internal_result.rb +5 -5
  40. data/ruby/neo4j/driver/internal/internal_session.rb +1 -1
  41. data/ruby/neo4j/driver/internal/internal_transaction.rb +4 -4
  42. data/ruby/neo4j/driver/internal/messaging/bolt_protocol_version.rb +3 -1
  43. data/ruby/neo4j/driver/internal/messaging/encode/route_message_encoder.rb +8 -2
  44. data/ruby/neo4j/driver/internal/messaging/encode/route_v44_message_encoder.rb +8 -13
  45. data/ruby/neo4j/driver/internal/messaging/request/abstract_streaming_message.rb +4 -1
  46. data/ruby/neo4j/driver/internal/messaging/request/begin_message.rb +2 -3
  47. data/ruby/neo4j/driver/internal/messaging/request/multi_database_util.rb +2 -2
  48. data/ruby/neo4j/driver/internal/messaging/request/route_message.rb +5 -10
  49. data/ruby/neo4j/driver/internal/messaging/request/run_with_metadata_message.rb +5 -3
  50. data/ruby/neo4j/driver/internal/messaging/request/transaction_metadata_builder.rb +2 -2
  51. data/ruby/neo4j/driver/internal/messaging/v3/bolt_protocol_v3.rb +8 -4
  52. data/ruby/neo4j/driver/internal/messaging/v44/message_writer_v44.rb +1 -1
  53. data/ruby/neo4j/driver/internal/read_only_bookmark_holder.rb +13 -0
  54. data/ruby/neo4j/driver/internal/resolved_bolt_server_address.rb +35 -0
  55. data/ruby/neo4j/driver/internal/security/security_plan_impl.rb +14 -9
  56. data/ruby/neo4j/driver/internal/util/error_util.rb +1 -1
  57. data/ruby/neo4j/driver/internal/util/result_holder.rb +70 -0
  58. data/ruby/neo4j/driver/net/{server_address1.rb → server_address.rb} +2 -2
  59. data/ruby/neo4j/driver/query.rb +1 -1
  60. data/ruby/neo4j/driver/transaction_config.rb +5 -1
  61. data/ruby/neo4j/driver/values.rb +3 -3
  62. data/ruby/neo4j/driver/version.rb +1 -1
  63. metadata +16 -14
  64. data/ruby/neo4j/driver/internal/database_name.rb +0 -12
@@ -6,109 +6,88 @@ module Neo4j::Driver
6
6
 
7
7
  delegate :servers, to: :routing_table
8
8
 
9
- def initialize(routing_table, rediscovery, connection_pool, routing_table_registry, logger, routing_table_purge_delay_ms)
9
+ def initialize(routing_table, rediscovery, connection_pool, routing_table_registry, logger, routing_table_purge_delay)
10
10
  @routing_table = routing_table
11
11
  @database_name = routing_table.database
12
12
  @rediscovery = rediscovery
13
13
  @connection_pool = connection_pool
14
14
  @routing_table_registry = routing_table_registry
15
15
  @log = logger
16
- @routing_table_purge_delay_ms = routing_table_purge_delay_ms
17
- @resolved_initial_routers = []
18
- @refresh_routing_table_future = nil
16
+ @routing_table_purge_delay = routing_table_purge_delay
17
+ @mutex = Concurrent::ReentrantReadWriteLock.new
19
18
  end
20
19
 
21
20
  def on_connection_failure(address)
22
21
  # remove server from the routing table, to prevent concurrent threads from making connections to this address
23
- routing_table.forget(address)
22
+ @routing_table.forget(address)
24
23
  end
25
24
 
26
25
  def on_write_failure(address)
27
- routing_table.forget_writer(address)
26
+ @routing_table.forget_writer(address)
28
27
  end
29
28
 
30
29
  def ensure_routing_table(context)
31
- # refresh is already happening concurrently, just use it's result
32
- return @refresh_routing_table_future unless @refresh_routing_table_future.nil?
33
-
34
- if routing_table.stale_for?(context.mode)
35
- # existing routing table is not fresh and should be updated
36
- @log.debug( "Routing table for database '#{@database_name.description}' is stale. #{routing_table}")
37
-
38
- result_future = java.util.concurrent.CompletableFuture.new
39
- @refresh_routing_table_future = result_future
40
-
41
- @rediscovery.lookup_cluster_composition(routing_table, @connection_pool, context.rediscovery_bookmark, nil).when_complete do |composition, completion_error|
42
- error = Util::Futures.completion_exception_cause(completion_error)
43
-
44
- if error.nil?
45
- fresh_cluster_composition_fetched(composition)
46
- else
47
- cluster_composition_lookup_failed(error)
48
- end
30
+ @mutex.with_write_lock do
31
+ if @routing_table.stale_for?(context.mode)
32
+ # existing routing table is not fresh and should be updated
33
+ @log.debug("Routing table for database '#{@database_name.description}' is stale. #{@routing_table}")
34
+
35
+ fresh_cluster_composition_fetched(
36
+ @rediscovery.lookup_cluster_composition(@routing_table, @connection_pool, context.rediscovery_bookmark,
37
+ nil))
38
+ else
39
+ # existing routing table is fresh, use it
40
+ @routing_table
49
41
  end
50
-
51
- result_future
52
- else
53
- # existing routing table is fresh, use it
54
- java.util.concurrent.CompletableFuture.completed_future(routing_table)
42
+ rescue => error
43
+ cluster_composition_lookup_failed(error)
55
44
  end
56
45
  end
57
46
 
58
47
  def update_routing_table(composition_lookup_result)
59
- if !@refresh_routing_table_future.nil?
60
- # refresh is already happening concurrently, just use its result
61
- @refresh_routing_table_future
62
- else
63
- if composition_lookup_result.get_cluster_composition.expiration_timestamp < routing_table.expiration_timestamp
64
- return java.util.concurrent.CompletableFuture.completed_future(routing_table)
48
+ @mutex.with_write_lock do
49
+ if composition_lookup_result.cluster_composition.expiration_timestamp < @routing_table.expiration_timestamp
50
+ @routing_table
51
+ else
52
+ fresh_cluster_composition_fetched(composition_lookup_result)
65
53
  end
66
-
67
- result_future = java.util.concurrent.CompletableFuture.new
68
- @refresh_routing_table_future = result_future
69
- fresh_cluster_composition_fetched(composition_lookup_result)
70
- result_future
71
54
  end
72
55
  end
73
56
 
74
- private def fresh_cluster_composition_fetched(composition_lookup_result)
75
- begin
76
- @log.debug("Fetched cluster composition for database '#{@database_name.description}'. #{composition_lookup_result.cluster_composition}")
77
- routing_table.update(composition_lookup_result.cluster_composition)
78
- @routing_table_registry.remove_aged
57
+ private
79
58
 
80
- addresses_to_retain = []
81
- @routing_table_registry.all_servers.stream.flat_map(BoltServerAddress::unicast_stream).for_each(addresses_to_retain::add)
59
+ def fresh_cluster_composition_fetched(composition_lookup_result)
60
+ @log.debug("Fetched cluster composition for database '#{@database_name.description}'. #{composition_lookup_result.cluster_composition}")
61
+ @routing_table.update(composition_lookup_result.cluster_composition)
62
+ @routing_table_registry.remove_aged
63
+ addresses_to_retain = @routing_table_registry.all_servers.map(&:unicast_stream).reduce(&:+)
82
64
 
83
- composition_lookup_result.resolved_initial_routers.if_present do |addresses|
84
- @resolved_initial_routers.clear
85
- @resolved_initial_routers << addresses
86
- end
65
+ composition_lookup_result.resolved_initial_routers&.then do |addresses|
66
+ addresses_to_retain << addresses
67
+ end
87
68
 
88
- addresses_to_retain << @resolved_initial_routers
89
- @connection_pool.retain_all(addresses_to_retain)
69
+ @connection_pool.retain_all(addresses_to_retain)
90
70
 
91
- @log.debug("Updated routing table for database '#{@database_name.description}'. #{routing_table}")
71
+ @log.debug("Updated routing table for database '#{@database_name.description}'. #{routing_table}")
72
+ @routing_table
73
+ rescue => error
74
+ cluster_composition_lookup_failed(error)
75
+ end
92
76
 
93
- routing_table_future = @refresh_routing_table_future
94
- @refresh_routing_table_future = nil
95
- routing_table_future.complete( routing_table)
96
- rescue StandardError => error
97
- cluster_composition_lookup_failed(error)
77
+ def cluster_composition_lookup_failed(error)
78
+ @log.error do
79
+ "Failed to update routing table for database '#{@database_name.description}'. Current routing table: #{@routing_table}."
98
80
  end
81
+ @log.error(error)
82
+ @routing_table_registry.remove(@database_name)
83
+ raise error
99
84
  end
100
85
 
101
- private def cluster_composition_lookup_failed(error)
102
- @log.error("Failed to update routing table for database '#{database_name.description}'. Current routing table: #{routing_table}.", error)
103
- @routing_table_registry.remove(database_name)
104
- routing_table_future = @refresh_routing_table_future
105
- @refresh_routing_table_future = nil
106
- routing_table_future.complete_exceptionally(error)
107
- end
86
+ public
108
87
 
109
88
  # This method cannot be synchronized as it will be visited by all routing table handler's threads concurrently
110
89
  def routing_table_aged?
111
- @refresh_routing_table_future.nil? && routing_table.has_been_stale_for?(@routing_table_purge_delay_ms)
90
+ @mutex.with_read_lock { @routing_table.has_been_stale_for?(@routing_table_purge_delay) }
112
91
  end
113
92
  end
114
93
  end
@@ -2,78 +2,57 @@ module Neo4j::Driver
2
2
  module Internal
3
3
  module Cluster
4
4
  class RoutingTableRegistryImpl
5
- def initialize(connection_pool, rediscovery, clock, logger, routing_table_purge_delay_ms)
6
- @factory = RoutingTableHandlerFactory.new(connection_pool, rediscovery, clock, logger, routing_table_purge_delay_ms)
7
- @routing_table_handlers = Concurrent::Hash.new
8
- @principal_to_database_name_stage = {}
5
+ def initialize(connection_pool, rediscovery, clock, logger, routing_table_purge_delay)
6
+ @factory = RoutingTableHandlerFactory.new(connection_pool, rediscovery, clock, logger, routing_table_purge_delay)
7
+ @routing_table_handlers = Concurrent::Map.new
8
+ @principal_to_database_name = {}
9
9
  @clock = clock
10
10
  @connection_pool = connection_pool
11
11
  @rediscovery = rediscovery
12
12
  @log = logger
13
+ @mutex = Mutex.new
13
14
  end
14
15
 
15
16
  def ensure_routing_table(context)
16
- ensure_database_name_is_completed(context).then_compose do |ctx_and_handler|
17
- completed_context = ctx_and_handler.context
18
- handler = ctx_and_handler.handler.nil? ? get_or_create(Util::Futures.join_now_or_else_throw(completed_context.database_name_future, Async::ConnectionContext::PENDING_DATABASE_NAME_EXCEPTION_SUPPLIER)) : ctx_and_handler.handler
19
- handler.ensure_routing_table(completed_context).then_apply(-> (_ignored) { handler })
20
- end
17
+ ctx_and_handler = ensure_database_name_is_completed(context)
18
+ (ctx_and_handler.handler || get_or_create(context.database_name))
19
+ .tap { |handler| handler.ensure_routing_table(ctx_and_handler.context) }
21
20
  end
22
21
 
23
22
  private def ensure_database_name_is_completed(context)
24
- context_database_name_future = context.database_name_future
23
+ context_database_name = context.database_name
24
+
25
+ return ConnectionContextAndHandler.new(context, nil) if context_database_name
26
+ @mutex.synchronize do
27
+ return ConnectionContextAndHandler.new(context, nil) if context_database_name
25
28
 
26
- if context_database_name_future.done?
27
- context_and_handler_stage = java.util.concurrent.CompletableFuture.completed_future(ConnectionContextAndHandler.new(context, nil))
28
- else
29
29
  impersonated_user = context.impersonated_user
30
30
  principal = Principal.new(impersonated_user)
31
- database_name_stage = @principal_to_database_name_stage[principal]
32
- handler_ref = java.util.concurrent.atomic.AtomicReference.new
33
-
34
- if database_name_stage.nil?
35
- database_name_future = java.util.concurrent.CompletableFuture.new
36
- @principal_to_database_name_stage[principal] = database_name_future
37
- database_name_stage = database_name_future
38
-
39
- routing_table = ClusterRoutingTable.new(DatabaseNameUtil::DEFAULT_DATABASE, @clock)
40
-
41
- @rediscovery.lookup_cluster_composition(routing_table, @connection_pool, context.rediscovery_bookmark, impersonated_user).then_compose do |composition_lookup_result|
42
- database_name = DatabaseNameUtil.database(composition_lookup_result.cluster_composition.database_name)
43
- handler = get_or_create(database_name)
44
- handler_ref.set(handler)
45
- handler.update_routing_table(composition_lookup_result).then_apply(-> (_ignored) { database_name })
46
- end.when_complete do |database_name, _throwable|
47
- @principal_to_database_name_stage.delete(principal)
48
- end.when_complete do |database_name, throwable|
49
- if throwable.nil?
50
- database_name_future.complete(database_name)
51
- else
52
- database_name_future.complete_exceptionally(throwable)
53
- end
54
- end
55
- end
31
+ database_name = @principal_to_database_name[principal]
32
+ handler_ref = Concurrent::AtomicReference.new
56
33
 
57
- context_and_handler_stage =
58
- database_name_stage.then_apply do |database_name|
59
- context_database_name_future.complete(database_name)
60
- ConnectionContextAndHandler.new(context, handler_ref)
34
+ if database_name.nil?
35
+ @principal_to_database_name[principal] = database_name
36
+
37
+ routing_table = ClusterRoutingTable.new(DatabaseNameUtil.default_database, @clock)
38
+
39
+ composition_lookup_result = @rediscovery.lookup_cluster_composition(routing_table, @connection_pool, context.rediscovery_bookmark, impersonated_user)
40
+ database_name = DatabaseNameUtil.database(composition_lookup_result.cluster_composition.database_name)
41
+ handler = get_or_create(database_name)
42
+ handler_ref.set(handler)
43
+ handler.update_routing_table(composition_lookup_result)
44
+ @principal_to_database_name.delete(principal)
61
45
  end
62
- end
63
46
 
64
- context_and_handler_stage
47
+ context.database_name = database_name
48
+ ConnectionContextAndHandler.new(context, handler_ref.get)
49
+ end
65
50
  end
66
51
 
67
52
  def all_servers
68
53
  # obviously we just had a snapshot of all servers in all routing tables
69
54
  # after we read it, the set could already be changed.
70
- servers = []
71
-
72
- @routing_table_handlers.values.each do |table_handler|
73
- servers << table_handler.servers
74
- end
75
-
76
- servers
55
+ @routing_table_handlers.values.map(&:servers).reduce(&:+)
77
56
  end
78
57
 
79
58
  def remove(database_name)
@@ -90,17 +69,19 @@ module Neo4j::Driver
90
69
  end
91
70
  end
92
71
 
93
- def get_routing_table_handler(database_name)
94
- java.util.Optional.of_nullable(@routing_table_handlers[database_name])
72
+ def routing_table_handler(database_name)
73
+ database_name = DatabaseNameUtil.database(database_name) unless database_name.is_a?(InternalDatabaseName)
74
+ @routing_table_handlers[database_name]
95
75
  end
96
76
 
97
- def contains(database_name)
98
- @routing_table_handlers.keys.include?(database_name)
99
- end
77
+ # For tests
78
+ delegate :key?, to: :@routing_table_handlers
100
79
 
101
80
  def get_or_create(database_name)
102
- @routing_table_handlers.compute_if_absent(database_name) do |name|
103
- handler = @factory.new_instance(name, self)
81
+ @routing_table_handlers.compute_if_absent(database_name) do
82
+ # TODO: Verify if applies
83
+ # Note: Atomic methods taking a block do not allow the self instance to be used within the block. Doing so will cause a deadlock.
84
+ handler = @factory.new_instance(database_name, self)
104
85
  @log.debug("Routing table handler for database '#{database_name.description}' is added.")
105
86
  handler
106
87
  end
@@ -109,17 +90,17 @@ module Neo4j::Driver
109
90
  private
110
91
 
111
92
  class RoutingTableHandlerFactory
112
- def initialize(connection_pool, rediscovery, clock, logger, routing_table_purge_delay_ms)
93
+ def initialize(connection_pool, rediscovery, clock, logger, routing_table_purge_delay)
113
94
  @connection_pool = connection_pool
114
95
  @rediscovery = rediscovery
115
96
  @clock = clock
116
97
  @logger = logger
117
- @routing_table_purge_delay_ms = routing_table_purge_delay_ms
98
+ @routing_table_purge_delay = routing_table_purge_delay
118
99
  end
119
100
 
120
101
  def new_instance(database_name, all_tables)
121
102
  routing_table = ClusterRoutingTable.new(database_name, @clock)
122
- RoutingTableHandlerImpl.new(routing_table, @rediscovery, @connection_pool, all_tables, @logger, @routing_table_purge_delay_ms)
103
+ RoutingTableHandlerImpl.new(routing_table, @rediscovery, @connection_pool, all_tables, @logger, @routing_table_purge_delay)
123
104
  end
124
105
  end
125
106
 
@@ -5,7 +5,7 @@ module Neo4j::Driver
5
5
  # This implementation of the {@link RoutingProcedureRunner} works with single database versions of Neo4j calling
6
6
  # the procedure `dbms.cluster.routing.getRoutingTable`
7
7
  class SingleDatabaseRoutingProcedureRunner
8
- ROUTING_CONTEXT = 'context'
8
+ ROUTING_CONTEXT = :context
9
9
  GET_ROUTING_TABLE = "CALL dbms.cluster.routing.getRoutingTable($#{ROUTING_CONTEXT})"
10
10
 
11
11
  def initialize(context)
@@ -16,17 +16,15 @@ module Neo4j::Driver
16
16
  delegate = connection(connection)
17
17
  procedure = procedure_query(connection.server_version, database_name)
18
18
  bookmark_holder = bookmark_holder(bookmark)
19
- run_procedure(delegate, procedure, bookmark_holder).then_compose do |records|
20
- release_connection(delegate, records).handle do |records, error|
21
- process_procedure_response(procedure, records, error)
22
- end
23
- end
19
+ run_procedure(delegate, procedure, bookmark_holder)
20
+ .side { release_connection(delegate) }
21
+ .chain { |records, error| process_procedure_response(procedure, records, error) }
24
22
  end
25
23
 
26
24
  private
27
25
 
28
26
  def connection(connection)
29
- Async::Connection::DirectConnection.new(connection, default_database, AccessMode::WRITE, nil)
27
+ Async::Connection::DirectConnection.new(connection, DatabaseNameUtil.default_database, AccessMode::WRITE, nil)
30
28
  end
31
29
 
32
30
  def procedure_query(server_version, database_name)
@@ -34,7 +32,7 @@ module Neo4j::Driver
34
32
  raise Exceptions::FatalDiscoveryException, "Refreshing routing table for multi-databases is not supported in server version lower than 4.0. Current server version: #{server_version}. Database name: '#{database_name.description}'"
35
33
  end
36
34
 
37
- Query.new(GET_ROUTING_TABLE, Values.parameters(ROUTING_CONTEXT, @context.to_map))
35
+ Query.new(GET_ROUTING_TABLE, ROUTING_CONTEXT => @context.to_h)
38
36
  end
39
37
 
40
38
  def bookmark_holder(_ignored)
@@ -45,30 +43,28 @@ module Neo4j::Driver
45
43
  connection.protocol
46
44
  .run_in_auto_commit_transaction(connection, procedure, bookmark_holder, TransactionConfig.empty,
47
45
  Handlers::Pulln::FetchSizeUtil::UNLIMITED_FETCH_SIZE)
48
- .async_result.then_compose(Async::ResultCursor::list_async)
46
+ .async_result.then(&:to_a)
49
47
  end
50
48
 
51
- def release_connection(connection, records)
49
+ def release_connection(connection)
52
50
  # It is not strictly required to release connection after routing procedure invocation because it'll
53
51
  # be released by the PULL_ALL response handler after result is fully fetched. Such release will happen
54
52
  # in background. However, releasing it early as part of whole chain makes it easier to reason about
55
53
  # rediscovery in stub server tests. Some of them assume connections to instances not present in new
56
54
  # routing table will be closed immediately.
57
- connection.release.then_apply(->(_ignore) { records } )
55
+ connection.release
58
56
  end
59
57
 
60
58
  def process_procedure_response(procedure, records, error)
61
- cause = Util::Futures.completion_exception_cause(error)
62
-
63
- return RoutingProcedureResponse.new(procedure, records) if cause.nil?
64
-
65
- handle_error(procedure, cause)
59
+ error ? handle_error(procedure, error) : RoutingProcedureResponse.new(procedure, records: records)
66
60
  end
67
61
 
68
62
  def handle_error(procedure, error)
69
- return RoutingProcedureResponse.new(procedure, records) if error.is_a? Exceptions::ClientException
70
-
71
- raise java.util.concurrent.CompletionException, error
63
+ if error.is_a? Exceptions::ClientException
64
+ RoutingProcedureResponse.new(procedure, error: error)
65
+ else
66
+ raise error
67
+ end
72
68
  end
73
69
  end
74
70
  end
@@ -2,6 +2,7 @@ module Neo4j::Driver
2
2
  module Internal
3
3
  module Cursor
4
4
  class AsyncResultCursorImpl
5
+ include Enumerable
5
6
  delegate :consume_async, :next_async, :peek_async, to: :@pull_all_handler
6
7
 
7
8
  def initialize(run_handler, pull_all_handler)
@@ -14,20 +15,24 @@ module Neo4j::Driver
14
15
  end
15
16
 
16
17
  def single_async
17
- first_record = next_async
18
- unless first_record
19
- raise Exceptions::NoSuchRecordException, 'Cannot retrieve a single record, because this result is empty.'
20
- end
21
- if next_async
22
- raise Exceptions::NoSuchRecordException,
23
- 'Expected a result with a single record, but this result contains at least one more. Ensure your query returns only one record.'
18
+ next_async.compose do |first_record|
19
+ unless first_record
20
+ raise Exceptions::NoSuchRecordException, 'Cannot retrieve a single record, because this result is empty.'
21
+ end
22
+ next_async.then do |second_record|
23
+ if second_record
24
+ raise Exceptions::NoSuchRecordException,
25
+ 'Expected a result with a single record, but this result contains at least one more. Ensure your query returns only one record.'
26
+ end
27
+ first_record
28
+ end
24
29
  end
25
- first_record
26
30
  end
27
31
 
28
- def each_async(&action)
29
- internal_for_each_async(&action)
30
- consume_async
32
+ def each(&action)
33
+ result_holder = Util::ResultHolder.new
34
+ internal_for_each_async(result_holder, &action)
35
+ result_holder.then { consume_async }
31
36
  end
32
37
 
33
38
  def to_async(&map_function)
@@ -36,7 +41,7 @@ module Neo4j::Driver
36
41
 
37
42
  def discard_all_failure_async
38
43
  # runError has priority over other errors and is expected to have been reported to user by now
39
- consume_async #.chain { |_fulfilled, _summary, error| @run_error ? nil : error }
44
+ consume_async.chain { |_summary, error| run_error ? nil : error }
40
45
  nil
41
46
  end
42
47
 
@@ -45,18 +50,25 @@ module Neo4j::Driver
45
50
  @pull_all_handler.pull_all_failure_async.then { |error| run_error ? nil : error }
46
51
  end
47
52
 
48
- private def internal_for_each_async
49
- while record = next_async
50
- begin
51
- yield record
52
- rescue
53
- nil
53
+ private def internal_for_each_async(result_holder, &action)
54
+ next_async.chain do |record, error|
55
+ if error
56
+ result_holder.fail(error)
57
+ elsif record
58
+ begin
59
+ yield record
60
+ rescue => action_error
61
+ result_holder.fail(action_error)
62
+ end
63
+ internal_for_each_async(result_holder, &action)
64
+ else
65
+ result_holder.succeed(nil)
54
66
  end
55
67
  end
56
68
  end
57
69
 
58
70
  def map_successful_run_completion_async
59
- run_error || self
71
+ run_error&.then(&Util::ResultHolder.method(:failed)) || Util::ResultHolder.successful(self)
60
72
  end
61
73
 
62
74
  def run_error
@@ -2,6 +2,7 @@ module Neo4j::Driver
2
2
  module Internal
3
3
  module Cursor
4
4
  class DisposableAsyncResultCursor
5
+ include Enumerable
5
6
  delegate :keys, :pull_all_failure_async, to: :@delegate
6
7
 
7
8
  def initialize(delegate)
@@ -14,25 +15,23 @@ module Neo4j::Driver
14
15
  end
15
16
 
16
17
  def next_async
17
- assert_not_disposed.then_compose(-> (_ignored) { @delegate.next_async })
18
+ assert_not_disposed
19
+ @delegate.next_async
18
20
  end
19
21
 
20
22
  def peek_async
21
- assert_not_disposed.then_compose(-> (_ignored) { @delegate.peek_async })
23
+ assert_not_disposed
24
+ @delegate.peek_async
22
25
  end
23
26
 
24
27
  def single_async
25
- assert_not_disposed.then_compose(-> (_ignored) { @delegate.single_async })
28
+ assert_not_disposed
29
+ @delegate.single_async
26
30
  end
27
31
 
28
- def for_each_async(action)
29
- assert_not_disposed.then_compose(-> (_ignored) { @delegate.for_each_async(action) })
30
- end
31
-
32
- def list_async(map_function = nil)
33
- return assert_not_disposed.then_compose(-> (_ignored) { @delegate.list_async(map_function) }) if map_function.present?
34
-
35
- assert_not_disposed.then_compose(-> (_ignored) { @delegate.list_async })
32
+ def each(&action)
33
+ assert_not_disposed
34
+ @delegate.each(&action)
36
35
  end
37
36
 
38
37
  def discard_all_failure_async
@@ -41,9 +40,7 @@ module Neo4j::Driver
41
40
  end
42
41
 
43
42
  private def assert_not_disposed
44
- return Util::Futures.failed_future(new_result_consumed_error) if @disposed
45
-
46
- Util::Futures.completed_with_null
43
+ raise Util::ErrorUtil.new_result_consumed_error if @disposed
47
44
  end
48
45
 
49
46
  def disposed?
@@ -51,7 +48,7 @@ module Neo4j::Driver
51
48
  end
52
49
 
53
50
  def map_successful_run_completion_async
54
- @delegate.map_successful_run_completion_async
51
+ @delegate.map_successful_run_completion_async.then { self }
55
52
  end
56
53
  end
57
54
  end
@@ -6,8 +6,8 @@ module Neo4j::Driver
6
6
 
7
7
  private
8
8
 
9
- DEFAULT_DATABASE = Struct.new(:database_name, :description).new(nil, '<default database>')
10
- SYSTEM_DATABASE = InternalDatabaseName.new(SYSTEM_DATABASE_NAME)
9
+ DEFAULT_DATABASE = InternalDatabaseName.new(description: '<default database>')
10
+ SYSTEM_DATABASE = InternalDatabaseName.new(database_name: SYSTEM_DATABASE_NAME)
11
11
 
12
12
  public
13
13
 
@@ -27,7 +27,7 @@ module Neo4j::Driver
27
27
  when SYSTEM_DATABASE_NAME
28
28
  system_database
29
29
  else
30
- InternalDatabaseName.new(name)
30
+ InternalDatabaseName.new(database_name: name)
31
31
  end
32
32
  end
33
33
  end
@@ -1,12 +1,6 @@
1
1
  module Neo4j::Driver
2
2
  module Internal
3
- class DefaultBookmarkHolder
4
- attr_reader :bookmark
5
-
6
- def initialize(bookmark = InternalBookmark.empty)
7
- @bookmark = bookmark
8
- end
9
-
3
+ class DefaultBookmarkHolder < ReadOnlyBookmarkHolder
10
4
  def bookmark=(bookmark)
11
5
  @bookmark = bookmark if bookmark.present?
12
6
  end
@@ -22,7 +22,7 @@ module Neo4j::Driver
22
22
 
23
23
  def supports_multi_db?
24
24
  private_acquire_connection.then do |conn|
25
- supports_multi_database?(conn)
25
+ Messaging::Request::MultiDatabaseUtil.supports_multi_database?(conn)
26
26
  ensure
27
27
  conn.release
28
28
  end
@@ -83,8 +83,8 @@ module Neo4j::Driver::Internal
83
83
  driver(:Direct, securityPlan, address, connection_provider, retryLogic, metricsProvider, config)
84
84
  end
85
85
 
86
- def create_routing_driver(securityPlan, address, connection_pool, eventExecutorGroup, routingSettings, retryLogic, metricsProvider, config)
87
- connection_provider = create_load_balancer(address, connection_pool, eventExecutorGroup, config, routingSettings)
86
+ def create_routing_driver(securityPlan, address, connection_pool, eventExecutorGroup, routing_settings, retryLogic, metricsProvider, config)
87
+ connection_provider = create_load_balancer(address, connection_pool, eventExecutorGroup, config, routing_settings)
88
88
  driver(:Routing, securityPlan, address, connection_provider, retryLogic, metricsProvider, config)
89
89
  end
90
90
 
@@ -95,11 +95,11 @@ module Neo4j::Driver::Internal
95
95
  end
96
96
  end
97
97
 
98
- def create_load_balancer(address, connection_pool, eventExecutorGroup, config, routingSettings)
98
+ def create_load_balancer(address, connection_pool, eventExecutorGroup, config, routing_settings)
99
99
  load_balancing_strategy = Cluster::Loadbalancing::LeastConnectedLoadBalancingStrategy.new(connection_pool, config[:logger])
100
100
  resolver = create_resolver(config)
101
101
  Cluster::Loadbalancing::LoadBalancer.new(
102
- address, routingSettings, connection_pool, eventExecutorGroup,
102
+ address, routing_settings, connection_pool, eventExecutorGroup,
103
103
  config[:logger], load_balancing_strategy, resolver, &method(:domain_name_resolver))
104
104
  end
105
105
 
@@ -4,16 +4,16 @@ module Neo4j::Driver
4
4
  class CommitTxResponseHandler
5
5
  include Spi::ResponseHandler
6
6
 
7
- def initialize(completion_listener)
8
- @completion_listener = completion_listener
7
+ def initialize(result_holder)
8
+ @result_holder = result_holder
9
9
  end
10
10
 
11
11
  def on_success(metadata)
12
- @completion_listener.bookmark = metadata[:bookmark]&.then(&InternalBookmark.method(:parse))
12
+ @result_holder.succeed(metadata[:bookmark]&.then(&InternalBookmark.method(:parse)))
13
13
  end
14
14
 
15
15
  def on_failure(error)
16
- raise error
16
+ @result_holder.fail(error)
17
17
  end
18
18
 
19
19
  def on_record(fields)