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

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