neo4j-ruby-driver 6.0.3.alpha.0 → 6.2.1.beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (346) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +113 -411
  3. data/{ruby → lib}/neo4j/driver/access_mode.rb +2 -1
  4. data/lib/neo4j/driver/auth_token_managers.rb +31 -0
  5. data/lib/neo4j/driver/auth_tokens.rb +41 -0
  6. data/lib/neo4j/driver/auto_commit_retries_mode.rb +18 -0
  7. data/lib/neo4j/driver/bolt/bolt_version.rb +67 -0
  8. data/lib/neo4j/driver/bolt/connection.rb +1054 -0
  9. data/lib/neo4j/driver/bolt/handshake.rb +232 -0
  10. data/lib/neo4j/driver/bolt/message/failure.rb +139 -0
  11. data/lib/neo4j/driver/bolt/message/ignored.rb +27 -0
  12. data/lib/neo4j/driver/bolt/message/record.rb +30 -0
  13. data/lib/neo4j/driver/bolt/message/success.rb +34 -0
  14. data/lib/neo4j/driver/bolt/message.rb +112 -0
  15. data/lib/neo4j/driver/bolt/pool.rb +255 -0
  16. data/lib/neo4j/driver/bolt/protocol/base.rb +146 -0
  17. data/lib/neo4j/driver/bolt/protocol/v3.rb +43 -0
  18. data/lib/neo4j/driver/bolt/protocol/v4.rb +24 -0
  19. data/lib/neo4j/driver/bolt/protocol/v43.rb +28 -0
  20. data/lib/neo4j/driver/bolt/protocol/v44.rb +23 -0
  21. data/lib/neo4j/driver/bolt/protocol/v5.rb +27 -0
  22. data/lib/neo4j/driver/bolt/protocol/v51.rb +24 -0
  23. data/lib/neo4j/driver/bolt/protocol/v52.rb +28 -0
  24. data/lib/neo4j/driver/bolt/protocol/v53.rb +27 -0
  25. data/lib/neo4j/driver/bolt/protocol/v54.rb +14 -0
  26. data/lib/neo4j/driver/bolt/protocol/v55.rb +17 -0
  27. data/lib/neo4j/driver/bolt/protocol/v56.rb +14 -0
  28. data/lib/neo4j/driver/bolt/protocol/v57.rb +31 -0
  29. data/lib/neo4j/driver/bolt/protocol/v58.rb +15 -0
  30. data/lib/neo4j/driver/bolt/protocol/v6.rb +43 -0
  31. data/lib/neo4j/driver/bolt/protocol/v61.rb +21 -0
  32. data/lib/neo4j/driver/bolt/protocol_version_handler.rb +39 -0
  33. data/lib/neo4j/driver/bolt/record_buffer.rb +136 -0
  34. data/lib/neo4j/driver/bolt/stream_handler.rb +53 -0
  35. data/lib/neo4j/driver/bolt/tls_config.rb +129 -0
  36. data/lib/neo4j/driver/bolt/wire.rb +321 -0
  37. data/lib/neo4j/driver/bookmark.rb +15 -0
  38. data/lib/neo4j/driver/bookmark_managers.rb +39 -0
  39. data/lib/neo4j/driver/client_certificate.rb +19 -0
  40. data/lib/neo4j/driver/client_certificate_manager.rb +37 -0
  41. data/lib/neo4j/driver/client_certificate_managers.rb +15 -0
  42. data/lib/neo4j/driver/client_certificates.rb +14 -0
  43. data/lib/neo4j/driver/direct/connection_provider.rb +258 -0
  44. data/lib/neo4j/driver/driver.rb +268 -0
  45. data/lib/neo4j/driver/eager_result.rb +10 -0
  46. data/lib/neo4j/driver/exceptions/neo4j_exception.rb +16 -7
  47. data/lib/neo4j/driver/exceptions/protocol_exception.rb +0 -5
  48. data/lib/neo4j/driver/exceptions/security_retryable_exception.rb +14 -0
  49. data/lib/neo4j/driver/exceptions/transaction_terminated_exception.rb +13 -0
  50. data/lib/neo4j/driver/graph_database.rb +29 -0
  51. data/lib/neo4j/driver/internal/clock.rb +20 -0
  52. data/lib/neo4j/driver/internal/clock_adapter.rb +21 -0
  53. data/lib/neo4j/driver/internal/default_bookmark_manager.rb +54 -0
  54. data/lib/neo4j/driver/internal/deprecator.rb +9 -5
  55. data/lib/neo4j/driver/internal/driver_factory.rb +128 -0
  56. data/lib/neo4j/driver/internal/duration_normalizer.rb +14 -41
  57. data/lib/neo4j/driver/internal/extras.rb +23 -0
  58. data/lib/neo4j/driver/internal/home_db_cache.rb +57 -0
  59. data/lib/neo4j/driver/internal/internal_auth_token_manager.rb +37 -0
  60. data/lib/neo4j/driver/internal/metrics.rb +31 -0
  61. data/lib/neo4j/driver/internal/security/static_auth_token_manager.rb +25 -0
  62. data/lib/neo4j/driver/internal/validator.rb +10 -0
  63. data/lib/neo4j/driver/net.rb +29 -0
  64. data/lib/neo4j/driver/packstream/markers.rb +49 -0
  65. data/lib/neo4j/driver/packstream/packer.rb +333 -0
  66. data/lib/neo4j/driver/packstream/structure.rb +27 -0
  67. data/lib/neo4j/driver/packstream/unpacker.rb +168 -0
  68. data/lib/neo4j/driver/record.rb +49 -0
  69. data/lib/neo4j/driver/result.rb +282 -0
  70. data/lib/neo4j/driver/routing/load_balancer.rb +690 -0
  71. data/lib/neo4j/driver/routing/routed_connection.rb +132 -0
  72. data/lib/neo4j/driver/routing/routing_table.rb +127 -0
  73. data/lib/neo4j/driver/routing/server_address.rb +25 -0
  74. data/lib/neo4j/driver/routing_control.rb +13 -0
  75. data/lib/neo4j/driver/session.rb +575 -0
  76. data/lib/neo4j/driver/summary/database_info.rb +25 -0
  77. data/lib/neo4j/driver/summary/gql_notification.rb +38 -0
  78. data/lib/neo4j/driver/summary/gql_status_object.rb +35 -0
  79. data/lib/neo4j/driver/summary/notification.rb +46 -0
  80. data/lib/neo4j/driver/summary/plan.rb +23 -0
  81. data/lib/neo4j/driver/summary/profile.rb +28 -0
  82. data/lib/neo4j/driver/summary/query.rb +19 -0
  83. data/lib/neo4j/driver/summary/query_type.rb +5 -1
  84. data/lib/neo4j/driver/summary/result_summary.rb +213 -0
  85. data/lib/neo4j/driver/summary/server_info.rb +33 -0
  86. data/lib/neo4j/driver/summary/summary_counters.rb +72 -0
  87. data/lib/neo4j/driver/transaction.rb +341 -0
  88. data/lib/neo4j/driver/types/duration.rb +76 -0
  89. data/lib/neo4j/driver/types/local_date_time.rb +52 -8
  90. data/lib/neo4j/driver/types/local_time.rb +42 -7
  91. data/lib/neo4j/driver/types/node.rb +27 -0
  92. data/lib/neo4j/driver/types/offset_time.rb +71 -7
  93. data/lib/neo4j/driver/types/path.rb +68 -0
  94. data/lib/neo4j/driver/types/point.rb +38 -18
  95. data/lib/neo4j/driver/types/relationship.rb +41 -0
  96. data/lib/neo4j/driver/types/temporal_value.rb +43 -0
  97. data/lib/neo4j/driver/types/unbound_relationship.rb +29 -0
  98. data/lib/neo4j/driver/types/unresolvable_zoned_date_time.rb +38 -0
  99. data/lib/neo4j/driver/types/unsupported_type.rb +25 -0
  100. data/lib/neo4j/driver/types/uuid.rb +29 -0
  101. data/{ruby → lib}/neo4j/driver/version.rb +1 -1
  102. data/lib/neo4j/driver.rb +16 -0
  103. data/lib/neo4j-ruby-driver_loader.rb +34 -20
  104. metadata +132 -390
  105. data/lib/neo4j/driver/internal/bolt_server_address.rb +0 -97
  106. data/lib/neo4j/driver/synchronizable.rb +0 -23
  107. data/lib/neo4j/driver/types/time.rb +0 -45
  108. data/ruby/neo4j/driver/auth_tokens.rb +0 -34
  109. data/ruby/neo4j/driver/bookmark.rb +0 -21
  110. data/ruby/neo4j/driver/config.rb +0 -89
  111. data/ruby/neo4j/driver/graph_database.rb +0 -78
  112. data/ruby/neo4j/driver/internal/async/connection/bolt_protocol_util.rb +0 -51
  113. data/ruby/neo4j/driver/internal/async/connection/bootstrap_factory.rb +0 -22
  114. data/ruby/neo4j/driver/internal/async/connection/channel_attributes.rb +0 -31
  115. data/ruby/neo4j/driver/internal/async/connection/channel_connected_listener.rb +0 -32
  116. data/ruby/neo4j/driver/internal/async/connection/channel_connector_impl.rb +0 -83
  117. data/ruby/neo4j/driver/internal/async/connection/channel_pipeline_builder_impl.rb +0 -22
  118. data/ruby/neo4j/driver/internal/async/connection/direct_connection.rb +0 -30
  119. data/ruby/neo4j/driver/internal/async/connection/event_loop_group_factory.rb +0 -83
  120. data/ruby/neo4j/driver/internal/async/connection/handshake_completed_listener.rb +0 -27
  121. data/ruby/neo4j/driver/internal/async/connection/handshake_handler.rb +0 -113
  122. data/ruby/neo4j/driver/internal/async/connection/netty_channel_initializer.rb +0 -57
  123. data/ruby/neo4j/driver/internal/async/connection/netty_domain_name_resolver.rb +0 -26
  124. data/ruby/neo4j/driver/internal/async/connection/netty_domain_name_resolver_group.rb +0 -19
  125. data/ruby/neo4j/driver/internal/async/connection/routing_connection.rb +0 -36
  126. data/ruby/neo4j/driver/internal/async/connection/stream.rb +0 -12
  127. data/ruby/neo4j/driver/internal/async/connection/stream_reader.rb +0 -16
  128. data/ruby/neo4j/driver/internal/async/connection_context.rb +0 -10
  129. data/ruby/neo4j/driver/internal/async/immutable_connection_context.rb +0 -24
  130. data/ruby/neo4j/driver/internal/async/inbound/byte_buf_input.rb +0 -30
  131. data/ruby/neo4j/driver/internal/async/inbound/channel_error_handler.rb +0 -77
  132. data/ruby/neo4j/driver/internal/async/inbound/chunk_decoder.rb +0 -41
  133. data/ruby/neo4j/driver/internal/async/inbound/connect_timeout_handler.rb +0 -32
  134. data/ruby/neo4j/driver/internal/async/inbound/connection_read_timeout_handler.rb +0 -17
  135. data/ruby/neo4j/driver/internal/async/inbound/inbound_message_dispatcher.rb +0 -171
  136. data/ruby/neo4j/driver/internal/async/inbound/inbound_message_handler.rb +0 -42
  137. data/ruby/neo4j/driver/internal/async/inbound/message_decoder.rb +0 -51
  138. data/ruby/neo4j/driver/internal/async/internal_async_session.rb +0 -92
  139. data/ruby/neo4j/driver/internal/async/internal_async_transaction.rb +0 -13
  140. data/ruby/neo4j/driver/internal/async/leak_logging_network_session.rb +0 -34
  141. data/ruby/neo4j/driver/internal/async/network_connection.rb +0 -194
  142. data/ruby/neo4j/driver/internal/async/network_session.rb +0 -150
  143. data/ruby/neo4j/driver/internal/async/outbound/chunk_aware_byte_buf_output.rb +0 -110
  144. data/ruby/neo4j/driver/internal/async/outbound/outbound_message_handler.rb +0 -39
  145. data/ruby/neo4j/driver/internal/async/pool/channel.rb +0 -66
  146. data/ruby/neo4j/driver/internal/async/pool/channel_pool.rb +0 -31
  147. data/ruby/neo4j/driver/internal/async/pool/channel_tracker.rb +0 -135
  148. data/ruby/neo4j/driver/internal/async/pool/connection_pool_impl.rb +0 -156
  149. data/ruby/neo4j/driver/internal/async/pool/netty_channel_health_checker.rb +0 -87
  150. data/ruby/neo4j/driver/internal/async/pool/netty_channel_pool.rb +0 -52
  151. data/ruby/neo4j/driver/internal/async/pool/network_connection_factory.rb +0 -21
  152. data/ruby/neo4j/driver/internal/async/pool/pool_settings.rb +0 -34
  153. data/ruby/neo4j/driver/internal/async/pool/timed_stack.rb +0 -15
  154. data/ruby/neo4j/driver/internal/async/result_cursors_holder.rb +0 -17
  155. data/ruby/neo4j/driver/internal/async/unmanaged_transaction.rb +0 -212
  156. data/ruby/neo4j/driver/internal/bookmark_holder.rb +0 -9
  157. data/ruby/neo4j/driver/internal/cluster/cluster_composition.rb +0 -48
  158. data/ruby/neo4j/driver/internal/cluster/cluster_composition_lookup_result.rb +0 -14
  159. data/ruby/neo4j/driver/internal/cluster/cluster_routing_table.rb +0 -122
  160. data/ruby/neo4j/driver/internal/cluster/identity_resolver.rb +0 -10
  161. data/ruby/neo4j/driver/internal/cluster/loadbalancing/least_connected_load_balancing_strategy.rb +0 -68
  162. data/ruby/neo4j/driver/internal/cluster/loadbalancing/load_balancer.rb +0 -125
  163. data/ruby/neo4j/driver/internal/cluster/loadbalancing/round_robin_array_index.rb +0 -13
  164. data/ruby/neo4j/driver/internal/cluster/multi_databases_routing_procedure_runner.rb +0 -31
  165. data/ruby/neo4j/driver/internal/cluster/rediscovery_impl.rb +0 -147
  166. data/ruby/neo4j/driver/internal/cluster/route_message_routing_procedure_runner.rb +0 -43
  167. data/ruby/neo4j/driver/internal/cluster/routing_context.rb +0 -77
  168. data/ruby/neo4j/driver/internal/cluster/routing_procedure_cluster_composition_provider.rb +0 -60
  169. data/ruby/neo4j/driver/internal/cluster/routing_procedure_response.rb +0 -35
  170. data/ruby/neo4j/driver/internal/cluster/routing_settings.rb +0 -24
  171. data/ruby/neo4j/driver/internal/cluster/routing_table_handler_impl.rb +0 -95
  172. data/ruby/neo4j/driver/internal/cluster/routing_table_registry_impl.rb +0 -121
  173. data/ruby/neo4j/driver/internal/cluster/single_database_routing_procedure_runner.rb +0 -73
  174. data/ruby/neo4j/driver/internal/connection_settings.rb +0 -16
  175. data/ruby/neo4j/driver/internal/cursor/async_result_cursor_impl.rb +0 -55
  176. data/ruby/neo4j/driver/internal/cursor/async_result_cursor_only_factory.rb +0 -24
  177. data/ruby/neo4j/driver/internal/cursor/disposable_async_result_cursor.rb +0 -61
  178. data/ruby/neo4j/driver/internal/cursor/result_cursor_factory_impl.rb +0 -24
  179. data/ruby/neo4j/driver/internal/cursor/rx_result_cursor_impl.rb +0 -110
  180. data/ruby/neo4j/driver/internal/database_name_util.rb +0 -37
  181. data/ruby/neo4j/driver/internal/default_bookmark_holder.rb +0 -9
  182. data/ruby/neo4j/driver/internal/default_domain_name_resolver.rb +0 -11
  183. data/ruby/neo4j/driver/internal/direct_connection_provider.rb +0 -40
  184. data/ruby/neo4j/driver/internal/driver_factory.rb +0 -126
  185. data/ruby/neo4j/driver/internal/eager_result_value.rb +0 -5
  186. data/ruby/neo4j/driver/internal/handlers/begin_tx_response_handler.rb +0 -20
  187. data/ruby/neo4j/driver/internal/handlers/channel_releasing_reset_response_handler.rb +0 -30
  188. data/ruby/neo4j/driver/internal/handlers/commit_tx_response_handler.rb +0 -23
  189. data/ruby/neo4j/driver/internal/handlers/hello_response_handler.rb +0 -65
  190. data/ruby/neo4j/driver/internal/handlers/init_response_handler.rb +0 -34
  191. data/ruby/neo4j/driver/internal/handlers/legacy_pull_all_response_handler.rb +0 -199
  192. data/ruby/neo4j/driver/internal/handlers/no_op_response_handler.rb +0 -16
  193. data/ruby/neo4j/driver/internal/handlers/ping_response_handler.rb +0 -29
  194. data/ruby/neo4j/driver/internal/handlers/pull_handlers.rb +0 -32
  195. data/ruby/neo4j/driver/internal/handlers/pulln/auto_pull_response_handler.rb +0 -169
  196. data/ruby/neo4j/driver/internal/handlers/pulln/basic_pull_response_handler.rb +0 -298
  197. data/ruby/neo4j/driver/internal/handlers/pulln/fetch_size_util.rb +0 -20
  198. data/ruby/neo4j/driver/internal/handlers/reset_response_handler.rb +0 -34
  199. data/ruby/neo4j/driver/internal/handlers/rollback_tx_response_handler.rb +0 -25
  200. data/ruby/neo4j/driver/internal/handlers/route_message_response_handler.rb +0 -21
  201. data/ruby/neo4j/driver/internal/handlers/routing_response_handler.rb +0 -79
  202. data/ruby/neo4j/driver/internal/handlers/run_response_handler.rb +0 -38
  203. data/ruby/neo4j/driver/internal/handlers/session_pull_response_completion_listener.rb +0 -34
  204. data/ruby/neo4j/driver/internal/handlers/transaction_pull_response_completion_listener.rb +0 -20
  205. data/ruby/neo4j/driver/internal/impersonation_util.rb +0 -22
  206. data/ruby/neo4j/driver/internal/internal_bookmark.rb +0 -9
  207. data/ruby/neo4j/driver/internal/internal_database_name.rb +0 -9
  208. data/ruby/neo4j/driver/internal/internal_driver.rb +0 -83
  209. data/ruby/neo4j/driver/internal/internal_entity.rb +0 -21
  210. data/ruby/neo4j/driver/internal/internal_node.rb +0 -21
  211. data/ruby/neo4j/driver/internal/internal_pair.rb +0 -9
  212. data/ruby/neo4j/driver/internal/internal_path.rb +0 -35
  213. data/ruby/neo4j/driver/internal/internal_point2_d.rb +0 -9
  214. data/ruby/neo4j/driver/internal/internal_point3_d.rb +0 -6
  215. data/ruby/neo4j/driver/internal/internal_record.rb +0 -27
  216. data/ruby/neo4j/driver/internal/internal_relationship.rb +0 -27
  217. data/ruby/neo4j/driver/internal/internal_result.rb +0 -49
  218. data/ruby/neo4j/driver/internal/internal_session.rb +0 -91
  219. data/ruby/neo4j/driver/internal/internal_transaction.rb +0 -48
  220. data/ruby/neo4j/driver/internal/logging/channel_activity_logger.rb +0 -29
  221. data/ruby/neo4j/driver/internal/logging/channel_error_logger.rb +0 -17
  222. data/ruby/neo4j/driver/internal/logging/prefixed_logger.rb +0 -19
  223. data/ruby/neo4j/driver/internal/logging/reformatted_logger.rb +0 -17
  224. data/ruby/neo4j/driver/internal/messaging/abstract_message_writer.rb +0 -23
  225. data/ruby/neo4j/driver/internal/messaging/bolt_protocol.rb +0 -32
  226. data/ruby/neo4j/driver/internal/messaging/bolt_protocol_version.rb +0 -48
  227. data/ruby/neo4j/driver/internal/messaging/common/common_message_reader.rb +0 -51
  228. data/ruby/neo4j/driver/internal/messaging/common/common_value.rb +0 -33
  229. data/ruby/neo4j/driver/internal/messaging/common/common_value_packer.rb +0 -104
  230. data/ruby/neo4j/driver/internal/messaging/common/common_value_unpacker.rb +0 -256
  231. data/ruby/neo4j/driver/internal/messaging/encode/begin_message_encoder.rb +0 -15
  232. data/ruby/neo4j/driver/internal/messaging/encode/commit_message_encoder.rb +0 -14
  233. data/ruby/neo4j/driver/internal/messaging/encode/discard_all_message_encoder.rb +0 -14
  234. data/ruby/neo4j/driver/internal/messaging/encode/discard_message_encoder.rb +0 -15
  235. data/ruby/neo4j/driver/internal/messaging/encode/goodbye_message_encoder.rb +0 -14
  236. data/ruby/neo4j/driver/internal/messaging/encode/hello_message_encoder.rb +0 -15
  237. data/ruby/neo4j/driver/internal/messaging/encode/init_message_encoder.rb +0 -16
  238. data/ruby/neo4j/driver/internal/messaging/encode/logoff_message_encoder.rb +0 -14
  239. data/ruby/neo4j/driver/internal/messaging/encode/logon_message_encoder.rb +0 -15
  240. data/ruby/neo4j/driver/internal/messaging/encode/pull_all_message_encoder.rb +0 -14
  241. data/ruby/neo4j/driver/internal/messaging/encode/pull_message_encoder.rb +0 -15
  242. data/ruby/neo4j/driver/internal/messaging/encode/reset_message_encoder.rb +0 -14
  243. data/ruby/neo4j/driver/internal/messaging/encode/rollback_message_encoder.rb +0 -14
  244. data/ruby/neo4j/driver/internal/messaging/encode/route_message_encoder.rb +0 -24
  245. data/ruby/neo4j/driver/internal/messaging/encode/route_v44_message_encoder.rb +0 -22
  246. data/ruby/neo4j/driver/internal/messaging/encode/run_message_encoder.rb +0 -16
  247. data/ruby/neo4j/driver/internal/messaging/encode/run_with_metadata_message_encoder.rb +0 -17
  248. data/ruby/neo4j/driver/internal/messaging/request/abstract_streaming_message.rb +0 -25
  249. data/ruby/neo4j/driver/internal/messaging/request/begin_message.rb +0 -25
  250. data/ruby/neo4j/driver/internal/messaging/request/commit_message.rb +0 -20
  251. data/ruby/neo4j/driver/internal/messaging/request/discard_all_message.rb +0 -20
  252. data/ruby/neo4j/driver/internal/messaging/request/discard_message.rb +0 -23
  253. data/ruby/neo4j/driver/internal/messaging/request/goodbye_message.rb +0 -20
  254. data/ruby/neo4j/driver/internal/messaging/request/hello_message.rb +0 -30
  255. data/ruby/neo4j/driver/internal/messaging/request/init_message.rb +0 -19
  256. data/ruby/neo4j/driver/internal/messaging/request/logoff_message.rb +0 -13
  257. data/ruby/neo4j/driver/internal/messaging/request/logon_message.rb +0 -13
  258. data/ruby/neo4j/driver/internal/messaging/request/message_with_metadata.rb +0 -17
  259. data/ruby/neo4j/driver/internal/messaging/request/multi_database_util.rb +0 -26
  260. data/ruby/neo4j/driver/internal/messaging/request/pull_all_message.rb +0 -23
  261. data/ruby/neo4j/driver/internal/messaging/request/pull_message.rb +0 -22
  262. data/ruby/neo4j/driver/internal/messaging/request/reset_message.rb +0 -32
  263. data/ruby/neo4j/driver/internal/messaging/request/rollback_message.rb +0 -20
  264. data/ruby/neo4j/driver/internal/messaging/request/route_message.rb +0 -28
  265. data/ruby/neo4j/driver/internal/messaging/request/run_message.rb +0 -23
  266. data/ruby/neo4j/driver/internal/messaging/request/run_with_metadata_message.rb +0 -49
  267. data/ruby/neo4j/driver/internal/messaging/request/transaction_metadata_builder.rb +0 -24
  268. data/ruby/neo4j/driver/internal/messaging/response/failure_message.rb +0 -40
  269. data/ruby/neo4j/driver/internal/messaging/response/ignored_message.rb +0 -29
  270. data/ruby/neo4j/driver/internal/messaging/response/record_message.rb +0 -33
  271. data/ruby/neo4j/driver/internal/messaging/response/success_message.rb +0 -34
  272. data/ruby/neo4j/driver/internal/messaging/v3/bolt_protocol_v3.rb +0 -82
  273. data/ruby/neo4j/driver/internal/messaging/v3/message_format_v3.rb +0 -17
  274. data/ruby/neo4j/driver/internal/messaging/v3/message_writer_v3.rb +0 -27
  275. data/ruby/neo4j/driver/internal/messaging/v4/bolt_protocol_v4.rb +0 -29
  276. data/ruby/neo4j/driver/internal/messaging/v4/message_format_v4.rb +0 -13
  277. data/ruby/neo4j/driver/internal/messaging/v4/message_writer_v4.rb +0 -17
  278. data/ruby/neo4j/driver/internal/messaging/v41/bolt_protocol_v41.rb +0 -25
  279. data/ruby/neo4j/driver/internal/messaging/v42/bolt_protocol_v42.rb +0 -13
  280. data/ruby/neo4j/driver/internal/messaging/v43/bolt_protocol_v43.rb +0 -19
  281. data/ruby/neo4j/driver/internal/messaging/v43/message_format_v43.rb +0 -14
  282. data/ruby/neo4j/driver/internal/messaging/v43/message_writer_v43.rb +0 -20
  283. data/ruby/neo4j/driver/internal/messaging/v44/bolt_protocol_v44.rb +0 -17
  284. data/ruby/neo4j/driver/internal/messaging/v44/message_format_v44.rb +0 -14
  285. data/ruby/neo4j/driver/internal/messaging/v44/message_writer_v44.rb +0 -15
  286. data/ruby/neo4j/driver/internal/messaging/v5/bolt_protocol_v5.rb +0 -21
  287. data/ruby/neo4j/driver/internal/messaging/v5/message_format_v5.rb +0 -18
  288. data/ruby/neo4j/driver/internal/messaging/v5/value_unpacker_v5.rb +0 -17
  289. data/ruby/neo4j/driver/internal/messaging/v51/bolt_protocol_v51.rb +0 -40
  290. data/ruby/neo4j/driver/internal/messaging/v51/message_format_v51.rb +0 -14
  291. data/ruby/neo4j/driver/internal/messaging/v51/message_writer_v51.rb +0 -17
  292. data/ruby/neo4j/driver/internal/metrics/connection_pool_metrics_listener.rb +0 -34
  293. data/ruby/neo4j/driver/internal/metrics/internal_abstract_metrics.rb +0 -46
  294. data/ruby/neo4j/driver/internal/metrics/internal_connection_pool_metrics.rb +0 -105
  295. data/ruby/neo4j/driver/internal/metrics/internal_metrics.rb +0 -82
  296. data/ruby/neo4j/driver/internal/metrics/internal_metrics_provider.rb +0 -18
  297. data/ruby/neo4j/driver/internal/metrics/listener_event.rb +0 -17
  298. data/ruby/neo4j/driver/internal/metrics/metrics_provider.rb +0 -24
  299. data/ruby/neo4j/driver/internal/metrics/time_recorder_listener_event.rb +0 -15
  300. data/ruby/neo4j/driver/internal/packstream/byte_array_incompatible_packer.rb +0 -12
  301. data/ruby/neo4j/driver/internal/packstream/pack_input.rb +0 -47
  302. data/ruby/neo4j/driver/internal/packstream/pack_output.rb +0 -39
  303. data/ruby/neo4j/driver/internal/packstream/pack_stream.rb +0 -324
  304. data/ruby/neo4j/driver/internal/packstream/pack_type.rb +0 -17
  305. data/ruby/neo4j/driver/internal/read_only_bookmark_holder.rb +0 -13
  306. data/ruby/neo4j/driver/internal/resolved_bolt_server_address.rb +0 -35
  307. data/ruby/neo4j/driver/internal/retry/exponential_backoff_retry_logic.rb +0 -150
  308. data/ruby/neo4j/driver/internal/revocation_strategy.rb +0 -19
  309. data/ruby/neo4j/driver/internal/scheme.rb +0 -32
  310. data/ruby/neo4j/driver/internal/security/internal_auth_token.rb +0 -15
  311. data/ruby/neo4j/driver/internal/security/security_plan_impl.rb +0 -48
  312. data/ruby/neo4j/driver/internal/security_setting.rb +0 -66
  313. data/ruby/neo4j/driver/internal/session_factory_impl.rb +0 -32
  314. data/ruby/neo4j/driver/internal/spi/connection.rb +0 -19
  315. data/ruby/neo4j/driver/internal/spi/connection_pool.rb +0 -9
  316. data/ruby/neo4j/driver/internal/spi/response_handler.rb +0 -23
  317. data/ruby/neo4j/driver/internal/summary/internal_database_info.rb +0 -7
  318. data/ruby/neo4j/driver/internal/summary/internal_input_position.rb +0 -11
  319. data/ruby/neo4j/driver/internal/summary/internal_notification.rb +0 -16
  320. data/ruby/neo4j/driver/internal/summary/internal_plan.rb +0 -41
  321. data/ruby/neo4j/driver/internal/summary/internal_profiled_plan.rb +0 -32
  322. data/ruby/neo4j/driver/internal/summary/internal_result_summary.rb +0 -33
  323. data/ruby/neo4j/driver/internal/summary/internal_server_info.rb +0 -6
  324. data/ruby/neo4j/driver/internal/summary/internal_summary_counters.rb +0 -18
  325. data/ruby/neo4j/driver/internal/svm/netty_substitutions.rb +0 -196
  326. data/ruby/neo4j/driver/internal/svm/z_lib_substitutions.rb +0 -21
  327. data/ruby/neo4j/driver/internal/util/certificate_tool.rb +0 -65
  328. data/ruby/neo4j/driver/internal/util/clock.rb +0 -29
  329. data/ruby/neo4j/driver/internal/util/error_util.rb +0 -104
  330. data/ruby/neo4j/driver/internal/util/extract.rb +0 -123
  331. data/ruby/neo4j/driver/internal/util/format.rb +0 -39
  332. data/ruby/neo4j/driver/internal/util/futures.rb +0 -99
  333. data/ruby/neo4j/driver/internal/util/iterables.rb +0 -35
  334. data/ruby/neo4j/driver/internal/util/lock_util.rb +0 -23
  335. data/ruby/neo4j/driver/internal/util/metadata_extractor.rb +0 -101
  336. data/ruby/neo4j/driver/internal/util/mutex.rb +0 -9
  337. data/ruby/neo4j/driver/internal/util/preconditions.rb +0 -16
  338. data/ruby/neo4j/driver/internal/util/result_holder.rb +0 -72
  339. data/ruby/neo4j/driver/internal/util/server_version.rb +0 -60
  340. data/ruby/neo4j/driver/logging1.rb +0 -51
  341. data/ruby/neo4j/driver/net/server_address.rb +0 -9
  342. data/ruby/neo4j/driver/query.rb +0 -48
  343. data/ruby/neo4j/driver/records.rb +0 -13
  344. data/ruby/neo4j/driver/transaction_config.rb +0 -50
  345. data/ruby/neo4j/driver/values.rb +0 -26
  346. data/ruby/neo4j/driver.rb +0 -30
