neo4j-ruby-driver 6.0.0.alpha.2 → 6.2.1.beta.1

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 (346) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +113 -411
  3. data/{ruby → lib}/neo4j/driver/access_mode.rb +2 -1
  4. data/lib/neo4j/driver/auth_token_managers.rb +31 -0
  5. data/lib/neo4j/driver/auth_tokens.rb +41 -0
  6. data/lib/neo4j/driver/auto_commit_retries_mode.rb +18 -0
  7. data/lib/neo4j/driver/bolt/bolt_version.rb +67 -0
  8. data/lib/neo4j/driver/bolt/connection.rb +1054 -0
  9. data/lib/neo4j/driver/bolt/handshake.rb +232 -0
  10. data/lib/neo4j/driver/bolt/message/failure.rb +139 -0
  11. data/lib/neo4j/driver/bolt/message/ignored.rb +27 -0
  12. data/lib/neo4j/driver/bolt/message/record.rb +30 -0
  13. data/lib/neo4j/driver/bolt/message/success.rb +34 -0
  14. data/lib/neo4j/driver/bolt/message.rb +112 -0
  15. data/lib/neo4j/driver/bolt/pool.rb +255 -0
  16. data/lib/neo4j/driver/bolt/protocol/base.rb +146 -0
  17. data/lib/neo4j/driver/bolt/protocol/v3.rb +43 -0
  18. data/lib/neo4j/driver/bolt/protocol/v4.rb +24 -0
  19. data/lib/neo4j/driver/bolt/protocol/v43.rb +28 -0
  20. data/lib/neo4j/driver/bolt/protocol/v44.rb +23 -0
  21. data/lib/neo4j/driver/bolt/protocol/v5.rb +27 -0
  22. data/lib/neo4j/driver/bolt/protocol/v51.rb +24 -0
  23. data/lib/neo4j/driver/bolt/protocol/v52.rb +28 -0
  24. data/lib/neo4j/driver/bolt/protocol/v53.rb +27 -0
  25. data/lib/neo4j/driver/bolt/protocol/v54.rb +14 -0
  26. data/lib/neo4j/driver/bolt/protocol/v55.rb +17 -0
  27. data/lib/neo4j/driver/bolt/protocol/v56.rb +14 -0
  28. data/lib/neo4j/driver/bolt/protocol/v57.rb +31 -0
  29. data/lib/neo4j/driver/bolt/protocol/v58.rb +15 -0
  30. data/lib/neo4j/driver/bolt/protocol/v6.rb +43 -0
  31. data/lib/neo4j/driver/bolt/protocol/v61.rb +21 -0
  32. data/lib/neo4j/driver/bolt/protocol_version_handler.rb +39 -0
  33. data/lib/neo4j/driver/bolt/record_buffer.rb +136 -0
  34. data/lib/neo4j/driver/bolt/stream_handler.rb +53 -0
  35. data/lib/neo4j/driver/bolt/tls_config.rb +129 -0
  36. data/lib/neo4j/driver/bolt/wire.rb +321 -0
  37. data/lib/neo4j/driver/bookmark.rb +15 -0
  38. data/lib/neo4j/driver/bookmark_managers.rb +39 -0
  39. data/lib/neo4j/driver/client_certificate.rb +19 -0
  40. data/lib/neo4j/driver/client_certificate_manager.rb +37 -0
  41. data/lib/neo4j/driver/client_certificate_managers.rb +15 -0
  42. data/lib/neo4j/driver/client_certificates.rb +14 -0
  43. data/lib/neo4j/driver/direct/connection_provider.rb +258 -0
  44. data/lib/neo4j/driver/driver.rb +268 -0
  45. data/lib/neo4j/driver/eager_result.rb +10 -0
  46. data/lib/neo4j/driver/exceptions/neo4j_exception.rb +16 -7
  47. data/lib/neo4j/driver/exceptions/protocol_exception.rb +0 -5
  48. data/lib/neo4j/driver/exceptions/security_retryable_exception.rb +14 -0
  49. data/lib/neo4j/driver/exceptions/transaction_terminated_exception.rb +13 -0
  50. data/lib/neo4j/driver/graph_database.rb +29 -0
  51. data/lib/neo4j/driver/internal/clock.rb +20 -0
  52. data/lib/neo4j/driver/internal/clock_adapter.rb +21 -0
  53. data/lib/neo4j/driver/internal/default_bookmark_manager.rb +54 -0
  54. data/lib/neo4j/driver/internal/deprecator.rb +9 -5
  55. data/lib/neo4j/driver/internal/driver_factory.rb +128 -0
  56. data/lib/neo4j/driver/internal/duration_normalizer.rb +14 -41
  57. data/lib/neo4j/driver/internal/extras.rb +23 -0
  58. data/lib/neo4j/driver/internal/home_db_cache.rb +57 -0
  59. data/lib/neo4j/driver/internal/internal_auth_token_manager.rb +37 -0
  60. data/lib/neo4j/driver/internal/metrics.rb +31 -0
  61. data/lib/neo4j/driver/internal/security/static_auth_token_manager.rb +25 -0
  62. data/lib/neo4j/driver/internal/validator.rb +10 -0
  63. data/lib/neo4j/driver/net.rb +29 -0
  64. data/lib/neo4j/driver/packstream/markers.rb +49 -0
  65. data/lib/neo4j/driver/packstream/packer.rb +333 -0
  66. data/lib/neo4j/driver/packstream/structure.rb +27 -0
  67. data/lib/neo4j/driver/packstream/unpacker.rb +168 -0
  68. data/lib/neo4j/driver/record.rb +49 -0
  69. data/lib/neo4j/driver/result.rb +282 -0
  70. data/lib/neo4j/driver/routing/load_balancer.rb +690 -0
  71. data/lib/neo4j/driver/routing/routed_connection.rb +132 -0
  72. data/lib/neo4j/driver/routing/routing_table.rb +127 -0
  73. data/lib/neo4j/driver/routing/server_address.rb +25 -0
  74. data/lib/neo4j/driver/routing_control.rb +13 -0
  75. data/lib/neo4j/driver/session.rb +575 -0
  76. data/lib/neo4j/driver/summary/database_info.rb +25 -0
  77. data/lib/neo4j/driver/summary/gql_notification.rb +38 -0
  78. data/lib/neo4j/driver/summary/gql_status_object.rb +35 -0
  79. data/lib/neo4j/driver/summary/notification.rb +46 -0
  80. data/lib/neo4j/driver/summary/plan.rb +23 -0
  81. data/lib/neo4j/driver/summary/profile.rb +28 -0
  82. data/lib/neo4j/driver/summary/query.rb +19 -0
  83. data/lib/neo4j/driver/summary/query_type.rb +5 -1
  84. data/lib/neo4j/driver/summary/result_summary.rb +213 -0
  85. data/lib/neo4j/driver/summary/server_info.rb +33 -0
  86. data/lib/neo4j/driver/summary/summary_counters.rb +72 -0
  87. data/lib/neo4j/driver/transaction.rb +341 -0
  88. data/lib/neo4j/driver/types/duration.rb +76 -0
  89. data/lib/neo4j/driver/types/local_date_time.rb +52 -8
  90. data/lib/neo4j/driver/types/local_time.rb +42 -7
  91. data/lib/neo4j/driver/types/node.rb +27 -0
  92. data/lib/neo4j/driver/types/offset_time.rb +71 -7
  93. data/lib/neo4j/driver/types/path.rb +68 -0
  94. data/lib/neo4j/driver/types/point.rb +38 -18
  95. data/lib/neo4j/driver/types/relationship.rb +41 -0
  96. data/lib/neo4j/driver/types/temporal_value.rb +43 -0
  97. data/lib/neo4j/driver/types/unbound_relationship.rb +29 -0
  98. data/lib/neo4j/driver/types/unresolvable_zoned_date_time.rb +38 -0
  99. data/lib/neo4j/driver/types/unsupported_type.rb +25 -0
  100. data/lib/neo4j/driver/types/uuid.rb +29 -0
  101. data/{ruby → lib}/neo4j/driver/version.rb +1 -1
  102. data/lib/neo4j/driver.rb +16 -0
  103. data/lib/neo4j-ruby-driver_loader.rb +34 -20
  104. metadata +132 -390
  105. data/lib/neo4j/driver/internal/bolt_server_address.rb +0 -97
  106. data/lib/neo4j/driver/synchronizable.rb +0 -23
  107. data/lib/neo4j/driver/types/time.rb +0 -45
  108. data/ruby/neo4j/driver/auth_tokens.rb +0 -34
  109. data/ruby/neo4j/driver/bookmark.rb +0 -21
  110. data/ruby/neo4j/driver/config.rb +0 -89
  111. data/ruby/neo4j/driver/graph_database.rb +0 -78
  112. data/ruby/neo4j/driver/internal/async/connection/bolt_protocol_util.rb +0 -51
  113. data/ruby/neo4j/driver/internal/async/connection/bootstrap_factory.rb +0 -22
  114. data/ruby/neo4j/driver/internal/async/connection/channel_attributes.rb +0 -31
  115. data/ruby/neo4j/driver/internal/async/connection/channel_connected_listener.rb +0 -32
  116. data/ruby/neo4j/driver/internal/async/connection/channel_connector_impl.rb +0 -83
  117. data/ruby/neo4j/driver/internal/async/connection/channel_pipeline_builder_impl.rb +0 -22
  118. data/ruby/neo4j/driver/internal/async/connection/direct_connection.rb +0 -30
  119. data/ruby/neo4j/driver/internal/async/connection/event_loop_group_factory.rb +0 -83
  120. data/ruby/neo4j/driver/internal/async/connection/handshake_completed_listener.rb +0 -27
  121. data/ruby/neo4j/driver/internal/async/connection/handshake_handler.rb +0 -113
  122. data/ruby/neo4j/driver/internal/async/connection/netty_channel_initializer.rb +0 -57
  123. data/ruby/neo4j/driver/internal/async/connection/netty_domain_name_resolver.rb +0 -26
  124. data/ruby/neo4j/driver/internal/async/connection/netty_domain_name_resolver_group.rb +0 -19
  125. data/ruby/neo4j/driver/internal/async/connection/routing_connection.rb +0 -36
  126. data/ruby/neo4j/driver/internal/async/connection/stream.rb +0 -12
  127. data/ruby/neo4j/driver/internal/async/connection/stream_reader.rb +0 -16
  128. data/ruby/neo4j/driver/internal/async/connection_context.rb +0 -10
  129. data/ruby/neo4j/driver/internal/async/immutable_connection_context.rb +0 -24
  130. data/ruby/neo4j/driver/internal/async/inbound/byte_buf_input.rb +0 -30
  131. data/ruby/neo4j/driver/internal/async/inbound/channel_error_handler.rb +0 -77
  132. data/ruby/neo4j/driver/internal/async/inbound/chunk_decoder.rb +0 -41
  133. data/ruby/neo4j/driver/internal/async/inbound/connect_timeout_handler.rb +0 -32
  134. data/ruby/neo4j/driver/internal/async/inbound/connection_read_timeout_handler.rb +0 -17
  135. data/ruby/neo4j/driver/internal/async/inbound/inbound_message_dispatcher.rb +0 -171
  136. data/ruby/neo4j/driver/internal/async/inbound/inbound_message_handler.rb +0 -42
  137. data/ruby/neo4j/driver/internal/async/inbound/message_decoder.rb +0 -51
  138. data/ruby/neo4j/driver/internal/async/internal_async_session.rb +0 -92
  139. data/ruby/neo4j/driver/internal/async/internal_async_transaction.rb +0 -13
  140. data/ruby/neo4j/driver/internal/async/leak_logging_network_session.rb +0 -34
  141. data/ruby/neo4j/driver/internal/async/network_connection.rb +0 -194
  142. data/ruby/neo4j/driver/internal/async/network_session.rb +0 -150
  143. data/ruby/neo4j/driver/internal/async/outbound/chunk_aware_byte_buf_output.rb +0 -110
  144. data/ruby/neo4j/driver/internal/async/outbound/outbound_message_handler.rb +0 -39
  145. data/ruby/neo4j/driver/internal/async/pool/channel.rb +0 -66
  146. data/ruby/neo4j/driver/internal/async/pool/channel_pool.rb +0 -31
  147. data/ruby/neo4j/driver/internal/async/pool/channel_tracker.rb +0 -135
  148. data/ruby/neo4j/driver/internal/async/pool/connection_pool_impl.rb +0 -156
  149. data/ruby/neo4j/driver/internal/async/pool/netty_channel_health_checker.rb +0 -87
  150. data/ruby/neo4j/driver/internal/async/pool/netty_channel_pool.rb +0 -52
  151. data/ruby/neo4j/driver/internal/async/pool/network_connection_factory.rb +0 -21
  152. data/ruby/neo4j/driver/internal/async/pool/pool_settings.rb +0 -34
  153. data/ruby/neo4j/driver/internal/async/pool/timed_stack.rb +0 -15
  154. data/ruby/neo4j/driver/internal/async/result_cursors_holder.rb +0 -17
  155. data/ruby/neo4j/driver/internal/async/unmanaged_transaction.rb +0 -212
  156. data/ruby/neo4j/driver/internal/bookmark_holder.rb +0 -9
  157. data/ruby/neo4j/driver/internal/cluster/cluster_composition.rb +0 -48
  158. data/ruby/neo4j/driver/internal/cluster/cluster_composition_lookup_result.rb +0 -14
  159. data/ruby/neo4j/driver/internal/cluster/cluster_routing_table.rb +0 -122
  160. data/ruby/neo4j/driver/internal/cluster/identity_resolver.rb +0 -10
  161. data/ruby/neo4j/driver/internal/cluster/loadbalancing/least_connected_load_balancing_strategy.rb +0 -68
  162. data/ruby/neo4j/driver/internal/cluster/loadbalancing/load_balancer.rb +0 -125
  163. data/ruby/neo4j/driver/internal/cluster/loadbalancing/round_robin_array_index.rb +0 -13
  164. data/ruby/neo4j/driver/internal/cluster/multi_databases_routing_procedure_runner.rb +0 -31
  165. data/ruby/neo4j/driver/internal/cluster/rediscovery_impl.rb +0 -147
  166. data/ruby/neo4j/driver/internal/cluster/route_message_routing_procedure_runner.rb +0 -43
  167. data/ruby/neo4j/driver/internal/cluster/routing_context.rb +0 -77
  168. data/ruby/neo4j/driver/internal/cluster/routing_procedure_cluster_composition_provider.rb +0 -60
  169. data/ruby/neo4j/driver/internal/cluster/routing_procedure_response.rb +0 -35
  170. data/ruby/neo4j/driver/internal/cluster/routing_settings.rb +0 -24
  171. data/ruby/neo4j/driver/internal/cluster/routing_table_handler_impl.rb +0 -95
  172. data/ruby/neo4j/driver/internal/cluster/routing_table_registry_impl.rb +0 -121
  173. data/ruby/neo4j/driver/internal/cluster/single_database_routing_procedure_runner.rb +0 -73
  174. data/ruby/neo4j/driver/internal/connection_settings.rb +0 -16
  175. data/ruby/neo4j/driver/internal/cursor/async_result_cursor_impl.rb +0 -55
  176. data/ruby/neo4j/driver/internal/cursor/async_result_cursor_only_factory.rb +0 -24
  177. data/ruby/neo4j/driver/internal/cursor/disposable_async_result_cursor.rb +0 -61
  178. data/ruby/neo4j/driver/internal/cursor/result_cursor_factory_impl.rb +0 -24
  179. data/ruby/neo4j/driver/internal/cursor/rx_result_cursor_impl.rb +0 -110
  180. data/ruby/neo4j/driver/internal/database_name_util.rb +0 -37
  181. data/ruby/neo4j/driver/internal/default_bookmark_holder.rb +0 -9
  182. data/ruby/neo4j/driver/internal/default_domain_name_resolver.rb +0 -11
  183. data/ruby/neo4j/driver/internal/direct_connection_provider.rb +0 -40
  184. data/ruby/neo4j/driver/internal/driver_factory.rb +0 -126
  185. data/ruby/neo4j/driver/internal/eager_result_value.rb +0 -5
  186. data/ruby/neo4j/driver/internal/handlers/begin_tx_response_handler.rb +0 -20
  187. data/ruby/neo4j/driver/internal/handlers/channel_releasing_reset_response_handler.rb +0 -30
  188. data/ruby/neo4j/driver/internal/handlers/commit_tx_response_handler.rb +0 -23
  189. data/ruby/neo4j/driver/internal/handlers/hello_response_handler.rb +0 -65
  190. data/ruby/neo4j/driver/internal/handlers/init_response_handler.rb +0 -34
  191. data/ruby/neo4j/driver/internal/handlers/legacy_pull_all_response_handler.rb +0 -199
  192. data/ruby/neo4j/driver/internal/handlers/no_op_response_handler.rb +0 -16
  193. data/ruby/neo4j/driver/internal/handlers/ping_response_handler.rb +0 -29
  194. data/ruby/neo4j/driver/internal/handlers/pull_handlers.rb +0 -32
  195. data/ruby/neo4j/driver/internal/handlers/pulln/auto_pull_response_handler.rb +0 -169
  196. data/ruby/neo4j/driver/internal/handlers/pulln/basic_pull_response_handler.rb +0 -298
  197. data/ruby/neo4j/driver/internal/handlers/pulln/fetch_size_util.rb +0 -20
  198. data/ruby/neo4j/driver/internal/handlers/reset_response_handler.rb +0 -34
  199. data/ruby/neo4j/driver/internal/handlers/rollback_tx_response_handler.rb +0 -25
  200. data/ruby/neo4j/driver/internal/handlers/route_message_response_handler.rb +0 -21
  201. data/ruby/neo4j/driver/internal/handlers/routing_response_handler.rb +0 -79
  202. data/ruby/neo4j/driver/internal/handlers/run_response_handler.rb +0 -38
  203. data/ruby/neo4j/driver/internal/handlers/session_pull_response_completion_listener.rb +0 -34
  204. data/ruby/neo4j/driver/internal/handlers/transaction_pull_response_completion_listener.rb +0 -20
  205. data/ruby/neo4j/driver/internal/impersonation_util.rb +0 -22
  206. data/ruby/neo4j/driver/internal/internal_bookmark.rb +0 -9
  207. data/ruby/neo4j/driver/internal/internal_database_name.rb +0 -9
  208. data/ruby/neo4j/driver/internal/internal_driver.rb +0 -83
  209. data/ruby/neo4j/driver/internal/internal_entity.rb +0 -21
  210. data/ruby/neo4j/driver/internal/internal_node.rb +0 -21
  211. data/ruby/neo4j/driver/internal/internal_pair.rb +0 -9
  212. data/ruby/neo4j/driver/internal/internal_path.rb +0 -35
  213. data/ruby/neo4j/driver/internal/internal_point2_d.rb +0 -9
  214. data/ruby/neo4j/driver/internal/internal_point3_d.rb +0 -6
  215. data/ruby/neo4j/driver/internal/internal_record.rb +0 -27
  216. data/ruby/neo4j/driver/internal/internal_relationship.rb +0 -27
  217. data/ruby/neo4j/driver/internal/internal_result.rb +0 -49
  218. data/ruby/neo4j/driver/internal/internal_session.rb +0 -91
  219. data/ruby/neo4j/driver/internal/internal_transaction.rb +0 -48
  220. data/ruby/neo4j/driver/internal/logging/channel_activity_logger.rb +0 -29
  221. data/ruby/neo4j/driver/internal/logging/channel_error_logger.rb +0 -17
  222. data/ruby/neo4j/driver/internal/logging/prefixed_logger.rb +0 -19
  223. data/ruby/neo4j/driver/internal/logging/reformatted_logger.rb +0 -17
  224. data/ruby/neo4j/driver/internal/messaging/abstract_message_writer.rb +0 -23
  225. data/ruby/neo4j/driver/internal/messaging/bolt_protocol.rb +0 -32
  226. data/ruby/neo4j/driver/internal/messaging/bolt_protocol_version.rb +0 -48
  227. data/ruby/neo4j/driver/internal/messaging/common/common_message_reader.rb +0 -51
  228. data/ruby/neo4j/driver/internal/messaging/common/common_value.rb +0 -33
  229. data/ruby/neo4j/driver/internal/messaging/common/common_value_packer.rb +0 -104
  230. data/ruby/neo4j/driver/internal/messaging/common/common_value_unpacker.rb +0 -256
  231. data/ruby/neo4j/driver/internal/messaging/encode/begin_message_encoder.rb +0 -15
  232. data/ruby/neo4j/driver/internal/messaging/encode/commit_message_encoder.rb +0 -14
  233. data/ruby/neo4j/driver/internal/messaging/encode/discard_all_message_encoder.rb +0 -14
  234. data/ruby/neo4j/driver/internal/messaging/encode/discard_message_encoder.rb +0 -15
  235. data/ruby/neo4j/driver/internal/messaging/encode/goodbye_message_encoder.rb +0 -14
  236. data/ruby/neo4j/driver/internal/messaging/encode/hello_message_encoder.rb +0 -15
  237. data/ruby/neo4j/driver/internal/messaging/encode/init_message_encoder.rb +0 -16
  238. data/ruby/neo4j/driver/internal/messaging/encode/logoff_message_encoder.rb +0 -14
  239. data/ruby/neo4j/driver/internal/messaging/encode/logon_message_encoder.rb +0 -15
  240. data/ruby/neo4j/driver/internal/messaging/encode/pull_all_message_encoder.rb +0 -14
  241. data/ruby/neo4j/driver/internal/messaging/encode/pull_message_encoder.rb +0 -15
  242. data/ruby/neo4j/driver/internal/messaging/encode/reset_message_encoder.rb +0 -14
  243. data/ruby/neo4j/driver/internal/messaging/encode/rollback_message_encoder.rb +0 -14
  244. data/ruby/neo4j/driver/internal/messaging/encode/route_message_encoder.rb +0 -24
  245. data/ruby/neo4j/driver/internal/messaging/encode/route_v44_message_encoder.rb +0 -22
  246. data/ruby/neo4j/driver/internal/messaging/encode/run_message_encoder.rb +0 -16
  247. data/ruby/neo4j/driver/internal/messaging/encode/run_with_metadata_message_encoder.rb +0 -17
  248. data/ruby/neo4j/driver/internal/messaging/request/abstract_streaming_message.rb +0 -25
  249. data/ruby/neo4j/driver/internal/messaging/request/begin_message.rb +0 -25
  250. data/ruby/neo4j/driver/internal/messaging/request/commit_message.rb +0 -20
  251. data/ruby/neo4j/driver/internal/messaging/request/discard_all_message.rb +0 -20
  252. data/ruby/neo4j/driver/internal/messaging/request/discard_message.rb +0 -23
  253. data/ruby/neo4j/driver/internal/messaging/request/goodbye_message.rb +0 -20
  254. data/ruby/neo4j/driver/internal/messaging/request/hello_message.rb +0 -30
  255. data/ruby/neo4j/driver/internal/messaging/request/init_message.rb +0 -19
  256. data/ruby/neo4j/driver/internal/messaging/request/logoff_message.rb +0 -13
  257. data/ruby/neo4j/driver/internal/messaging/request/logon_message.rb +0 -13
  258. data/ruby/neo4j/driver/internal/messaging/request/message_with_metadata.rb +0 -17
  259. data/ruby/neo4j/driver/internal/messaging/request/multi_database_util.rb +0 -26
  260. data/ruby/neo4j/driver/internal/messaging/request/pull_all_message.rb +0 -23
  261. data/ruby/neo4j/driver/internal/messaging/request/pull_message.rb +0 -22
  262. data/ruby/neo4j/driver/internal/messaging/request/reset_message.rb +0 -32
  263. data/ruby/neo4j/driver/internal/messaging/request/rollback_message.rb +0 -20
  264. data/ruby/neo4j/driver/internal/messaging/request/route_message.rb +0 -28
  265. data/ruby/neo4j/driver/internal/messaging/request/run_message.rb +0 -23
  266. data/ruby/neo4j/driver/internal/messaging/request/run_with_metadata_message.rb +0 -49
  267. data/ruby/neo4j/driver/internal/messaging/request/transaction_metadata_builder.rb +0 -24
  268. data/ruby/neo4j/driver/internal/messaging/response/failure_message.rb +0 -40
  269. data/ruby/neo4j/driver/internal/messaging/response/ignored_message.rb +0 -29
  270. data/ruby/neo4j/driver/internal/messaging/response/record_message.rb +0 -33
  271. data/ruby/neo4j/driver/internal/messaging/response/success_message.rb +0 -34
  272. data/ruby/neo4j/driver/internal/messaging/v3/bolt_protocol_v3.rb +0 -82
  273. data/ruby/neo4j/driver/internal/messaging/v3/message_format_v3.rb +0 -17
  274. data/ruby/neo4j/driver/internal/messaging/v3/message_writer_v3.rb +0 -27
  275. data/ruby/neo4j/driver/internal/messaging/v4/bolt_protocol_v4.rb +0 -29
  276. data/ruby/neo4j/driver/internal/messaging/v4/message_format_v4.rb +0 -13
  277. data/ruby/neo4j/driver/internal/messaging/v4/message_writer_v4.rb +0 -17
  278. data/ruby/neo4j/driver/internal/messaging/v41/bolt_protocol_v41.rb +0 -25
  279. data/ruby/neo4j/driver/internal/messaging/v42/bolt_protocol_v42.rb +0 -13
  280. data/ruby/neo4j/driver/internal/messaging/v43/bolt_protocol_v43.rb +0 -19
  281. data/ruby/neo4j/driver/internal/messaging/v43/message_format_v43.rb +0 -14
  282. data/ruby/neo4j/driver/internal/messaging/v43/message_writer_v43.rb +0 -20
  283. data/ruby/neo4j/driver/internal/messaging/v44/bolt_protocol_v44.rb +0 -17
  284. data/ruby/neo4j/driver/internal/messaging/v44/message_format_v44.rb +0 -14
  285. data/ruby/neo4j/driver/internal/messaging/v44/message_writer_v44.rb +0 -15
  286. data/ruby/neo4j/driver/internal/messaging/v5/bolt_protocol_v5.rb +0 -21
  287. data/ruby/neo4j/driver/internal/messaging/v5/message_format_v5.rb +0 -18
  288. data/ruby/neo4j/driver/internal/messaging/v5/value_unpacker_v5.rb +0 -17
  289. data/ruby/neo4j/driver/internal/messaging/v51/bolt_protocol_v51.rb +0 -40
  290. data/ruby/neo4j/driver/internal/messaging/v51/message_format_v51.rb +0 -14
  291. data/ruby/neo4j/driver/internal/messaging/v51/message_writer_v51.rb +0 -17
  292. data/ruby/neo4j/driver/internal/metrics/connection_pool_metrics_listener.rb +0 -34
  293. data/ruby/neo4j/driver/internal/metrics/internal_abstract_metrics.rb +0 -46
  294. data/ruby/neo4j/driver/internal/metrics/internal_connection_pool_metrics.rb +0 -105
  295. data/ruby/neo4j/driver/internal/metrics/internal_metrics.rb +0 -82
  296. data/ruby/neo4j/driver/internal/metrics/internal_metrics_provider.rb +0 -18
  297. data/ruby/neo4j/driver/internal/metrics/listener_event.rb +0 -17
  298. data/ruby/neo4j/driver/internal/metrics/metrics_provider.rb +0 -24
  299. data/ruby/neo4j/driver/internal/metrics/time_recorder_listener_event.rb +0 -15
  300. data/ruby/neo4j/driver/internal/packstream/byte_array_incompatible_packer.rb +0 -12
  301. data/ruby/neo4j/driver/internal/packstream/pack_input.rb +0 -47
  302. data/ruby/neo4j/driver/internal/packstream/pack_output.rb +0 -39
  303. data/ruby/neo4j/driver/internal/packstream/pack_stream.rb +0 -324
  304. data/ruby/neo4j/driver/internal/packstream/pack_type.rb +0 -17
  305. data/ruby/neo4j/driver/internal/read_only_bookmark_holder.rb +0 -13
  306. data/ruby/neo4j/driver/internal/resolved_bolt_server_address.rb +0 -35
  307. data/ruby/neo4j/driver/internal/retry/exponential_backoff_retry_logic.rb +0 -150
  308. data/ruby/neo4j/driver/internal/revocation_strategy.rb +0 -19
  309. data/ruby/neo4j/driver/internal/scheme.rb +0 -32
  310. data/ruby/neo4j/driver/internal/security/internal_auth_token.rb +0 -15
  311. data/ruby/neo4j/driver/internal/security/security_plan_impl.rb +0 -48
  312. data/ruby/neo4j/driver/internal/security_setting.rb +0 -66
  313. data/ruby/neo4j/driver/internal/session_factory_impl.rb +0 -32
  314. data/ruby/neo4j/driver/internal/spi/connection.rb +0 -19
  315. data/ruby/neo4j/driver/internal/spi/connection_pool.rb +0 -9
  316. data/ruby/neo4j/driver/internal/spi/response_handler.rb +0 -23
  317. data/ruby/neo4j/driver/internal/summary/internal_database_info.rb +0 -7
  318. data/ruby/neo4j/driver/internal/summary/internal_input_position.rb +0 -11
  319. data/ruby/neo4j/driver/internal/summary/internal_notification.rb +0 -16
  320. data/ruby/neo4j/driver/internal/summary/internal_plan.rb +0 -41
  321. data/ruby/neo4j/driver/internal/summary/internal_profiled_plan.rb +0 -32
  322. data/ruby/neo4j/driver/internal/summary/internal_result_summary.rb +0 -33
  323. data/ruby/neo4j/driver/internal/summary/internal_server_info.rb +0 -6
  324. data/ruby/neo4j/driver/internal/summary/internal_summary_counters.rb +0 -18
  325. data/ruby/neo4j/driver/internal/svm/netty_substitutions.rb +0 -196
  326. data/ruby/neo4j/driver/internal/svm/z_lib_substitutions.rb +0 -21
  327. data/ruby/neo4j/driver/internal/util/certificate_tool.rb +0 -65
  328. data/ruby/neo4j/driver/internal/util/clock.rb +0 -29
  329. data/ruby/neo4j/driver/internal/util/error_util.rb +0 -104
  330. data/ruby/neo4j/driver/internal/util/extract.rb +0 -123
  331. data/ruby/neo4j/driver/internal/util/format.rb +0 -39
  332. data/ruby/neo4j/driver/internal/util/futures.rb +0 -99
  333. data/ruby/neo4j/driver/internal/util/iterables.rb +0 -35
  334. data/ruby/neo4j/driver/internal/util/lock_util.rb +0 -23
  335. data/ruby/neo4j/driver/internal/util/metadata_extractor.rb +0 -101
  336. data/ruby/neo4j/driver/internal/util/mutex.rb +0 -9
  337. data/ruby/neo4j/driver/internal/util/preconditions.rb +0 -16
  338. data/ruby/neo4j/driver/internal/util/result_holder.rb +0 -72
  339. data/ruby/neo4j/driver/internal/util/server_version.rb +0 -60
  340. data/ruby/neo4j/driver/logging1.rb +0 -51
  341. data/ruby/neo4j/driver/net/server_address.rb +0 -9
  342. data/ruby/neo4j/driver/query.rb +0 -48
  343. data/ruby/neo4j/driver/records.rb +0 -13
  344. data/ruby/neo4j/driver/transaction_config.rb +0 -50
  345. data/ruby/neo4j/driver/values.rb +0 -26
  346. data/ruby/neo4j/driver.rb +0 -30
