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