@@ -0,0 +1,341 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Neo4j
4
+ module Driver
5
+ # Represents an explicit transaction
6
+ class Transaction
7
+ attr_reader :connection
8
+
9
+ def initialize(connection, session, bookmarks = [], options = {}, telemetry_api: nil, telemetry_ack: nil,
10
+ pipelined: false, on_begin: nil, on_release: nil)
11
+ @connection = connection
12
+ @session = session
13
+ @options = options
14
+ # executeQuery pipelines BEGIN + RUN + PULL (Optimization:ExecuteQueryPipelining):
15
+ # BEGIN's reply is read only after the first RUN+PULL are flushed, not eagerly.
16
+ @pipelined = pipelined
17
+ @on_begin = on_begin # called with the BEGIN reply (home-db cache update)
18
+ @on_release = on_release # called once when the connection is no longer needed
19
+ @open = true
20
+ @committed = false
21
+ @rolled_back = false
22
+ @failed = false
23
+ @terminating_error = nil # the classified error that terminated this tx (a RUN/commit failure)
24
+ @current_result = nil
25
+ # Every result opened in this tx, in order. Bolt lets multiple stay open
26
+ # and streaming concurrently (qid multiplexing); a new RUN no longer
27
+ # force-buffers the previous one, so we track them all here to discard
28
+ # any still-open at commit/rollback and to spot a mid-stream failure.
29
+ @open_results = []
30
+
31
+ # Begin the transaction
32
+ # Drop blank values so the serialised BEGIN map matches what
33
+ # testkit's stub scripts expect (e.g. `BEGIN {"db": "adb"}`,
34
+ # not `BEGIN {"db": "adb", "tx_metadata": {}}`).
35
+ begin_extra = {
36
+ bookmarks: bookmarks,
37
+ db: options[:database],
38
+ mode: options[:access_mode],
39
+ tx_timeout: options[:timeout],
40
+ tx_metadata: options[:metadata],
41
+ imp_user: options[:impersonated_user]
42
+ }
43
+ begin_extra.reject!(&Internal::Extras::BLANK)
44
+
45
+ # TELEMETRY (api = 0 managed / 1 explicit / 3 executeQuery) pipelined
46
+ # ahead of BEGIN when the server opted in and the driver didn't disable
47
+ # it; its SUCCESS is read first (see #ack_begin!).
48
+ @telemetry_sent = @connection.telemetry(telemetry_api, disabled: options[:telemetry_disabled])
49
+ @telemetry_ack = telemetry_ack
50
+ # Session-level NotificationsConfig rides on BEGIN (5.2+); the tx's own
51
+ # RUNs carry none. nil / pre-5.2 => no notification keys on the wire.
52
+ @connection.send_message(@connection.protocol.build_begin(begin_extra,
53
+ notification_config: options[:notification_config]))
54
+ @connection.flush
55
+
56
+ # Non-pipelined: read BEGIN's reply now, a plain round-trip (unchanged).
57
+ # Pipelined (executeQuery): defer it so the first #run can flush RUN+PULL
58
+ # before we block — a pipelining server withholds BEGIN's SUCCESS until it
59
+ # has all three. #ack_begin! drains it after that flush.
60
+ @begin_acked = false
61
+ ack_begin! unless @pipelined
62
+ rescue Exceptions::Neo4jException => e
63
+ # Classify first so the auth-token manager is notified and the
64
+ # connection is flagged for discard on an auth failure (the server
65
+ # closes it). BEGIN failed → server is in FAILED state; RESET to
66
+ # make it reusable, unless it's being discarded (a security
67
+ # failure: the server closes it and RESET would just error).
68
+ classified = @connection.classify_failure(e)
69
+ @connection.reset! unless @connection.auth_failed
70
+ @open = false
71
+ release_connection
72
+ raise classified
73
+ rescue StandardError
74
+ # Transport-level failure (IO/socket). RESET will likely fail
75
+ # too on a dead connection, but release the lease so it doesn't
76
+ # leak; pool reuse will surface the breakage to the next caller.
77
+ @open = false
78
+ release_connection
79
+ raise
80
+ end
81
+
82
+ def run(query, **parameters)
83
+ Internal::Validator.require_query_text!(query)
84
+ # A server failure terminated this transaction; further work is
85
+ # rejected locally (no wire traffic) as a TransactionTerminatedException
86
+ # — a ClientException subclass, matching the Java driver. Covers both a
87
+ # failed RUN (sets @failed) and a result that failed mid-stream during
88
+ # the user's own iteration (@current_result.failed?). Message wording
89
+ # stays "rolled back" to match the Java flavor (a shared integration spec
90
+ # asserts it on both impls); only the exception class becomes specific.
91
+ raise Exceptions::TransactionTerminatedException,
92
+ 'Cannot run more queries in this transaction, it has been rolled back' if terminated?
93
+ unless @open
94
+ # Mirror the Java/JRuby messages so the closed-state reason
95
+ # (committed vs rolled back) is reported the same on both impls.
96
+ raise Exceptions::ClientException,
97
+ "Cannot run more queries in this transaction, it has been #{@committed ? 'committed' : 'rolled back'}"
98
+ end
99
+
100
+ # A new query becomes current; the previous result must now name its qid
101
+ # explicitly on further PULL/DISCARD (the server defaults them to the last
102
+ # opened query). We keep it open and streaming rather than buffering it —
103
+ # that's the qid multiplexing the nested-result tests exercise.
104
+ @current_result&.demote!
105
+
106
+ fetch_size = effective_fetch_size
107
+
108
+ # send/flush are inside the begin block so a transport-level
109
+ # failure surfacing on fetch_response still sets @failed and
110
+ # goes through classify_failure. Connection#send_message and
111
+ # #flush both defer peer-closed errors so a buffered server
112
+ # FAILURE is read before fetch_response can raise its own
113
+ # EOF-driven ServiceUnavailableException — JRuby surfaces
114
+ # EPIPE eagerly, MRI tends to defer it naturally. Without
115
+ # this rescue placement, session.close's subsequent rollback
116
+ # path takes the ROLLBACK-message branch (rather than
117
+ # rollback_via_reset) and re-fails on the dead connection.
118
+ buffer = Bolt::RecordBuffer.new(fetch_size: fetch_size)
119
+ handler = Bolt::StreamHandler.new(buffer)
120
+ run_response =
121
+ begin
122
+ @connection.send_message(@connection.protocol.build_run(query, parameters, {}))
123
+ @connection.send_message(@connection.protocol.build_pull(n: fetch_size), handler)
124
+ @connection.flush
125
+ # Drain the pipelined BEGIN (+telemetry) reply now that RUN+PULL are on
126
+ # the wire — no-op unless this is the pipelined first run. A BEGIN that
127
+ # failed surfaces here and is handled as a run failure below; the tx
128
+ # then rolls back on its way out, resetting the connection.
129
+ ack_begin!
130
+ @connection.fetch_response.assert_success!
131
+ rescue Exceptions::Neo4jException => e
132
+ @failed = true
133
+ # Remember the terminating error: sibling results still open must
134
+ # raise it (not pull) once this RUN fails — the connection is FAILED.
135
+ raise(@terminating_error = @connection.classify_failure(e))
136
+ end
137
+
138
+ keys = (run_response.metadata[:fields] || run_response.metadata['fields'] || []).map(&:to_sym)
139
+
140
+ @current_result = Result.new(@connection, keys, buffer: buffer, handler: handler,
141
+ query_text: query, parameters: parameters,
142
+ run_metadata: run_response.metadata, fetch_size: fetch_size,
143
+ qid: run_response.metadata[:qid],
144
+ terminated_error: method(:terminating_error))
145
+ @open_results << @current_result
146
+ @current_result
147
+ end
148
+
149
+ def commit
150
+ # Java/JRuby-aligned messages for the already-closed states.
151
+ raise Exceptions::ClientException, 'Can\'t commit, transaction has been committed' if @committed
152
+ raise Exceptions::ClientException, 'Can\'t commit, transaction has been rolled back' if @rolled_back
153
+ raise Exceptions::ClientException, 'Transaction is already closed' unless @open
154
+
155
+ if terminated?
156
+ rollback_via_reset
157
+ raise Exceptions::TransactionTerminatedException,
158
+ "Transaction can't be committed. It has been rolled back"
159
+ end
160
+
161
+ begin
162
+ discard_open_results
163
+ rescue Exceptions::Neo4jException
164
+ rollback_via_reset
165
+ raise
166
+ end
167
+
168
+ # send/flush inside the begin block — see Transaction#run for
169
+ # the rationale on JRuby vs MRI socket-write timing.
170
+ response =
171
+ begin
172
+ @connection.send_message(Bolt::Message.commit)
173
+ @connection.flush
174
+ @connection.fetch_response.assert_success!
175
+ rescue Exceptions::Neo4jException => e
176
+ @failed = true
177
+ # Classify first so a security failure flags the connection
178
+ # for discard before rollback_via_reset releases it.
179
+ classified = @connection.classify_failure(e)
180
+ rollback_via_reset
181
+ raise classified
182
+ end
183
+
184
+ @committed = true
185
+ @open = false
186
+
187
+ bookmarks = response.metadata[:bookmark]
188
+ @session.update_bookmarks(bookmarks) if bookmarks
189
+ release_connection
190
+ end
191
+
192
+ def rollback
193
+ raise Exceptions::ClientException, 'Transaction is already closed' unless @open
194
+
195
+ # A pipelined executeQuery tx whose query never ran (e.g. local validation
196
+ # failed before RUN/PULL were sent) left BEGIN's reply unread — and a
197
+ # pipelining server may withhold it until RUN/PULL arrive, which now never
198
+ # will. RESET rolls the tx back and drains any pending reply without a
199
+ # blocking read that could deadlock; there are no open results to discard.
200
+ return rollback_via_reset unless @begin_acked
201
+
202
+ # A terminated tx left the connection FAILED: don't drain open results
203
+ # (that would send PULL/DISCARD the server rejects) — just RESET.
204
+ if terminated?
205
+ rollback_via_reset
206
+ return
207
+ end
208
+
209
+ begin
210
+ discard_open_results
211
+ rescue Exceptions::Neo4jException
212
+ # Failures surfaced while draining a pending result are expected
213
+ # during rollback — the tx is being discarded anyway. @failed is
214
+ # set; RESET path below will clean up the connection.
215
+ end
216
+
217
+ if @failed
218
+ rollback_via_reset
219
+ return
220
+ end
221
+
222
+ begin
223
+ @connection.send_message(Bolt::Message.rollback)
224
+ @connection.flush
225
+ @connection.fetch_response.assert_success!
226
+ rescue Exceptions::ServiceUnavailableException, Exceptions::SessionExpiredException
227
+ # Rolling back on a broken/dead connection is a no-op — the
228
+ # server discards the tx when the link dies. Swallow these so
229
+ # session.close's rollback path stays clean.
230
+ rescue Exceptions::Neo4jException => e
231
+ # A server FAILURE on ROLLBACK (e.g. DatabaseUnavailable) is a
232
+ # real error: the connection is now in FAILED state. RESET it
233
+ # back to READY, then surface the failure through the routing
234
+ # classifier — same as commit/run — so routing side effects
235
+ # (e.g. deactivate on DatabaseUnavailable) fire and the surfaced
236
+ # type is consistent. No-op for direct connections.
237
+ @connection.reset!
238
+ raise @connection.classify_failure(e)
239
+ ensure
240
+ @rolled_back = true
241
+ @open = false
242
+ release_connection
243
+ end
244
+ end
245
+
246
+ def close
247
+ rollback if @open && !@committed
248
+ end
249
+
250
+ def open?
251
+ @open
252
+ end
253
+
254
+ def failed?
255
+ @failed
256
+ end
257
+
258
+ private
259
+
260
+ # Read the deferred BEGIN acknowledgement (and the telemetry SUCCESS
261
+ # pipelined ahead of it). Idempotent: called once — eagerly in #initialize
262
+ # for a normal tx, or after the first RUN+PULL flush for a pipelined
263
+ # executeQuery (and defensively before ROLLBACK if that query never ran).
264
+ def ack_begin!
265
+ return if @begin_acked
266
+ @begin_acked = true
267
+
268
+ if @telemetry_sent
269
+ @connection.fetch_response.assert_success!
270
+ # The server acknowledged telemetry; a managed-tx retry won't re-send it.
271
+ @telemetry_ack&.call
272
+ end
273
+ begin_response = @connection.fetch_response.assert_success!
274
+ # A home-db BEGIN that sent db=nil comes back with the resolved name.
275
+ @on_begin&.call(begin_response)
276
+ begin_response
277
+ end
278
+
279
+ # The transaction is terminated once a server failure has hit it — either
280
+ # a tx method caught it (@failed) or any open result failed during the
281
+ # user's own iteration (its failure hasn't passed through a tx method).
282
+ def terminated? = @failed || @open_results.any?(&:failed?)
283
+
284
+ # The error that terminated this tx, or nil. A RUN/commit failure records
285
+ # it directly; a result that failed mid-iteration (the user's own PULL)
286
+ # carries it on the result. Passed to each result as its terminated_error
287
+ # so a sibling raises it instead of pulling on a FAILED connection.
288
+ def terminating_error = @terminating_error || @open_results.find(&:failed?)&.failure
289
+
290
+ # See Session#effective_fetch_size. Transactions inherit the session
291
+ # options at open, so the same default rules apply.
292
+ def effective_fetch_size
293
+ size = @options[:fetch_size]
294
+ size.nil? ? 1000 : size
295
+ end
296
+
297
+ # At tx end (commit/rollback) every still-open result goes out of scope,
298
+ # so discard each — DISCARD abandons remaining records (essential when a
299
+ # result is unbounded) rather than streaming them into memory, and leaves
300
+ # each raising ResultConsumedException on later access. Demoted results
301
+ # DISCARD by their qid; the current one omits it (targets the last query).
302
+ def discard_open_results
303
+ @open_results.each do |result|
304
+ begin
305
+ result.consume
306
+ rescue Exceptions::Neo4jException => e
307
+ @failed = true
308
+ # A wire error during PULL streaming (e.g. a reader connection
309
+ # interrupted mid-stream) raises ServiceUnavailable straight from
310
+ # fetch_response, not via Result#on_failure — so it never saw the
311
+ # routing classifier. Run it through here so a routed connection
312
+ # failure surfaces as SessionExpired (idempotent if already classified).
313
+ raise @connection.classify_failure(e)
314
+ end
315
+
316
+ # consume is a no-op when the result was already drained by the user;
317
+ # surface any stored failure so callers can react.
318
+ @failed = true if result.failed?
319
+ end
320
+ end
321
+
322
+ # Recover a failed transaction by asking the server to RESET the
323
+ # connection. RESET transitions the server from FAILED back to READY
324
+ # and implicitly rolls back the open transaction.
325
+ def rollback_via_reset
326
+ # Skip RESET on a connection being discarded (auth failure: the
327
+ # server closes it, RESET would just error); release then honors
328
+ # the discard flag so it isn't pooled.
329
+ @connection.reset! unless @connection.auth_failed
330
+ @rolled_back = true
331
+ @open = false
332
+ release_connection
333
+ end
334
+
335
+ def release_connection
336
+ @on_release&.call
337
+ @on_release = nil # idempotent
338
+ end
339
+ end
340
+ end
341
+ end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Neo4j
4
+ module Driver
5
+ module Types
6
+ # Bolt Duration. Stores months, days, seconds, nanoseconds as
7
+ # distinct ints — Neo4j keeps these separate because months are
8
+ # variable-length (no fixed conversion to seconds).
9
+ class Duration < TemporalValue
10
+ attr_reader :months, :days, :seconds, :nanoseconds
11
+
12
+ def self.significant_fields = %i[months days seconds nanoseconds]
13
+
14
+ def initialize(months, days, seconds, nanoseconds)
15
+ @months = months.to_i
16
+ @days = days.to_i
17
+ @seconds = seconds.to_i
18
+ @nanoseconds = nanoseconds.to_i
19
+ normalize!
20
+ end
21
+
22
+ # Parse ISO 8601 duration: "P1Y2M3DT4H5M6.789S", "P3M", "PT2H30M"
23
+ def self.parse(string)
24
+ unless string =~ /^P(?:(\d+)Y)?(?:(\d+)M)?(?:(\d+)D)?(?:T(?:(\d+)H)?(?:(\d+)M)?(?:(\d+(?:\.\d+)?)S)?)?$/
25
+ raise ArgumentError, "Invalid ISO 8601 duration format: #{string}"
26
+ end
27
+
28
+ years = ($1 || 0).to_i
29
+ months = ($2 || 0).to_i
30
+ days = ($3 || 0).to_i
31
+ hours = ($4 || 0).to_i
32
+ minutes = ($5 || 0).to_i
33
+ seconds_with_fraction = ($6 || 0).to_f
34
+
35
+ total_months = years * 12 + months
36
+ whole_seconds = seconds_with_fraction.to_i
37
+ fractional_seconds = seconds_with_fraction - whole_seconds
38
+ nanos = (fractional_seconds * NANOS_PER_SECOND).round
39
+ total_seconds = hours * 3600 + minutes * 60 + whole_seconds
40
+
41
+ new(total_months, days, total_seconds, nanos)
42
+ end
43
+
44
+ def parts
45
+ { months: @months, days: @days, seconds: @seconds, nanoseconds: @nanoseconds }
46
+ end
47
+
48
+ # ISO 8601 form. Nanoseconds get a 9-digit zero-padded suffix on
49
+ # seconds so 1s + 5ns prints as "PT1.000000005S", not "PT1.5S".
50
+ def to_s
51
+ if @nanoseconds.zero?
52
+ format('P%dM%dDT%dS', @months, @days, @seconds)
53
+ else
54
+ format('P%dM%dDT%d.%09dS', @months, @days, @seconds, @nanoseconds)
55
+ end
56
+ end
57
+
58
+ private
59
+
60
+ # Normalize nanoseconds overflow/underflow into seconds:
61
+ # -1ns becomes -1s + 999999999ns; 1.5e9 ns becomes 1s + 5e8 ns.
62
+ def normalize!
63
+ if @nanoseconds < 0
64
+ sec_adjust = (@nanoseconds.abs / NANOS_PER_SECOND.to_f).ceil
65
+ @seconds -= sec_adjust
66
+ @nanoseconds += sec_adjust * NANOS_PER_SECOND
67
+ elsif @nanoseconds >= NANOS_PER_SECOND
68
+ sec_adjust = @nanoseconds / NANOS_PER_SECOND
69
+ @seconds += sec_adjust
70
+ @nanoseconds %= NANOS_PER_SECOND
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
@@ -3,17 +3,61 @@
3
3
  module Neo4j