@@ -0,0 +1,690 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Neo4j
4
+ module Driver
5
+ module Routing
6
+ # Routing-aware connection provider. Mirrors the design of Java's
7
+ # RoutingTableHandlerImpl + LoadBalancer and Python's Neo4jPool:
8
+ #
9
+ # - Per-database RoutingTable cache, mutated through @refresh_lock.
10
+ # - Per-server connection pools (TimedStack), keyed by ServerAddress.
11
+ # - acquire(access_mode:) ensures the table is fresh, then loops
12
+ # selecting an address and opening a connection; on connection
13
+ # failure the address is `deactivate`d (forgotten from every
14
+ # table + per-server pool torn down) and we retry until the role
15
+ # bucket is exhausted.
16
+ # - Connections handed out are wrapped in RoutedConnection so the
17
+ # pool gets called back on write-side leader changes (NotALeader,
18
+ # ForbiddenOnReadOnlyDatabase) and stale-server connection errors.
19
+ class LoadBalancer
20
+ ROUTING_CONTEXT_RESERVED_KEYS = %w[address].freeze
21
+
22
+ # Codes that mean "this is a client mistake, retrying the
23
+ # routing fetch against another router won't help, fail fast".
24
+ # Matches Python's Neo4jError._is_fatal_during_discovery.
25
+ FATAL_DISCOVERY_CODES = %w[
26
+ Neo.ClientError.Database.DatabaseNotFound
27
+ Neo.ClientError.Transaction.InvalidBookmark
28
+ Neo.ClientError.Transaction.InvalidBookmarkMixture
29
+ Neo.ClientError.Statement.TypeError
30
+ Neo.ClientError.Statement.ArgumentError
31
+ Neo.ClientError.Request.Invalid
32
+ ].freeze
33
+
34
+ # `domain_name_resolver` is the factory-injected hostname->IPs hook
35
+ # (nil = system DNS); baked into every connection this balancer builds.
36
+ def initialize(uri, auth_manager, options = {}, domain_name_resolver: nil, clock: Internal::Clock.new)
37
+ @uri = uri
38
+ @auth_manager = auth_manager
39
+ @options = options
40
+ @clock = clock
41
+ @domain_name_resolver = domain_name_resolver
42
+ @routing_context = parse_routing_context(uri)
43
+ @pools = {} # ServerAddress => ConnectionPool::TimedStack
44
+ @routing_tables = {} # database (str or nil) => RoutingTable
45
+ @cursor = Hash.new(0) # round-robin per (database, role)
46
+ # Per-server authorization-expired generation counters (see
47
+ # Connection#auth_epoch and Direct::ConnectionProvider). Bumped for a
48
+ # server when one of its connections reports AuthorizationExpired, so
49
+ # the OTHER connections to that same server re-authenticate on their
50
+ # next acquire. Scoped per-address (not driver-wide) because the
51
+ # server's authorization cache is per-server — a reader's expiry must
52
+ # not force the writer pool to re-auth.
53
+ @auth_epochs = Hash.new(0)
54
+ # Monitor (reentrant) because ensure_routing_table_is_fresh holds
55
+ # the lock while it goes through pool_for, which also locks.
56
+ @refresh_lock = Monitor.new
57
+ # Optimization:HomeDatabaseCache — a driver-wide identity->home-db
58
+ # cache, consulted only when every open pooled connection advertises
59
+ # server-side routing (@ssr_with/@ssr_without track that tally, kept
60
+ # current by track_ssr_open/close as connections open and tear down).
61
+ @home_db_cache = Internal::HomeDbCache.new
62
+ @ssr_with = 0
63
+ @ssr_without = 0
64
+ @ssr_lock = Mutex.new
65
+ end
66
+
67
+ attr_reader :home_db_cache
68
+
69
+ # Home-db cache gate: true when there is at least one open pooled
70
+ # connection and every one advertised `ssr.enabled`, so the server will
71
+ # re-route an optimistically guessed home database.
72
+ def ssr_enabled? = @ssr_lock.synchronize { @ssr_with.positive? && @ssr_without.zero? }
73
+
74
+ def track_ssr_open(conn)
75
+ @ssr_lock.synchronize { conn.ssr_enabled? ? @ssr_with += 1 : @ssr_without += 1 }
76
+ end
77
+
78
+ def track_ssr_close(conn)
79
+ @ssr_lock.synchronize do
80
+ if conn.ssr_enabled?
81
+ @ssr_with -= 1 if @ssr_with.positive?
82
+ else
83
+ @ssr_without -= 1 if @ssr_without.positive?
84
+ end
85
+ end
86
+ end
87
+
88
+ # Open (or pop) a connection appropriate for `access_mode` against
89
+ # `database` (nil = home db). Loops: select an address, try to
90
+ # acquire, deactivate on connection failure and try again. Raises
91
+ # ServiceUnavailableException only when the role bucket has been
92
+ # exhausted by deactivations.
93
+ def acquire(access_mode: :write, database: nil, bookmarks: nil, imp_user: nil, auth: nil, deadline: nil)
94
+ # Fast-fail with IllegalStateException for use-after-close.
95
+ # Otherwise the next routing fetch would propagate a generic
96
+ # Connection-refused ServiceUnavailableException, masking the
97
+ # actual bug in the caller's lifecycle.
98
+ raise Exceptions::IllegalStateException, 'Driver is closed' if @closed
99
+
100
+ access_mode = access_mode.to_sym
101
+ # `auth` is the per-session override (nil = manager's current
102
+ # token). The worker identity is resolved per turn inside the loop
103
+ # below, not once here: on Bolt 5.0 an acquire that discards a
104
+ # token-rotated connection must re-consult the manager so the
105
+ # replacement issues its own get_token (mirrors
106
+ # Direct::ConnectionProvider — one extra get_auth per rotation).
107
+ # Discovery resolves its own identity independently, so a routed
108
+ # acquire with the default token consults the manager twice
109
+ # (get_auth_count == 2): once for the ROUTE connection, once for the
110
+ # worker. A session-carried token short-circuits both (count 0).
111
+ #
112
+ # imp_user is threaded into discovery so the ROUTE call enforces
113
+ # impersonation support (Bolt 4.4+) against the router, matching
114
+ # the RUN/BEGIN path — see Connection#route. `auth` is threaded so
115
+ # the ROUTE connection authenticates as the session's identity
116
+ # (per-session token) rather than always the manager's default.
117
+ # Resolve to the concrete database name: for a home-db acquire
118
+ # (database == nil) discovery returns the resolved name, which keys
119
+ # the table and the address selection below.
120
+ resolved_database = ensure_routing_table_is_fresh(
121
+ database, access_mode, bookmarks: bookmarks, imp_user: imp_user, auth: auth
122
+ ).database
123
+
124
+ last_error = nil
125
+ loop do
126
+ address = select_address(resolved_database, access_mode)
127
+ unless address
128
+ # Routing table yielded no usable server for this mode —
129
+ # a session-expired condition (the session can't be served,
130
+ # caller should get a fresh one), even if the last attempt
131
+ # failed with a connection-level ServiceUnavailable. Surface
132
+ # SessionExpired with that as the cause (matches Java).
133
+ # Explicit cause: this raise is outside the per-address
134
+ # rescue, so $! would not auto-populate it. cause: nil is
135
+ # fine when there was no connection-level failure.
136
+ raise Exceptions::SessionExpiredException,
137
+ "No #{access_mode} servers available for database #{resolved_database.inspect}",
138
+ cause: last_error
139
+ end
140
+
141
+ pool = pool_for(address)
142
+ begin
143
+ epoch = auth_epoch_for(address)
144
+ # Re-resolve per turn (see acquire header): a discard-and-retry
145
+ # on Bolt 5.0 token rotation must issue its own get_token.
146
+ effective = auth || @auth_manager.get_token
147
+ inner = pool.pop(auth: effective, deadline: deadline)
148
+ begin
149
+ ensure_identity(inner, effective, session_auth: auth, address: address, epoch: epoch)
150
+ rescue StandardError
151
+ # Don't leak the worker slot if identity enforcement fails
152
+ # (per-session auth on Bolt < 5.1, or a re-auth LOGON
153
+ # failure); discard it, then let the error propagate.
154
+ discard(address, inner)
155
+ raise
156
+ end
157
+ # On Bolt 5.0 ensure_identity can't re-auth in place, so a
158
+ # reused connection whose token or auth epoch is stale (token
159
+ # rotation, or an AuthorizationExpired refresh) must be discarded
160
+ # and replaced by a fresh one — mirrors Direct::ConnectionProvider.
161
+ unless inner.protocol.supports_re_auth? ||
162
+ (inner.auth == effective && inner.auth_epoch == epoch)
163
+ discard(address, inner)
164
+ next
165
+ end
166
+ return RoutedConnection.new(self, inner, address, access_mode, resolved_database)
167
+ rescue Exceptions::ServiceUnavailableException => e
168
+ # Server is unreachable (open_connection raised inside the
169
+ # pool's create block). Drop the address from every table
170
+ # and tear down its pool; loop and try another address.
171
+ last_error = e
172
+ deactivate(address)
173
+ end
174
+ end
175
+ end
176
+
177
+ # Current default identity from the auth-token manager (refreshes
178
+ # as needed); on_security_exception feeds a failure back to it.
179
+ # Mirror Direct::ConnectionProvider so Session stays polymorphic.
180
+ # Resolve the user's home database (database == nil): the ROUTE
181
+ # response carries the resolved name in `db` (Bolt 4.4+/5.x), which
182
+ # the routing table records as its `database`. The session uses the
183
+ # resolved name on RUN/BEGIN so the server doesn't re-resolve it per
184
+ # op. nil on the procedure path (3.0/4.0-4.2 have no db in the reply),
185
+ # where the server resolves the home db from a null `db` itself.
186
+ def home_database(bookmarks, imp_user = nil, auth = nil)
187
+ # Same use-after-close guard as #acquire: home-db resolution routes
188
+ # too, so a closed driver must fast-fail with IllegalStateException
189
+ # rather than re-routing into a Connection-refused
190
+ # ServiceUnavailableException. This is the ONLY routing entry for the
191
+ # single-database path (Bolt 3.0, database == nil) — it never reaches
192
+ # #acquire's guard — so without this a post-close session.run on a 3.0
193
+ # routing driver surfaced the wrong error type.
194
+ raise Exceptions::IllegalStateException, 'Driver is closed' if @closed
195
+
196
+ resolved = ensure_routing_table_is_fresh(nil, :read, bookmarks: bookmarks, imp_user: imp_user, auth: auth).database
197
+ # Remember the authoritative name (Optimization:HomeDatabaseCache) so a
198
+ # later same-identity session can guess it and skip discovery.
199
+ cache_home_db(imp_user, auth, resolved)
200
+ resolved
201
+ end
202
+
203
+ # Optimization:HomeDatabaseCache — this identity's last resolved home
204
+ # database, or nil when we can't optimistically guess it: no cache entry,
205
+ # or not every open connection does server-side routing (so the server
206
+ # might not re-route a stale guess). Used only to pick the routing table
207
+ # to acquire against — the operation still sends db=null so the server
208
+ # resolves the real home db.
209
+ def home_db_guess(imp_user, auth)
210
+ return nil unless ssr_enabled?
211
+
212
+ @home_db_cache.get(@home_db_cache.compute_key(imp_user, auth))
213
+ end
214
+
215
+ # Record the server's resolved home database for this identity.
216
+ def cache_home_db(imp_user, auth, database)
217
+ @home_db_cache.set(@home_db_cache.compute_key(imp_user, auth), database)
218
+ end
219
+
220
+ # Whether a usable table for `database` is already cached — so acquiring
221
+ # against it won't ROUTE. The home-db cache uses this to decide whether a
222
+ # guessed db can be sent as db=nil (table fresh, server resolves) or must
223
+ # be pinned (a ROUTE will run and authoritatively resolve it).
224
+ def routing_table_fresh?(database, access_mode = :read)
225
+ @refresh_lock.synchronize do
226
+ table = @routing_tables[database]
227
+ !!table&.fresh?(readonly: access_mode == :read)
228
+ end
229
+ end
230
+
231
+ def current_auth_token = @auth_manager.get_token
232
+
233
+ def on_security_exception(address, token, error, session_scoped = false)
234
+ # See Direct::ConnectionProvider#on_security_exception: an expired
235
+ # authorization cache forces the OTHER connections to that same
236
+ # server to re-auth. Provider-side, so it runs regardless of who
237
+ # owns the token; the manager notification is skipped for a
238
+ # per-session identity.
239
+ @refresh_lock.synchronize { @auth_epochs[address] += 1 } if error.is_a?(Exceptions::AuthorizationExpiredException)
240
+ return false if session_scoped
241
+
242
+ @auth_manager.handle_security_exception(token, error)
243
+ end
244
+
245
+ # Current auth generation for a server, read under the lock that guards
246
+ # the @auth_epochs Hash (mutated in on_security_exception).
247
+ def auth_epoch_for(address) = @refresh_lock.synchronize { @auth_epochs[address] }
248
+
249
+ def release(connection)
250
+ # Direct provider tolerates nil; mirror that. Internal callers
251
+ # always release a RoutedConnection but guard so the wrong
252
+ # type doesn't NoMethodError later.
253
+ return unless connection.respond_to?(:discard_on_release)
254
+
255
+ if connection.discard_on_release
256
+ discard(connection.address_obj, connection.inner)
257
+ return
258
+ end
259
+
260
+ @refresh_lock.synchronize do
261
+ pool = @pools[connection.address_obj]
262
+ pool&.push(connection.inner)
263
+ end
264
+ end
265
+
266
+ # Routing-aware verify_connectivity: force-refresh the routing
267
+ # table, then probe *any* reader to confirm the cluster is
268
+ # reachable. RESET so the borrowed connection lands back in the
269
+ # pool in a known-clean state (matches testkit's
270
+ # `test_routing_from_pool` expectation of one RESET per probe).
271
+ def verify_connectivity
272
+ invalidate_routing_table(nil)
273
+ conn = acquire(access_mode: :read)
274
+ begin
275
+ # propagate: a failed probe means a dead reader — surface it and
276
+ # discard the connection rather than pooling it (see
277
+ # Direct::ConnectionProvider#verify_connectivity).
278
+ conn.reset!(propagate: true)
279
+ rescue StandardError
280
+ conn.discard_on_release = true
281
+ release(conn)
282
+ raise
283
+ end
284
+ release(conn)
285
+ end
286
+
287
+ # Per-server-address pool snapshots (driver.metrics). One entry per
288
+ # server we hold a pool for; the address key is the Routing::ServerAddress
289
+ # the pool was created under.
290
+ def connection_pool_metrics
291
+ @refresh_lock.synchronize do
292
+ @pools.map do |address, pool|
293
+ in_use, idle = pool.metrics_snapshot
294
+ Internal::Metrics::ConnectionPoolMetrics.new(address, in_use, idle)
295
+ end
296
+ end
297
+ end
298
+
299
+ # Routing requires Bolt 4.0+ (the ROUTE message and `CALL
300
+ # dbms.routing.getRoutingTable` are 4.0+ features). If we got
301
+ # this far the answer is always true.
302
+ # Multi-database support is a property of the negotiated Bolt
303
+ # version (4.0+), not of routing per se — a neo4j:// driver against a
304
+ # 3.0 cluster still routes (via the getRoutingTable procedure) but
305
+ # does NOT support multiple databases. Probe an actual connection's
306
+ # protocol, mirroring Direct::ConnectionProvider. The acquire does
307
+ # discovery + a reader HELLO (no query), then the connection is
308
+ # released straight back to the pool.
309
+ def supports_multi_db?
310
+ conn = acquire(access_mode: :read)
311
+ conn.protocol.supports_multiple_databases?
312
+ ensure
313
+ release(conn) if conn
314
+ end
315
+
316
+ def close
317
+ @refresh_lock.synchronize do
318
+ @closed = true
319
+ @pools.each_value { |pool| pool.shutdown { |conn| conn.close rescue nil } }
320
+ @pools.clear
321
+ @routing_tables.clear
322
+ end
323
+ end
324
+
325
+ # Internal — mirrors Java's
326
+ # ConnectionProvider#getRoutingTableRegistry(). LoadBalancer is
327
+ # both the connection provider and the routing-table registry;
328
+ # the layer split exists in Java mostly because it predates
329
+ # generics. Used by testkit's GetRoutingTable handler.
330
+ def routing_table_registry = self
331
+
332
+ # Internal — mirrors Java's
333
+ # RoutingTableRegistry#getRoutingTableHandler(databaseName).
334
+ # Pure read: returns the cached table for the database, or a
335
+ # new empty placeholder if no table has ever been fetched.
336
+ # (Empty means routers/readers/writers are all empty — it is
337
+ # NOT `fresh?`; callers that want a fetched table go through
338
+ # ensure_routing_table_is_fresh.) Deliberately does NOT force
339
+ # a fetch — testkit's get_routing_table contract is "what's
340
+ # currently known", and an auto-fetch here causes a second
341
+ # ROUTE on a stub server that already hung up after the first
342
+ # (see test_should_fail_on_routing_table_with_no_reader).
343
+ def routing_table_handler(database)
344
+ @refresh_lock.synchronize do
345
+ Handler.new(@routing_tables[database] || RoutingTable.new(database: database, clock: @clock))
346
+ end
347
+ end
348
+
349
+ # Internal — force a fresh ROUTE call for the given database
350
+ # regardless of TTL/cache. Used by ForcedRoutingTableUpdate
351
+ # testkit handler. Threads bookmarks through to the ROUTE
352
+ # payload so causal-consistency assertions work.
353
+ def refresh(database, bookmarks = nil)
354
+ @refresh_lock.synchronize do
355
+ invalidate_routing_table(database)
356
+ update_routing_table(database, bookmarks: bookmarks)
357
+ end
358
+ end
359
+
360
+ # Mirrors org.neo4j.driver.internal.cluster.RoutingTableHandler
361
+ # for the slim surface testkit reads (just .routing_table).
362
+ Handler = Struct.new(:routing_table)
363
+
364
+ # Called by RoutedConnection on a fatal connection-level error
365
+ # (or DatabaseUnavailable). Removes the address from every
366
+ # database's routing table and tears down its connection pool.
367
+ def deactivate(address)
368
+ @refresh_lock.synchronize do
369
+ @routing_tables.each_value { |table| table.forget(address) }
370
+ pool = @pools.delete(address)
371
+ pool&.shutdown { |conn| conn.close rescue nil }
372
+ end
373
+ end
374
+
375
+ # Called by RoutedConnection on a write-mode operation that hit
376
+ # NotALeader / ForbiddenOnReadOnlyDatabase. The server is alive
377
+ # but no longer the leader for this db — drop it from the writers
378
+ # bucket only; routers/readers stay.
379
+ def on_write_failure(address, database)
380
+ @refresh_lock.synchronize do
381
+ @routing_tables[database]&.forget_writer(address)
382
+ end
383
+ end
384
+
385
+ private
386
+
387
+ def parse_routing_context(uri)
388
+ context = {}
389
+ unless uri.query.nil? || uri.query.empty?
390
+ URI.decode_www_form(uri.query).each do |k, v|
391
+ raise ArgumentError, "Routing context key '#{k}' is reserved" if ROUTING_CONTEXT_RESERVED_KEYS.include?(k)
392
+
393
+ context[k.to_sym] = v
394
+ end
395
+ end
396
+ # Seed `address` is always part of the routing context, regardless
397
+ # of whether the URI carried query params. Otherwise the ROUTE
398
+ # payload silently differs between `neo4j://host` and
399
+ # `neo4j://host?k=v`.
400
+ context[:address] = "#{uri.host}:#{uri.port || ServerAddress::DEFAULT_PORT}"
401
+ context
402
+ end
403
+
404
+ # Make sure a usable routing table exists for `database` and the
405
+ # requested `access_mode`. Cheap fast path when the cached table
406
+ # is still fresh; otherwise fetches one inside the lock with a
407
+ # second freshness check (double-checked locking pattern matches
408
+ # Python's ensure_routing_table_is_fresh). Optional `bookmarks`
409
+ # are threaded into the ROUTE payload.
410
+ def ensure_routing_table_is_fresh(database, access_mode, bookmarks: nil, imp_user: nil, auth: nil)
411
+ @refresh_lock.synchronize do
412
+ table = @routing_tables[database]
413
+ return table if table && table.fresh?(readonly: access_mode == :read)
414
+
415
+ update_routing_table(database, bookmarks: bookmarks, imp_user: imp_user, auth: auth)
416
+ end
417
+ end
418
+
419
+ def invalidate_routing_table(database)
420
+ @refresh_lock.synchronize { @routing_tables.delete(database) }
421
+ end
422
+
423
+ # Refresh the routing table from a router. Routers are tried in
424
+ # priority order: prefer the seed address if we have no table or
425
+ # the existing table came back without writers (likely-stale),
426
+ # otherwise prefer the existing routers list first. Each failed
427
+ # router is `deactivate`d before moving on to the next.
428
+ def update_routing_table(database, bookmarks: nil, imp_user: nil, auth: nil)
429
+ existing = @routing_tables[database]
430
+ prefer_seed = existing.nil? || existing.initialized_without_writers
431
+
432
+ errors = []
433
+ routers_in_order(existing, prefer_seed: prefer_seed).each do |router|
434
+ new_table = fetch_routing_table_from(router, database, bookmarks, errors, imp_user, auth)
435
+ next unless new_table
436
+
437
+ apply_routing_table(new_table)
438
+ return @routing_tables[new_table.database]
439
+ end
440
+
441
+ last = errors.last
442
+ raise Exceptions::ServiceUnavailableException.new(
443
+ "Unable to retrieve routing information for database #{database.inspect}: " \
444
+ "tried #{errors.length} router(s) without success" \
445
+ "#{last ? " (last: #{last.message})" : ''}",
446
+ suppressed: errors
447
+ )
448
+ end
449
+
450
+ def routers_in_order(existing, prefer_seed:)
451
+ seeds = resolved_seed_routers
452
+ existing_others = existing ? existing.routers.to_a - seeds : []
453
+ prefer_seed ? [*seeds, *existing_others] : [*existing_others, *seeds]
454
+ end
455
+
456
+ # The seed router(s) to bootstrap discovery from. A custom address
457
+ # resolver (Config#resolver, Java's ServerAddressResolver) expands the
458
+ # driver's URI address into the set of initial routers — re-resolved
459
+ # on every rediscovery so a changed cluster membership / router IP is
460
+ # picked up. Without a resolver the single URI address is the only
461
+ # seed. (Hostname->IP resolution is a separate, connect-time concern;
462
+ # see Bolt::Connection#resolved_addresses.)
463
+ def resolved_seed_routers
464
+ seed = seed_router
465
+ return [seed] unless (resolver = @options[:resolver])
466
+
467
+ Array(resolver.call(seed.to_s)).map { |addr| ServerAddress.parse(addr.to_s) }
468
+ end
469
+
470
+ def seed_router
471
+ ServerAddress.parse("#{@uri.host}:#{@uri.port || ServerAddress::DEFAULT_PORT}")
472
+ end
473
+
474
+ # Returns a new RoutingTable on success, nil on a per-router
475
+ # failure. Connection-level failures additionally `deactivate`
476
+ # the router; protocol-level failures (e.g. ClientException
477
+ # while routing) just record the error so the caller tries the
478
+ # next router.
479
+ def fetch_routing_table_from(router, database, bookmarks, errors, imp_user = nil, auth = nil)
480
+ pool = pool_for(router)
481
+ conn = nil
482
+ begin
483
+ # Discovery's identity is resolved per turn inside the loop below
484
+ # (per-session token short-circuits the manager consult). It sets
485
+ # BOTH the fresh-connection token and the re-auth of a reused
486
+ # router connection: pool.pop(auth:) only sets a *fresh*
487
+ # connection's identity, so without ensure_identity a reused
488
+ # ROUTE connection would run under the previous lessee's user
489
+ # (and skip the Bolt 5.1 gate for per-session auth). Re-resolving
490
+ # each turn means a Bolt 5.0 discard-and-rebuild on token rotation
491
+ # issues its own get_token, matching the worker path in #acquire.
492
+ # pop is inside the begin block on purpose: open_connection
493
+ # is the pool's create block and can raise ServiceUnavailable
494
+ # (router unreachable). If that escaped the method,
495
+ # update_routing_table's iteration over remaining routers
496
+ # would stop on the first unreachable one.
497
+ #
498
+ # Loop for the same Bolt 5.0 reason as the worker path in #acquire:
499
+ # a reused router connection that can't re-auth in place and whose
500
+ # token/epoch is stale (rotation, AuthorizationExpired) is discarded
501
+ # and rebuilt rather than ROUTEing under a stale identity.
502
+ loop do
503
+ epoch = auth_epoch_for(router)
504
+ effective = auth || @auth_manager.get_token
505
+ conn = pool.pop(auth: effective)
506
+ ensure_identity(conn, effective, session_auth: auth, address: router, epoch: epoch)
507
+ break if conn.protocol.supports_re_auth? ||
508
+ (conn.auth == effective && conn.auth_epoch == epoch)
509
+
510
+ discard(router, conn)
511
+ conn = nil
512
+ end
513
+ rt = conn.route(database: database, bookmarks: Array(bookmarks),
514
+ imp_user: imp_user, routing_context: @routing_context)
515
+ new_table = RoutingTable.from_response(symbolize(rt), database, clock: @clock)
516
+
517
+ if new_table.routers.empty? || new_table.readers.empty?
518
+ errors << Exceptions::ServiceUnavailableException.new(
519
+ "Router #{router} returned a routing table with no " \
520
+ "#{new_table.routers.empty? ? 'routers' : 'readers'}"
521
+ )
522
+ pool.push(conn)
523
+ return nil
524
+ end
525
+
526
+ pool.push(conn)
527
+ new_table
528
+ rescue Exceptions::ServiceUnavailableException, ::Timeout::Error => e
529
+ errors << e
530
+ # Connection (if any) is presumed dead; deactivate tears
531
+ # down the pool so the conn's `created` slot is reclaimed.
532
+ conn&.close rescue nil
533
+ deactivate(router)
534
+ nil
535
+ rescue Exceptions::Neo4jException => e
536
+ # Connection is in FAILED state; drop it before we either
537
+ # re-raise (fatal) or try the next router (transient).
538
+ discard(router, conn) if conn
539
+ raise if fatal_during_discovery?(e)
540
+
541
+ errors << e
542
+ nil
543
+ end
544
+ end
545
+
546
+ # A subset of Neo.ClientError.* codes means the request itself
547
+ # is unsatisfiable — retrying against a different router can't
548
+ # help. Propagate immediately so the caller sees the original
549
+ # exception class and code rather than a wrapped
550
+ # ServiceUnavailableException with the original buried in
551
+ # `suppressed`. Mirrors Python's _is_fatal_during_discovery.
552
+ def fatal_during_discovery?(error)
553
+ code = error.code.to_s
554
+ return true if FATAL_DISCOVERY_CODES.include?(code)
555
+
556
+ # A client-side ClientException carries no server code — it was
557
+ # raised by the driver while building the request (e.g.
558
+ # impersonation over a pre-4.4 protocol), so it is identical on
559
+ # every router and retrying can't help. Propagate it rather than
560
+ # collecting it and masking it as a ServiceUnavailableException.
561
+ return true if code.empty? && error.is_a?(Exceptions::ClientException)
562
+
563
+ code.start_with?('Neo.ClientError.Security.') &&
564
+ code != 'Neo.ClientError.Security.AuthorizationExpired'
565
+ end
566
+
567
+ # Cache the fetched table under its resolved database name. For an
568
+ # explicit-database acquire the resolved name equals the request; for a
569
+ # home-db acquire (requested nil) it is the name the router resolved, so
570
+ # home-db tables are keyed by their real name, never under nil.
571
+ def apply_routing_table(new_table)
572
+ database = new_table.database
573
+ existing = @routing_tables[database]
574
+ if existing
575
+ existing.update(new_table)
576
+ else
577
+ @routing_tables[database] = new_table
578
+ end
579
+ end
580
+
581
+ # Round-robin within the role bucket of the table for `database`.
582
+ # Returns nil when the bucket is empty (typically after one or
583
+ # more deactivations during the acquire loop).
584
+ def select_address(database, access_mode)
585
+ @refresh_lock.synchronize do
586
+ table = @routing_tables[database] or return nil
587
+ servers = table.servers_for(access_mode).to_a
588
+ return nil if servers.empty?
589
+
590
+ key = [database, access_mode]
591
+ address = servers[@cursor[key] % servers.size]
592
+ @cursor[key] += 1
593
+ address
594
+ end
595
+ end
596
+
597
+ # Close a checked-out connection without putting it back. Used
598
+ # when the connection is in a known-bad state (server FAILED,
599
+ # write-failure on a NotALeader, etc.) so we don't poison the
600
+ # pool. Bolt::Pool#discard closes and frees the slot so the
601
+ # next pop can lazily build a fresh one.
602
+ def discard(address, conn)
603
+ @refresh_lock.synchronize do
604
+ @pools[address]&.discard(conn)
605
+ end
606
+ end
607
+
608
+ def pool_for(address)
609
+ @refresh_lock.synchronize do
610
+ @pools[address] ||= Bolt::Pool.new(
611
+ size: max_pool_size,
612
+ options: @options,
613
+ clock: @clock,
614
+ connect_factory: ->(auth, deadline = nil) { open_connection(address, auth, deadline) }
615
+ )
616
+ end
617
+ end
618
+
619
+ def open_connection(address, auth = nil, deadline = nil)
620
+ # Preserve the encryption suffix: neo4j+s → bolt+s,
621
+ # neo4j+ssc → bolt+ssc, neo4j → bolt. Otherwise routing
622
+ # connections to a TLS cluster would open plaintext and the
623
+ # server would reject the non-TLS first record.
624
+ uri = "#{@uri.scheme.sub('neo4j', 'bolt')}://#{address}"
625
+ # routing_context goes into the HELLO map so the cluster can
626
+ # apply the configured policy / region (the same routing
627
+ # context is sent to readers/writers too; non-router servers
628
+ # just ignore it).
629
+ opts = @options.merge(routing_context: @routing_context)
630
+ # `auth` is the per-acquire identity (per-session token or the
631
+ # worker's resolved token). Fall back to the manager's current
632
+ # token for acquires that don't carry one (verify_connectivity).
633
+ conn = Bolt::Connection.new(uri, auth || @auth_manager.get_token, opts,
634
+ domain_name_resolver: @domain_name_resolver, clock: @clock).connect(deadline: deadline)
635
+ # Bind the security handler to this connection's server so an
636
+ # AuthorizationExpired bumps the right per-address epoch.
637
+ conn.security_exception_handler =
638
+ ->(token, error, session_scoped = false) { on_security_exception(address, token, error, session_scoped) }
639
+ # A freshly-authenticated connection belongs to the current auth
640
+ # generation for its server, so ensure_identity won't force-re-auth it.
641
+ conn.auth_epoch = auth_epoch_for(address)
642
+ # Fold this connection into the pool-wide SSR tally (and out of it when
643
+ # it tears down) so the home-db cache gate reflects live connections.
644
+ track_ssr_open(conn)
645
+ conn.on_close = method(:track_ssr_close)
646
+ conn
647
+ end
648
+
649
+ # Bring a popped worker connection to the `effective` identity —
650
+ # no-op for a fresh connection (built with it), LOGOFF/LOGON for a
651
+ # reused one. Mirrors Direct::ConnectionProvider#ensure_identity;
652
+ # see there for the session-auth-needs-5.1 rationale.
653
+ def ensure_identity(conn, effective, session_auth:, address:, epoch: nil)
654
+ # See Direct::ConnectionProvider#ensure_identity — tag the
655
+ # connection's current identity so security failures only notify
656
+ # the manager for the manager's own (default) token, and force a
657
+ # re-auth when this connection predates the latest AuthorizationExpired
658
+ # for its server.
659
+ conn.session_scoped_auth = !session_auth.nil?
660
+ epoch ||= auth_epoch_for(address)
661
+ force = conn.auth_epoch < epoch
662
+ if session_auth
663
+ unless conn.protocol.supports_re_auth?
664
+ raise Exceptions::UnsupportedFeatureException,
665
+ "Per-session auth requires Bolt 5.1+; negotiated #{conn.protocol.version}"
666
+ end
667
+ conn.authenticate(effective, force: force)
668
+ conn.auth_epoch = epoch
669
+ elsif conn.protocol.supports_re_auth?
670
+ conn.authenticate(effective, force: force)
671
+ conn.auth_epoch = epoch
672
+ end
673
+ end
674
+
675
+ def symbolize(value)
676
+ case value
677
+ when Hash then value.transform_keys(&:to_sym).transform_values { symbolize(it) }
678
+ when Array then value.map { symbolize(it) }
679
+ else value
680
+ end
681
+ end
682
+
683
+ def max_pool_size
684
+ @options[:max_connection_pool_size] || Driver::DEFAULT_MAX_POOL_SIZE
685
+ end
686
+
687
+ end
688
+ end
689
+ end
690
+ end