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.
- checksums.yaml +4 -4
- data/README.md +113 -411
- data/{ruby → lib}/neo4j/driver/access_mode.rb +2 -1
- data/lib/neo4j/driver/auth_token_managers.rb +31 -0
- data/lib/neo4j/driver/auth_tokens.rb +41 -0
- data/lib/neo4j/driver/auto_commit_retries_mode.rb +18 -0
- data/lib/neo4j/driver/bolt/bolt_version.rb +67 -0
- data/lib/neo4j/driver/bolt/connection.rb +1091 -0
- data/lib/neo4j/driver/bolt/handshake.rb +236 -0
- data/lib/neo4j/driver/bolt/message/failure.rb +139 -0
- data/lib/neo4j/driver/bolt/message/ignored.rb +27 -0
- data/lib/neo4j/driver/bolt/message/record.rb +30 -0
- data/lib/neo4j/driver/bolt/message/success.rb +34 -0
- data/lib/neo4j/driver/bolt/message.rb +112 -0
- data/lib/neo4j/driver/bolt/pool.rb +255 -0
- data/lib/neo4j/driver/bolt/protocol/base.rb +146 -0
- data/lib/neo4j/driver/bolt/protocol/v3.rb +43 -0
- data/lib/neo4j/driver/bolt/protocol/v4.rb +24 -0
- data/lib/neo4j/driver/bolt/protocol/v43.rb +28 -0
- data/lib/neo4j/driver/bolt/protocol/v44.rb +23 -0
- data/lib/neo4j/driver/bolt/protocol/v5.rb +27 -0
- data/lib/neo4j/driver/bolt/protocol/v51.rb +24 -0
- data/lib/neo4j/driver/bolt/protocol/v52.rb +28 -0
- data/lib/neo4j/driver/bolt/protocol/v53.rb +27 -0
- data/lib/neo4j/driver/bolt/protocol/v54.rb +14 -0
- data/lib/neo4j/driver/bolt/protocol/v55.rb +17 -0
- data/lib/neo4j/driver/bolt/protocol/v56.rb +14 -0
- data/lib/neo4j/driver/bolt/protocol/v57.rb +31 -0
- data/lib/neo4j/driver/bolt/protocol/v58.rb +15 -0
- data/lib/neo4j/driver/bolt/protocol/v6.rb +43 -0
- data/lib/neo4j/driver/bolt/protocol/v61.rb +21 -0
- data/lib/neo4j/driver/bolt/protocol_version_handler.rb +39 -0
- data/lib/neo4j/driver/bolt/record_buffer.rb +143 -0
- data/lib/neo4j/driver/bolt/stream_handler.rb +53 -0
- data/lib/neo4j/driver/bolt/tls_config.rb +129 -0
- data/lib/neo4j/driver/bolt/wire.rb +321 -0
- data/lib/neo4j/driver/bookmark.rb +15 -0
- data/lib/neo4j/driver/bookmark_managers.rb +39 -0
- data/lib/neo4j/driver/client_certificate.rb +19 -0
- data/lib/neo4j/driver/client_certificate_manager.rb +37 -0
- data/lib/neo4j/driver/client_certificate_managers.rb +15 -0
- data/lib/neo4j/driver/client_certificates.rb +14 -0
- data/lib/neo4j/driver/direct/connection_provider.rb +262 -0
- data/lib/neo4j/driver/driver.rb +268 -0
- data/lib/neo4j/driver/eager_result.rb +10 -0
- data/lib/neo4j/driver/exceptions/neo4j_exception.rb +16 -7
- data/lib/neo4j/driver/exceptions/no_such_record_exception.rb +1 -1
- data/lib/neo4j/driver/exceptions/protocol_exception.rb +0 -5
- data/lib/neo4j/driver/exceptions/security_retryable_exception.rb +14 -0
- data/lib/neo4j/driver/exceptions/transaction_terminated_exception.rb +13 -0
- data/lib/neo4j/driver/graph_database.rb +29 -0
- data/lib/neo4j/driver/internal/clock.rb +20 -0
- data/lib/neo4j/driver/internal/clock_adapter.rb +21 -0
- data/lib/neo4j/driver/internal/default_bookmark_manager.rb +54 -0
- data/lib/neo4j/driver/internal/deprecator.rb +9 -5
- data/lib/neo4j/driver/internal/driver_factory.rb +128 -0
- data/lib/neo4j/driver/internal/duration_normalizer.rb +14 -39
- data/lib/neo4j/driver/internal/extras.rb +23 -0
- data/lib/neo4j/driver/internal/home_db_cache.rb +57 -0
- data/lib/neo4j/driver/internal/internal_auth_token_manager.rb +37 -0
- data/lib/neo4j/driver/internal/metrics.rb +31 -0
- data/lib/neo4j/driver/internal/security/static_auth_token_manager.rb +25 -0
- data/lib/neo4j/driver/internal/validator.rb +16 -2
- data/lib/neo4j/driver/net.rb +29 -0
- data/lib/neo4j/driver/packstream/markers.rb +49 -0
- data/lib/neo4j/driver/packstream/packer.rb +335 -0
- data/lib/neo4j/driver/packstream/structure.rb +27 -0
- data/lib/neo4j/driver/packstream/unpacker.rb +170 -0
- data/lib/neo4j/driver/record.rb +43 -0
- data/lib/neo4j/driver/result.rb +282 -0
- data/lib/neo4j/driver/routing/load_balancer.rb +706 -0
- data/lib/neo4j/driver/routing/routed_connection.rb +132 -0
- data/lib/neo4j/driver/routing/routing_table.rb +127 -0
- data/lib/neo4j/driver/routing/server_address.rb +25 -0
- data/lib/neo4j/driver/routing_control.rb +13 -0
- data/lib/neo4j/driver/session.rb +577 -0
- data/lib/neo4j/driver/summary/database_info.rb +25 -0
- data/lib/neo4j/driver/summary/gql_notification.rb +38 -0
- data/lib/neo4j/driver/summary/gql_status_object.rb +35 -0
- data/lib/neo4j/driver/summary/notification.rb +46 -0
- data/lib/neo4j/driver/summary/plan.rb +23 -0
- data/lib/neo4j/driver/summary/profile.rb +28 -0
- data/lib/neo4j/driver/summary/query.rb +19 -0
- data/lib/neo4j/driver/summary/query_type.rb +5 -1
- data/lib/neo4j/driver/summary/result_summary.rb +215 -0
- data/lib/neo4j/driver/summary/server_info.rb +33 -0
- data/lib/neo4j/driver/summary/summary_counters.rb +72 -0
- data/lib/neo4j/driver/transaction.rb +344 -0
- data/lib/neo4j/driver/types/duration.rb +76 -0
- data/lib/neo4j/driver/types/entity.rb +40 -0
- data/lib/neo4j/driver/types/local_date_time.rb +77 -8
- data/lib/neo4j/driver/types/local_time.rb +50 -7
- data/lib/neo4j/driver/types/node.rb +18 -0
- data/lib/neo4j/driver/types/offset_time.rb +78 -7
- data/lib/neo4j/driver/types/path.rb +68 -0
- data/lib/neo4j/driver/types/point.rb +38 -18
- data/lib/neo4j/driver/types/relationship.rb +32 -0
- data/lib/neo4j/driver/types/temporal_value.rb +44 -0
- data/lib/neo4j/driver/types/unbound_relationship.rb +24 -0
- data/lib/neo4j/driver/types/unresolvable_zoned_date_time.rb +38 -0
- data/lib/neo4j/driver/types/unsupported_type.rb +25 -0
- data/lib/neo4j/driver/types/uuid.rb +29 -0
- data/{ruby → lib}/neo4j/driver/version.rb +1 -1
- data/lib/neo4j/driver.rb +15 -0
- data/lib/neo4j-ruby-driver_loader.rb +34 -20
- metadata +115 -441
- data/lib/neo4j/driver/internal/bolt_server_address.rb +0 -97
- data/lib/neo4j/driver/synchronizable.rb +0 -23
- data/lib/neo4j/driver/types/time.rb +0 -45
- data/ruby/neo4j/driver/auth_tokens.rb +0 -34
- data/ruby/neo4j/driver/bookmark.rb +0 -21
- data/ruby/neo4j/driver/config.rb +0 -89
- data/ruby/neo4j/driver/graph_database.rb +0 -78
- data/ruby/neo4j/driver/internal/async/connection/bolt_protocol_util.rb +0 -51
- data/ruby/neo4j/driver/internal/async/connection/bootstrap_factory.rb +0 -22
- data/ruby/neo4j/driver/internal/async/connection/channel_attributes.rb +0 -31
- data/ruby/neo4j/driver/internal/async/connection/channel_connected_listener.rb +0 -32
- data/ruby/neo4j/driver/internal/async/connection/channel_connector_impl.rb +0 -83
- data/ruby/neo4j/driver/internal/async/connection/channel_pipeline_builder_impl.rb +0 -22
- data/ruby/neo4j/driver/internal/async/connection/direct_connection.rb +0 -30
- data/ruby/neo4j/driver/internal/async/connection/event_loop_group_factory.rb +0 -83
- data/ruby/neo4j/driver/internal/async/connection/handshake_completed_listener.rb +0 -27
- data/ruby/neo4j/driver/internal/async/connection/handshake_handler.rb +0 -113
- data/ruby/neo4j/driver/internal/async/connection/netty_channel_initializer.rb +0 -57
- data/ruby/neo4j/driver/internal/async/connection/netty_domain_name_resolver.rb +0 -26
- data/ruby/neo4j/driver/internal/async/connection/netty_domain_name_resolver_group.rb +0 -19
- data/ruby/neo4j/driver/internal/async/connection/routing_connection.rb +0 -36
- data/ruby/neo4j/driver/internal/async/connection/stream.rb +0 -12
- data/ruby/neo4j/driver/internal/async/connection/stream_reader.rb +0 -16
- data/ruby/neo4j/driver/internal/async/connection_context.rb +0 -10
- data/ruby/neo4j/driver/internal/async/immutable_connection_context.rb +0 -24
- data/ruby/neo4j/driver/internal/async/inbound/byte_buf_input.rb +0 -30
- data/ruby/neo4j/driver/internal/async/inbound/channel_error_handler.rb +0 -77
- data/ruby/neo4j/driver/internal/async/inbound/chunk_decoder.rb +0 -41
- data/ruby/neo4j/driver/internal/async/inbound/connect_timeout_handler.rb +0 -32
- data/ruby/neo4j/driver/internal/async/inbound/connection_read_timeout_handler.rb +0 -17
- data/ruby/neo4j/driver/internal/async/inbound/inbound_message_dispatcher.rb +0 -171
- data/ruby/neo4j/driver/internal/async/inbound/inbound_message_handler.rb +0 -42
- data/ruby/neo4j/driver/internal/async/inbound/message_decoder.rb +0 -51
- data/ruby/neo4j/driver/internal/async/internal_async_session.rb +0 -92
- data/ruby/neo4j/driver/internal/async/internal_async_transaction.rb +0 -13
- data/ruby/neo4j/driver/internal/async/leak_logging_network_session.rb +0 -34
- data/ruby/neo4j/driver/internal/async/network_connection.rb +0 -194
- data/ruby/neo4j/driver/internal/async/network_session.rb +0 -150
- data/ruby/neo4j/driver/internal/async/outbound/chunk_aware_byte_buf_output.rb +0 -110
- data/ruby/neo4j/driver/internal/async/outbound/outbound_message_handler.rb +0 -39
- data/ruby/neo4j/driver/internal/async/pool/channel.rb +0 -66
- data/ruby/neo4j/driver/internal/async/pool/channel_pool.rb +0 -31
- data/ruby/neo4j/driver/internal/async/pool/channel_tracker.rb +0 -135
- data/ruby/neo4j/driver/internal/async/pool/connection_pool_impl.rb +0 -156
- data/ruby/neo4j/driver/internal/async/pool/netty_channel_health_checker.rb +0 -87
- data/ruby/neo4j/driver/internal/async/pool/netty_channel_pool.rb +0 -52
- data/ruby/neo4j/driver/internal/async/pool/network_connection_factory.rb +0 -21
- data/ruby/neo4j/driver/internal/async/pool/pool_settings.rb +0 -34
- data/ruby/neo4j/driver/internal/async/pool/timed_stack.rb +0 -15
- data/ruby/neo4j/driver/internal/async/result_cursors_holder.rb +0 -17
- data/ruby/neo4j/driver/internal/async/unmanaged_transaction.rb +0 -212
- data/ruby/neo4j/driver/internal/bookmark_holder.rb +0 -9
- data/ruby/neo4j/driver/internal/cluster/cluster_composition.rb +0 -48
- data/ruby/neo4j/driver/internal/cluster/cluster_composition_lookup_result.rb +0 -14
- data/ruby/neo4j/driver/internal/cluster/cluster_routing_table.rb +0 -122
- data/ruby/neo4j/driver/internal/cluster/identity_resolver.rb +0 -10
- data/ruby/neo4j/driver/internal/cluster/loadbalancing/least_connected_load_balancing_strategy.rb +0 -68
- data/ruby/neo4j/driver/internal/cluster/loadbalancing/load_balancer.rb +0 -125
- data/ruby/neo4j/driver/internal/cluster/loadbalancing/round_robin_array_index.rb +0 -13
- data/ruby/neo4j/driver/internal/cluster/multi_databases_routing_procedure_runner.rb +0 -31
- data/ruby/neo4j/driver/internal/cluster/rediscovery_impl.rb +0 -147
- data/ruby/neo4j/driver/internal/cluster/route_message_routing_procedure_runner.rb +0 -43
- data/ruby/neo4j/driver/internal/cluster/routing_context.rb +0 -77
- data/ruby/neo4j/driver/internal/cluster/routing_procedure_cluster_composition_provider.rb +0 -60
- data/ruby/neo4j/driver/internal/cluster/routing_procedure_response.rb +0 -35
- data/ruby/neo4j/driver/internal/cluster/routing_settings.rb +0 -24
- data/ruby/neo4j/driver/internal/cluster/routing_table_handler_impl.rb +0 -95
- data/ruby/neo4j/driver/internal/cluster/routing_table_registry_impl.rb +0 -121
- data/ruby/neo4j/driver/internal/cluster/single_database_routing_procedure_runner.rb +0 -73
- data/ruby/neo4j/driver/internal/connection_settings.rb +0 -16
- data/ruby/neo4j/driver/internal/cursor/async_result_cursor_impl.rb +0 -55
- data/ruby/neo4j/driver/internal/cursor/async_result_cursor_only_factory.rb +0 -24
- data/ruby/neo4j/driver/internal/cursor/disposable_async_result_cursor.rb +0 -61
- data/ruby/neo4j/driver/internal/cursor/result_cursor_factory_impl.rb +0 -24
- data/ruby/neo4j/driver/internal/cursor/rx_result_cursor_impl.rb +0 -110
- data/ruby/neo4j/driver/internal/database_name_util.rb +0 -37
- data/ruby/neo4j/driver/internal/default_bookmark_holder.rb +0 -9
- data/ruby/neo4j/driver/internal/default_domain_name_resolver.rb +0 -11
- data/ruby/neo4j/driver/internal/direct_connection_provider.rb +0 -40
- data/ruby/neo4j/driver/internal/driver_factory.rb +0 -126
- data/ruby/neo4j/driver/internal/eager_result_value.rb +0 -5
- data/ruby/neo4j/driver/internal/handlers/begin_tx_response_handler.rb +0 -20
- data/ruby/neo4j/driver/internal/handlers/channel_releasing_reset_response_handler.rb +0 -30
- data/ruby/neo4j/driver/internal/handlers/commit_tx_response_handler.rb +0 -23
- data/ruby/neo4j/driver/internal/handlers/hello_response_handler.rb +0 -65
- data/ruby/neo4j/driver/internal/handlers/init_response_handler.rb +0 -34
- data/ruby/neo4j/driver/internal/handlers/legacy_pull_all_response_handler.rb +0 -199
- data/ruby/neo4j/driver/internal/handlers/no_op_response_handler.rb +0 -16
- data/ruby/neo4j/driver/internal/handlers/ping_response_handler.rb +0 -29
- data/ruby/neo4j/driver/internal/handlers/pull_handlers.rb +0 -32
- data/ruby/neo4j/driver/internal/handlers/pulln/auto_pull_response_handler.rb +0 -169
- data/ruby/neo4j/driver/internal/handlers/pulln/basic_pull_response_handler.rb +0 -298
- data/ruby/neo4j/driver/internal/handlers/pulln/fetch_size_util.rb +0 -20
- data/ruby/neo4j/driver/internal/handlers/reset_response_handler.rb +0 -34
- data/ruby/neo4j/driver/internal/handlers/rollback_tx_response_handler.rb +0 -25
- data/ruby/neo4j/driver/internal/handlers/route_message_response_handler.rb +0 -21
- data/ruby/neo4j/driver/internal/handlers/routing_response_handler.rb +0 -79
- data/ruby/neo4j/driver/internal/handlers/run_response_handler.rb +0 -38
- data/ruby/neo4j/driver/internal/handlers/session_pull_response_completion_listener.rb +0 -34
- data/ruby/neo4j/driver/internal/handlers/transaction_pull_response_completion_listener.rb +0 -20
- data/ruby/neo4j/driver/internal/impersonation_util.rb +0 -22
- data/ruby/neo4j/driver/internal/internal_bookmark.rb +0 -9
- data/ruby/neo4j/driver/internal/internal_database_name.rb +0 -9
- data/ruby/neo4j/driver/internal/internal_driver.rb +0 -83
- data/ruby/neo4j/driver/internal/internal_entity.rb +0 -21
- data/ruby/neo4j/driver/internal/internal_node.rb +0 -21
- data/ruby/neo4j/driver/internal/internal_pair.rb +0 -9
- data/ruby/neo4j/driver/internal/internal_path.rb +0 -35
- data/ruby/neo4j/driver/internal/internal_point2_d.rb +0 -9
- data/ruby/neo4j/driver/internal/internal_point3_d.rb +0 -6
- data/ruby/neo4j/driver/internal/internal_record.rb +0 -27
- data/ruby/neo4j/driver/internal/internal_relationship.rb +0 -27
- data/ruby/neo4j/driver/internal/internal_result.rb +0 -49
- data/ruby/neo4j/driver/internal/internal_session.rb +0 -91
- data/ruby/neo4j/driver/internal/internal_transaction.rb +0 -48
- data/ruby/neo4j/driver/internal/logging/channel_activity_logger.rb +0 -29
- data/ruby/neo4j/driver/internal/logging/channel_error_logger.rb +0 -17
- data/ruby/neo4j/driver/internal/logging/prefixed_logger.rb +0 -19
- data/ruby/neo4j/driver/internal/logging/reformatted_logger.rb +0 -17
- data/ruby/neo4j/driver/internal/messaging/abstract_message_writer.rb +0 -23
- data/ruby/neo4j/driver/internal/messaging/bolt_protocol.rb +0 -32
- data/ruby/neo4j/driver/internal/messaging/bolt_protocol_version.rb +0 -48
- data/ruby/neo4j/driver/internal/messaging/common/common_message_reader.rb +0 -51
- data/ruby/neo4j/driver/internal/messaging/common/common_value.rb +0 -33
- data/ruby/neo4j/driver/internal/messaging/common/common_value_packer.rb +0 -104
- data/ruby/neo4j/driver/internal/messaging/common/common_value_unpacker.rb +0 -256
- data/ruby/neo4j/driver/internal/messaging/encode/begin_message_encoder.rb +0 -15
- data/ruby/neo4j/driver/internal/messaging/encode/commit_message_encoder.rb +0 -14
- data/ruby/neo4j/driver/internal/messaging/encode/discard_all_message_encoder.rb +0 -14
- data/ruby/neo4j/driver/internal/messaging/encode/discard_message_encoder.rb +0 -15
- data/ruby/neo4j/driver/internal/messaging/encode/goodbye_message_encoder.rb +0 -14
- data/ruby/neo4j/driver/internal/messaging/encode/hello_message_encoder.rb +0 -15
- data/ruby/neo4j/driver/internal/messaging/encode/init_message_encoder.rb +0 -16
- data/ruby/neo4j/driver/internal/messaging/encode/logoff_message_encoder.rb +0 -14
- data/ruby/neo4j/driver/internal/messaging/encode/logon_message_encoder.rb +0 -15
- data/ruby/neo4j/driver/internal/messaging/encode/pull_all_message_encoder.rb +0 -14
- data/ruby/neo4j/driver/internal/messaging/encode/pull_message_encoder.rb +0 -15
- data/ruby/neo4j/driver/internal/messaging/encode/reset_message_encoder.rb +0 -14
- data/ruby/neo4j/driver/internal/messaging/encode/rollback_message_encoder.rb +0 -14
- data/ruby/neo4j/driver/internal/messaging/encode/route_message_encoder.rb +0 -24
- data/ruby/neo4j/driver/internal/messaging/encode/route_v44_message_encoder.rb +0 -22
- data/ruby/neo4j/driver/internal/messaging/encode/run_message_encoder.rb +0 -16
- data/ruby/neo4j/driver/internal/messaging/encode/run_with_metadata_message_encoder.rb +0 -17
- data/ruby/neo4j/driver/internal/messaging/request/abstract_streaming_message.rb +0 -25
- data/ruby/neo4j/driver/internal/messaging/request/begin_message.rb +0 -25
- data/ruby/neo4j/driver/internal/messaging/request/commit_message.rb +0 -20
- data/ruby/neo4j/driver/internal/messaging/request/discard_all_message.rb +0 -20
- data/ruby/neo4j/driver/internal/messaging/request/discard_message.rb +0 -23
- data/ruby/neo4j/driver/internal/messaging/request/goodbye_message.rb +0 -20
- data/ruby/neo4j/driver/internal/messaging/request/hello_message.rb +0 -30
- data/ruby/neo4j/driver/internal/messaging/request/init_message.rb +0 -19
- data/ruby/neo4j/driver/internal/messaging/request/logoff_message.rb +0 -13
- data/ruby/neo4j/driver/internal/messaging/request/logon_message.rb +0 -13
- data/ruby/neo4j/driver/internal/messaging/request/message_with_metadata.rb +0 -17
- data/ruby/neo4j/driver/internal/messaging/request/multi_database_util.rb +0 -26
- data/ruby/neo4j/driver/internal/messaging/request/pull_all_message.rb +0 -23
- data/ruby/neo4j/driver/internal/messaging/request/pull_message.rb +0 -22
- data/ruby/neo4j/driver/internal/messaging/request/reset_message.rb +0 -32
- data/ruby/neo4j/driver/internal/messaging/request/rollback_message.rb +0 -20
- data/ruby/neo4j/driver/internal/messaging/request/route_message.rb +0 -28
- data/ruby/neo4j/driver/internal/messaging/request/run_message.rb +0 -23
- data/ruby/neo4j/driver/internal/messaging/request/run_with_metadata_message.rb +0 -49
- data/ruby/neo4j/driver/internal/messaging/request/transaction_metadata_builder.rb +0 -24
- data/ruby/neo4j/driver/internal/messaging/response/failure_message.rb +0 -40
- data/ruby/neo4j/driver/internal/messaging/response/ignored_message.rb +0 -29
- data/ruby/neo4j/driver/internal/messaging/response/record_message.rb +0 -33
- data/ruby/neo4j/driver/internal/messaging/response/success_message.rb +0 -34
- data/ruby/neo4j/driver/internal/messaging/v3/bolt_protocol_v3.rb +0 -82
- data/ruby/neo4j/driver/internal/messaging/v3/message_format_v3.rb +0 -17
- data/ruby/neo4j/driver/internal/messaging/v3/message_writer_v3.rb +0 -27
- data/ruby/neo4j/driver/internal/messaging/v4/bolt_protocol_v4.rb +0 -29
- data/ruby/neo4j/driver/internal/messaging/v4/message_format_v4.rb +0 -13
- data/ruby/neo4j/driver/internal/messaging/v4/message_writer_v4.rb +0 -17
- data/ruby/neo4j/driver/internal/messaging/v41/bolt_protocol_v41.rb +0 -25
- data/ruby/neo4j/driver/internal/messaging/v42/bolt_protocol_v42.rb +0 -13
- data/ruby/neo4j/driver/internal/messaging/v43/bolt_protocol_v43.rb +0 -19
- data/ruby/neo4j/driver/internal/messaging/v43/message_format_v43.rb +0 -14
- data/ruby/neo4j/driver/internal/messaging/v43/message_writer_v43.rb +0 -20
- data/ruby/neo4j/driver/internal/messaging/v44/bolt_protocol_v44.rb +0 -17
- data/ruby/neo4j/driver/internal/messaging/v44/message_format_v44.rb +0 -14
- data/ruby/neo4j/driver/internal/messaging/v44/message_writer_v44.rb +0 -15
- data/ruby/neo4j/driver/internal/messaging/v5/bolt_protocol_v5.rb +0 -21
- data/ruby/neo4j/driver/internal/messaging/v5/message_format_v5.rb +0 -18
- data/ruby/neo4j/driver/internal/messaging/v5/value_unpacker_v5.rb +0 -17
- data/ruby/neo4j/driver/internal/messaging/v51/bolt_protocol_v51.rb +0 -40
- data/ruby/neo4j/driver/internal/messaging/v51/message_format_v51.rb +0 -14
- data/ruby/neo4j/driver/internal/messaging/v51/message_writer_v51.rb +0 -17
- data/ruby/neo4j/driver/internal/metrics/connection_pool_metrics_listener.rb +0 -34
- data/ruby/neo4j/driver/internal/metrics/internal_abstract_metrics.rb +0 -46
- data/ruby/neo4j/driver/internal/metrics/internal_connection_pool_metrics.rb +0 -105
- data/ruby/neo4j/driver/internal/metrics/internal_metrics.rb +0 -82
- data/ruby/neo4j/driver/internal/metrics/internal_metrics_provider.rb +0 -18
- data/ruby/neo4j/driver/internal/metrics/listener_event.rb +0 -17
- data/ruby/neo4j/driver/internal/metrics/metrics_provider.rb +0 -24
- data/ruby/neo4j/driver/internal/metrics/time_recorder_listener_event.rb +0 -15
- data/ruby/neo4j/driver/internal/packstream/byte_array_incompatible_packer.rb +0 -12
- data/ruby/neo4j/driver/internal/packstream/pack_input.rb +0 -47
- data/ruby/neo4j/driver/internal/packstream/pack_output.rb +0 -39
- data/ruby/neo4j/driver/internal/packstream/pack_stream.rb +0 -324
- data/ruby/neo4j/driver/internal/packstream/pack_type.rb +0 -17
- data/ruby/neo4j/driver/internal/read_only_bookmark_holder.rb +0 -13
- data/ruby/neo4j/driver/internal/resolved_bolt_server_address.rb +0 -35
- data/ruby/neo4j/driver/internal/retry/exponential_backoff_retry_logic.rb +0 -150
- data/ruby/neo4j/driver/internal/revocation_strategy.rb +0 -19
- data/ruby/neo4j/driver/internal/scheme.rb +0 -32
- data/ruby/neo4j/driver/internal/security/internal_auth_token.rb +0 -15
- data/ruby/neo4j/driver/internal/security/security_plan_impl.rb +0 -48
- data/ruby/neo4j/driver/internal/security_setting.rb +0 -66
- data/ruby/neo4j/driver/internal/session_factory_impl.rb +0 -32
- data/ruby/neo4j/driver/internal/spi/connection.rb +0 -19
- data/ruby/neo4j/driver/internal/spi/connection_pool.rb +0 -9
- data/ruby/neo4j/driver/internal/spi/response_handler.rb +0 -23
- data/ruby/neo4j/driver/internal/summary/internal_database_info.rb +0 -7
- data/ruby/neo4j/driver/internal/summary/internal_input_position.rb +0 -11
- data/ruby/neo4j/driver/internal/summary/internal_notification.rb +0 -16
- data/ruby/neo4j/driver/internal/summary/internal_plan.rb +0 -41
- data/ruby/neo4j/driver/internal/summary/internal_profiled_plan.rb +0 -32
- data/ruby/neo4j/driver/internal/summary/internal_result_summary.rb +0 -33
- data/ruby/neo4j/driver/internal/summary/internal_server_info.rb +0 -6
- data/ruby/neo4j/driver/internal/summary/internal_summary_counters.rb +0 -18
- data/ruby/neo4j/driver/internal/svm/netty_substitutions.rb +0 -196
- data/ruby/neo4j/driver/internal/svm/z_lib_substitutions.rb +0 -21
- data/ruby/neo4j/driver/internal/util/certificate_tool.rb +0 -65
- data/ruby/neo4j/driver/internal/util/clock.rb +0 -29
- data/ruby/neo4j/driver/internal/util/error_util.rb +0 -104
- data/ruby/neo4j/driver/internal/util/extract.rb +0 -123
- data/ruby/neo4j/driver/internal/util/format.rb +0 -39
- data/ruby/neo4j/driver/internal/util/futures.rb +0 -99
- data/ruby/neo4j/driver/internal/util/iterables.rb +0 -35
- data/ruby/neo4j/driver/internal/util/lock_util.rb +0 -23
- data/ruby/neo4j/driver/internal/util/metadata_extractor.rb +0 -101
- data/ruby/neo4j/driver/internal/util/mutex.rb +0 -9
- data/ruby/neo4j/driver/internal/util/preconditions.rb +0 -16
- data/ruby/neo4j/driver/internal/util/result_holder.rb +0 -72
- data/ruby/neo4j/driver/internal/util/server_version.rb +0 -60
- data/ruby/neo4j/driver/logging1.rb +0 -51
- data/ruby/neo4j/driver/net/server_address.rb +0 -9
- data/ruby/neo4j/driver/query.rb +0 -48
- data/ruby/neo4j/driver/records.rb +0 -13
- data/ruby/neo4j/driver/transaction_config.rb +0 -50
- data/ruby/neo4j/driver/values.rb +0 -26
- 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
|