4
4
  module Driver
5
5
  module Types
6
- class LocalDateTime < Time
7
- class << self
8
- FIELDS = %i[year month day hour min sec nsec].freeze
6
+ # Bolt LocalDateTime wall-clock datetime without timezone.
7
+ # Stored as the wire-format pair (epoch_seconds, nanoseconds), where
8
+ # epoch_seconds is the wall-clock components encoded as if they were
9
+ # UTC. Two LocalDateTimes representing the same wall-clock value
10
+ # always have the same field values regardless of the host TZ.
11
+ class LocalDateTime < TemporalValue
12
+ PARSE_FORMATS = [
13
+ '%Y-%m-%d %H:%M:%S.%N',
14
+ '%Y-%m-%dT%H:%M:%S.%N',
15
+ '%Y-%m-%d %H:%M:%S',
16
+ '%Y-%m-%dT%H:%M:%S',
17
+ '%Y-%m-%d %H:%M'
18
+ ].freeze
9
19
 
10
- def significant_fields
11
- FIELDS
12
- end
20
+ attr_reader :epoch_seconds, :nanoseconds
21
+
22
+ def self.significant_fields = %i[epoch_seconds nanoseconds]
23
+
24
+ def initialize(epoch_seconds, nanoseconds)
25
+ @epoch_seconds = epoch_seconds
26
+ @nanoseconds = nanoseconds
27
+ end
28
+
29
+ def self.from_epoch(epoch_seconds, nanoseconds) = new(epoch_seconds, nanoseconds)
30
+
31
+ def self.from_time(time)
32
+ new(time.to_i, ((time.to_f - time.to_i) * NANOS_PER_SECOND).round)
33
+ end
34
+
35
+ def self.parse(string)
36
+ # Strip trailing timezone if present — naive datetime ignores it.
37
+ naive = string.sub(/[Z+\-]\d{2}:?\d{2}?$/, '')
38
+ time = PARSE_FORMATS.lazy.filter_map { |fmt| ::Time.strptime(naive, fmt) rescue nil }.first
39
+ raise ArgumentError, "Invalid LocalDateTime format: #{string}" unless time
40
+ from_time(::Time.utc(*time_components(time)))
13
41
  end
14
42
 
15
- delegate(*significant_fields, to: :@time)
16
- delegate :to_i, to: :@time
43
+ def self.time_components(time)
44
+ [time.year, time.month, time.day, time.hour, time.min, time.sec, time.subsec * 1_000_000]
45
+ end
46
+ private_class_method :time_components
47
+
48
+ def to_time
49
+ ::Time.at(@epoch_seconds, @nanoseconds.to_i, :nanosecond)
50
+ end
51
+
52
+ # Add a numeric or ActiveSupport::Duration. Sub-second precision
53
+ # preserved by going through to_f.
54
+ def +(seconds)
55
+ self.class.from_time(to_time + seconds.to_f)
56
+ end
57
+
58
+ def to_s
59
+ to_time.strftime('%Y-%m-%d %H:%M:%S.%N')
60
+ end
17
61
  end
18
62
  end
19
63
  end
@@ -3,16 +3,51 @@
3
3
  module Neo4j
4
4
  module Driver
5
5
  module Types
6
- class LocalTime < Time
7
- class << self
8
- FIELDS = %i[hour min sec nsec].freeze
6
+ # Bolt LocalTime time of day without timezone, stored as
7
+ # nanoseconds since midnight.
8
+ class LocalTime < TemporalValue
9
+ attr_reader :nanoseconds
9
10
 
10
- def significant_fields
11
- FIELDS
12
- end
11
+ def self.significant_fields = %i[nanoseconds]
12
+
13
+ def initialize(nanoseconds)
14
+ @nanoseconds = nanoseconds % NANOS_PER_DAY
15
+ end
16
+
17
+ def self.from_nanos(nanoseconds) = new(nanoseconds)
18
+
19
+ # Accepts "12:34:56.123456789" or any string containing such a
20
+ # time component (so a full datetime works too — we just take the
21
+ # time fields).
22
+ def self.parse(string)
23
+ match = string.match(/(\d{1,2}):(\d{2})(?::(\d{2})(?:\.(\d+))?)?/)
24
+ raise ArgumentError, "Invalid LocalTime format: #{string}" unless match
25
+
26
+ new(parse_nanos(match[1].to_i, match[2].to_i, (match[3] || 0).to_i, match[4] || '0'))
13
27
  end
14
28
 
15
- delegate(*significant_fields, to: :@time)
29
+ def self.parse_nanos(hour, minute, second, fraction)
30
+ hour * NANOS_PER_HOUR +
31
+ minute * NANOS_PER_MINUTE +
32
+ second * NANOS_PER_SECOND +
33
+ fraction.ljust(9, '0')[0..8].to_i
34
+ end
35
+ private_class_method :parse_nanos
36
+
37
+ def hour = (@nanoseconds / NANOS_PER_HOUR) % 24
38
+ def minute = (@nanoseconds / NANOS_PER_MINUTE) % 60
39
+ def second = (@nanoseconds / NANOS_PER_SECOND) % 60
40
+ def nanosecond = @nanoseconds % NANOS_PER_SECOND
41
+
42
+ # Add a numeric or ActiveSupport::Duration. Sub-second precision
43
+ # preserved by going through to_f (NOT to_i, which dropped 0.5s).
44
+ def +(seconds)
45
+ self.class.new(@nanoseconds + (seconds.to_f * NANOS_PER_SECOND).round)
46
+ end
47
+
48
+ def to_s
49
+ format('%02d:%02d:%02d.%09d', hour, minute, second, nanosecond)
50
+ end
16
51
  end
17
52
  end
18
53
  end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Neo4j
4
+ module Driver
5
+ module Types
6
+ # Represents a Node in the Neo4j graph
7
+ class Node
8
+ attr_reader :id, :labels, :properties, :element_id
9
+
10
+ def initialize(id, labels, properties, element_id = nil)
11
+ @id = id
12
+ @labels = labels
13
+ @properties = properties
14
+ @element_id = element_id || id.to_s
15
+ end
16
+
17
+ def [](key)
18
+ @properties[key.to_s] || @properties[key.to_sym]
19
+ end
20
+
21
+ def ==(other)
22
+ other.is_a?(Node) && other.id == @id
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -3,16 +3,80 @@
3
3
  module Neo4j
4
4
  module Driver
5
5
  module Types
6
- class OffsetTime < Time
7
- class << self
8
- FIELDS = %i[hour min sec nsec utc_offset].freeze
6
+ # Bolt OffsetTime time of day with a fixed UTC offset, stored as
7
+ # nanoseconds-since-midnight (in local wall clock) plus offset
8
+ # seconds. Equivalent to java.time.OffsetTime. Was previously
9
+ # called Types::Time, which collided cosmetically with ::Time.
10
+ class OffsetTime < TemporalValue
11
+ attr_reader :nanoseconds, :tz_offset_seconds
9
12
 
10
- def significant_fields
11
- FIELDS
12
- end
13
+ def self.significant_fields = %i[nanoseconds tz_offset_seconds]
14
+
15
+ def initialize(nanoseconds, tz_offset_seconds)
16
+ @nanoseconds = nanoseconds % NANOS_PER_DAY
17
+ @tz_offset_seconds = tz_offset_seconds
18
+ end
19
+
20
+ def self.from_nanos(nanoseconds, tz_offset_seconds) = new(nanoseconds, tz_offset_seconds)
21
+
22
+ # Accepts "12:34:56.123456789+01:00", "12:34:56Z", or any string
23
+ # containing such a time-with-offset component.
24
+ def self.parse(string)
25
+ match = string.match(/(\d{1,2}):(\d{2})(?::(\d{2})(?:\.(\d+))?)?([Z+\-][\d:]*)?/)
26
+ raise ArgumentError, "Invalid OffsetTime format: #{string}" unless match
27
+
28
+ nanos = LocalTime.send(:parse_nanos,
29
+ match[1].to_i, match[2].to_i, (match[3] || 0).to_i, match[4] || '0')
30
+ new(nanos, parse_offset(match[5] || 'Z'))
13
31
  end
14
32
 
15
- delegate(*significant_fields, to: :@time)
33
+ def self.parse_offset(str)
34
+ return 0 if str == 'Z'
35
+ raise ArgumentError, "Invalid timezone offset: #{str}" unless str =~ /^([+\-])(\d{2}):?(\d{2})?$/
36
+
37
+ sign = $1 == '+' ? 1 : -1
38
+ sign * ($2.to_i * 3600 + ($3 || 0).to_i * 60)
39
+ end
40
+ private_class_method :parse_offset
41
+
42
+ def hour = (@nanoseconds / NANOS_PER_HOUR) % 24
43
+ def minute = (@nanoseconds / NANOS_PER_MINUTE) % 60
44
+ def second = (@nanoseconds / NANOS_PER_SECOND) % 60
45
+ def nanosecond = @nanoseconds % NANOS_PER_SECOND
46
+
47
+ # Add a numeric or ActiveSupport::Duration. Sub-second precision
48
+ # preserved (see LocalTime#+).
49
+ def +(seconds)
50
+ self.class.new(@nanoseconds + (seconds.to_f * NANOS_PER_SECOND).round, @tz_offset_seconds)
51
+ end
52
+
53
+ def to_s
54
+ # Apply the sign to the formatted offset as a whole, not to
55
+ # `hours` alone — Ruby integer division for `-12600 / 3600` is
56
+ # -4 (floor toward -∞), which would render -03:30 as -04:30.
57
+ abs = @tz_offset_seconds.abs
58
+ sign = @tz_offset_seconds.negative? ? '-' : '+'
59
+ format('%02d:%02d:%02d.%09d%s%02d:%02d',
60
+ hour, minute, second, nanosecond,
61
+ sign, abs / 3600, (abs % 3600) / 60)
62
+ end
63
+
64
+ # Order by underlying UTC instant. Two OffsetTimes representing
65
+ # the same UTC instant in different offsets compare equal under
66
+ # <=>; == still requires same fields (they're not the same value).
67
+ def <=>(other)
68
+ return nil unless other.is_a?(OffsetTime)
69
+ utc_nanos <=> other.utc_nanos
70
+ end
71
+
72
+ protected
73
+
74
+ # UTC = local − offset. Positive offset means "ahead of UTC", so
75
+ # the UTC instant is *earlier* (smaller nanos). The previous
76
+ # implementation added instead of subtracting, inverting ordering.
77
+ def utc_nanos
78
+ @nanoseconds - (@tz_offset_seconds * NANOS_PER_SECOND)
79
+ end
16
80
  end
17
81
  end
18
82
  end