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

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 (84) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +113 -411
  3. data/lib/neo4j/driver/auth_token_manager.rb +29 -0
  4. data/lib/neo4j/driver/client_certificate_manager.rb +31 -0
  5. data/lib/neo4j/driver/exceptions/neo4j_exception.rb +16 -7
  6. data/lib/neo4j/driver/exceptions/no_such_record_exception.rb +1 -1
  7. data/lib/neo4j/driver/exceptions/protocol_exception.rb +0 -5
  8. data/lib/neo4j/driver/exceptions/security_retryable_exception.rb +14 -0
  9. data/lib/neo4j/driver/exceptions/transaction_terminated_exception.rb +13 -0
  10. data/lib/neo4j/driver/ext/auth_token.rb +17 -0
  11. data/{jruby → lib}/neo4j/driver/ext/auth_tokens.rb +5 -1
  12. data/lib/neo4j/driver/ext/bookmark_managers.rb +21 -0
  13. data/lib/neo4j/driver/ext/client_certificates.rb +21 -0
  14. data/{jruby → lib}/neo4j/driver/ext/config_converter.rb +36 -7
  15. data/{jruby → lib}/neo4j/driver/ext/exception_checkable.rb +3 -3
  16. data/lib/neo4j/driver/ext/exception_mapper.rb +173 -0
  17. data/lib/neo4j/driver/ext/graph_database.rb +25 -0
  18. data/lib/neo4j/driver/ext/internal/clock_adapter.rb +33 -0
  19. data/lib/neo4j/driver/ext/internal/cluster/routing_table_registry_impl.rb +28 -0
  20. data/lib/neo4j/driver/ext/internal/driver_factory.rb +54 -0
  21. data/lib/neo4j/driver/ext/internal/internal_notification_severity.rb +15 -0
  22. data/lib/neo4j/driver/ext/internal/reflection.rb +22 -0
  23. data/lib/neo4j/driver/ext/internal/summary/internal_gql_notification.rb +22 -0
  24. data/lib/neo4j/driver/ext/internal/summary/internal_gql_status_object.rb +18 -0
  25. data/{jruby → lib}/neo4j/driver/ext/internal/summary/internal_plan.rb +3 -5
  26. data/lib/neo4j/driver/ext/internal/summary/internal_query_profile.rb +36 -0
  27. data/lib/neo4j/driver/ext/internal/summary/internal_result_summary.rb +44 -0
  28. data/lib/neo4j/driver/ext/internal_driver.rb +102 -0
  29. data/{jruby → lib}/neo4j/driver/ext/internal_result.rb +2 -2
  30. data/{jruby → lib}/neo4j/driver/ext/internal_session.rb +5 -1
  31. data/lib/neo4j/driver/ext/logger.rb +55 -0
  32. data/lib/neo4j/driver/ext/map_converter.rb +13 -0
  33. data/lib/neo4j/driver/ext/neo_converter.rb +70 -0
  34. data/lib/neo4j/driver/ext/plain_map_converter.rb +11 -0
  35. data/{jruby → lib}/neo4j/driver/ext/ruby_converter.rb +20 -6
  36. data/lib/neo4j/driver/ext/unsupported_type.rb +14 -0
  37. data/lib/neo4j/driver/internal/deprecator.rb +9 -5
  38. data/lib/neo4j/driver/internal/duration_normalizer.rb +14 -39
  39. data/lib/neo4j/driver/internal/internal_auth_token_manager.rb +37 -0
  40. data/lib/neo4j/driver/internal/validator.rb +16 -2
  41. data/lib/neo4j/driver/summary/query_type.rb +5 -1
  42. data/lib/neo4j/driver/types/duration.rb +76 -0
  43. data/lib/neo4j/driver/types/local_date_time.rb +77 -8
  44. data/lib/neo4j/driver/types/local_time.rb +50 -7
  45. data/lib/neo4j/driver/types/offset_time.rb +78 -7
  46. data/lib/neo4j/driver/types/point.rb +38 -18
  47. data/lib/neo4j/driver/types/temporal_value.rb +44 -0
  48. data/lib/neo4j/driver/types/unresolvable_zoned_date_time.rb +38 -0
  49. data/{jruby → lib}/neo4j/driver/version.rb +1 -1
  50. data/{jruby → lib}/neo4j/driver.rb +31 -7
  51. data/lib/neo4j-ruby-driver_jars.rb +14 -0
  52. data/lib/neo4j-ruby-driver_loader.rb +34 -20
  53. data/lib/org/neo4j/driver/neo4j-java-driver-all/6.2.1/neo4j-java-driver-all-6.2.1.jar +0 -0
  54. data/lib/org/neo4j/driver/neo4j-java-driver-observation-metrics/6.2.1/neo4j-java-driver-observation-metrics-6.2.1.jar +0 -0
  55. data/lib/org/reactivestreams/reactive-streams/1.0.4/reactive-streams-1.0.4.jar +0 -0
  56. metadata +76 -226
  57. data/jruby/neo4j/driver/ext/async_converter.rb +0 -55
  58. data/jruby/neo4j/driver/ext/exception_mapper.rb +0 -92
  59. data/jruby/neo4j/driver/ext/graph_database.rb +0 -33
  60. data/jruby/neo4j/driver/ext/internal/async/internal_async_session.rb +0 -23
  61. data/jruby/neo4j/driver/ext/internal/cluster/routing_table_registry_impl.rb +0 -15
  62. data/jruby/neo4j/driver/ext/internal/cursor/disposable_async_result_cursor.rb +0 -15
  63. data/jruby/neo4j/driver/ext/internal/driver_factory.rb +0 -24
  64. data/jruby/neo4j/driver/ext/internal/summary/internal_result_summary.rb +0 -34
  65. data/jruby/neo4j/driver/ext/internal_driver.rb +0 -52
  66. data/jruby/neo4j/driver/ext/logger.rb +0 -60
  67. data/jruby/neo4j/driver/ext/map_converter.rb +0 -14
  68. data/jruby/neo4j/driver/ext/neo_converter.rb +0 -59
  69. data/lib/neo4j/driver/internal/bolt_server_address.rb +0 -97
  70. data/lib/neo4j/driver/synchronizable.rb +0 -23
  71. data/lib/neo4j/driver/types/time.rb +0 -45
  72. /data/{jruby → lib}/neo4j/driver/ext/internal/abstract_query_runner.rb +0 -0
  73. /data/{jruby → lib}/neo4j/driver/ext/internal/eager_result_value.rb +0 -0
  74. /data/{jruby → lib}/neo4j/driver/ext/internal/metrics/internal_connection_pool_metrics.rb +0 -0
  75. /data/{jruby → lib}/neo4j/driver/ext/internal/summary/internal_notification.rb +0 -0
  76. /data/{jruby → lib}/neo4j/driver/ext/internal_entity.rb +0 -0
  77. /data/{jruby → lib}/neo4j/driver/ext/internal_keys.rb +0 -0
  78. /data/{jruby → lib}/neo4j/driver/ext/internal_node.rb +0 -0
  79. /data/{jruby → lib}/neo4j/driver/ext/internal_record.rb +0 -0
  80. /data/{jruby → lib}/neo4j/driver/ext/internal_relationship.rb +0 -0
  81. /data/{jruby → lib}/neo4j/driver/ext/internal_transaction.rb +0 -0
  82. /data/{jruby → lib}/neo4j/driver/ext/query.rb +0 -0
  83. /data/{jruby → lib}/neo4j/driver/ext/run_override.rb +0 -0
  84. /data/{jruby → lib}/neo4j/driver/ext/start_end_naming.rb +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b150718dde06f62283bdde11edaca77f17a31cd9d5988229c7f9f11d0579f5fb
4
- data.tar.gz: 685a04443a4a9c8e8c90a3524a650166a6d63976d92e2e6418cd2807e2a531ef
3
+ metadata.gz: 8c23247c73132c7a801d9c4236df0e5ec06f7f89ca62e6a4bb5b5613a9ba0ca9
4
+ data.tar.gz: ad8bff687e3c0429cc26cc4c3663a7fcceb63f9c32c362dcb9a2cc968d416b38
5
5
  SHA512:
6
- metadata.gz: c988f11be9f369490e07f0de554590059f6b61f782abf9e16d48c16237e9298b88a5f7776d632bab79a6e0138f973cf11e2058a8d611e099ff59349d15668bb1
7
- data.tar.gz: 5ebe999f029edd2b4a20953b39176c1d3a6fd1c6fcf739c95084dabb7499b2e37f3fcd832a780d1dc8b2485a070ad18f6e72a0be62e2cfed62182b847c44dc9c
6
+ metadata.gz: e13cba51cf61aa2cf912d6cbf404acd8cd016e7d26aafbb50b89b988d30611e5e6b33ce22d1f786350389dc53730db535c92dc66fe94110e1a9dc7e892814094
7
+ data.tar.gz: 293189542dd4e549f20aaf0641734d677f94b12b6e323a07912e0c4d3caf08fa4d4706431b50a9e696ce3abbfc00ba7843e1f6499739760a00c2534fbc20ebcd
data/README.md CHANGED
@@ -1,87 +1,52 @@
1
1
  # Neo4j Ruby Driver
2
2
 
3
- This repository contains 2 implementation of a Neo4j driver for Ruby:
4
-
5
- - based on official Java implementation. It provides a thin wrapper over the Java driver (only on jruby).
6
- - pure Ruby implementation. Available on all Ruby versions >= 3.1.
7
-
8
- Network communication is handled using [Bolt Protocol](https://7687.org/).
9
-
10
- <details>
11
- <summary>Table of Contents</summary>
12
-
13
- * [Getting started](#getting-started)
14
- * [Installation](#installation)
15
- * [Getting a Neo4j instance](#getting-a-neo4j-instance)
16
- * [Quick start example](#quick-start-example)
17
- * [Server Compatibility](#server-compatibility)
18
- * [Usage](#usage)
19
- * [Connecting to a database](#connecting-to-a-database)
20
- * [URI schemes](#uri-schemes)
21
- * [Authentication](#authentication)
22
- * [Configuration](#configuration)
23
- * [Connectivity check](#connectivity-check)
24
- * [Sessions & transactions](#sessions--transactions)
25
- * [Session](#session)
26
- * [Auto-commit transactions](#auto-commit-transactions)
27
- * [Explicit transactions](#explicit-transactions)
28
- * [Read transactions](#read-transactions)
29
- * [Write transactions](#write-transactions)
30
- * [Working with results](#working-with-results)
31
- * [Accessing Node and Relationship data](#accessing-node-and-relationship-data)
32
- * [Working with Paths](#working-with-paths)
33
- * [Working with temporal types](#working-with-temporal-types)
34
- * [Type mapping](#type-mapping)
35
- * [Advanced](#advanced)
36
- * [Connection pooling](#connection-pooling)
37
- * [Logging](#logging)
38
- * [For Driver Engineers](#for-driver-engineers)
39
- * [Testing](#testing)
40
- * [Contributing](#contributing)
41
- * [License](#license)
42
-
43
- </details>
44
-
45
- ## Getting started
46
-
47
- ### Installation
48
-
49
- Add this line to your application's Gemfile:
50
-
51
- ```ruby
3
+ A driver for [Neo4j](https://neo4j.com) that speaks the
4
+ [Bolt protocol](https://neo4j.com/docs/bolt/current/), with **two
5
+ implementations behind one public Ruby API**:
6
+
7
+ - **MRI** (CRuby) — a pure-Ruby implementation of the Bolt protocol and
8
+ PackStream, with no Java dependency.
9
+ - **JRuby** — a thin wrapper over the official
10
+ [neo4j-java-driver](https://github.com/neo4j/neo4j-java-driver), whose jars
11
+ are managed by [jar-dependencies](https://github.com/mkristian/jar-dependencies).
12
+
13
+ Bundler installs the implementation matching your platform automatically, and
14
+ your code is identical either way. The gem version tracks the Java-driver
15
+ version it targets (e.g. `6.2.x`).
16
+
17
+ ## Features
18
+
19
+ - **Bolt 3.0 6.1** with handshake-manifest version negotiation
20
+ - **Complete type system**: Node, Relationship, Path, temporal types, spatial
21
+ Point, Duration, UUID, and a forward-compatible `UnsupportedType`
22
+ - **Transactions**: auto-commit, explicit (default-rollback), and managed
23
+ read/write with automatic retry
24
+ - **Sessions & causal consistency** via bookmarks
25
+ - **Authentication**: basic, bearer, Kerberos, custom, and managed auth-token
26
+ managers (with re-auth); per-session and per-`execute_query` auth
27
+ - **Cluster routing** with home-database resolution and caching
28
+ - **Security**: TLS 1.2/1.3, `bolt+s`/`bolt+ssc` schemes, and mutual-TLS client
29
+ certificates
30
+ - **Notifications config, GQL status objects, query telemetry**, connection
31
+ pooling, and a range of wire optimisations
32
+ - Verified against the shared driver
33
+ [testkit](https://github.com/neo4j-drivers/testkit) conformance suite on both
34
+ implementations
35
+
36
+ ## Installation
37
+
38
+ ```ruby
39
+ # Gemfile
52
40
  gem 'neo4j-ruby-driver'
53
41
  ```
54
42
 
55
- And then execute:
56
-
57
43
  ```bash
58
44
  bundle install
59
45
  ```
60
46
 
61
- Or install it yourself as:
62
-
63
- ```bash
64
- gem install neo4j-ruby-driver
65
- ```
66
-
67
- ### Getting a Neo4j instance
68
-
69
- You need a running Neo4j database in order to use the driver with it. The easiest way to spin up a **local instance** is
70
- through a Docker container.
71
-
72
- The command below runs the latest Neo4j version in Docker, setting the admin username and password to `neo4j` and
73
- `password` respectively:
74
-
75
- ```bash
76
- docker run \
77
- -p7474:7474 \
78
- -p7687:7687 \
79
- -d \
80
- -e NEO4J_AUTH=neo4j/password \
81
- neo4j:latest
82
- ```
47
+ ## Usage
83
48
 
84
- ### Quick start example
49
+ ### Basic connection
85
50
 
86
51
  ```ruby
87
52
  require 'neo4j/driver'
@@ -90,394 +55,131 @@ Neo4j::Driver::GraphDatabase.driver(
90
55
  'bolt://localhost:7687',
91
56
  Neo4j::Driver::AuthTokens.basic('neo4j', 'password')
92
57
  ) do |driver|
93
- driver.session(database: 'neo4j') do |session|
94
- query_result = session.run('RETURN 2+2 AS value')
95
- puts "2+2 equals #{query_result.single['value']}"
96
-
97
- # consume gives the execution summary
98
- create_result = session.run('CREATE (n)').consume
99
- puts "Nodes created: #{create_result.counters.nodes_created}"
58
+ driver.session do |session|
59
+ result = session.run('RETURN 1 AS num')
60
+ puts result.single[:num] # => 1
100
61
  end
101
62
  end
102
63
  ```
103
64
 
104
- ## Server Compatibility
105
-
106
- The compatibility with Neo4j Server versions is documented in
107
- the [Neo4j Knowledge Base](https://neo4j.com/developer/kb/neo4j-supported-versions/).
108
-
109
- ## Usage
110
-
111
- The API is to highest possible degree consistent with the official Java driver. Please refer to
112
- the [Neo4j Java Driver Manual](https://neo4j.com/docs/java-manual/current/), [examples in Ruby](https://github.com/neo4jrb/neo4j-ruby-driver/blob/master/docs/dev_manual_examples.rb),
113
- and code snippets below to understand how to use it.
114
- [Neo4j Java Driver API Docs](https://neo4j.com/docs/api/java-driver/current/) can be helpful as well.
115
-
116
- ### Connecting to a database
117
-
118
- #### URI schemes
119
-
120
- The driver supports the following URI schemes:
121
-
122
- | URI Scheme | Description |
123
- |----------------|---------------------------------------------------------------------------------|
124
- | `neo4j://` | Connect using routing to a cluster/causal cluster. |
125
- | `neo4j+s://` | Same as `neo4j://` but with full TLS encryption. |
126
- | `neo4j+ssc://` | Same as `neo4j://` but with full TLS encryption, without hostname verification. |
127
- | `bolt://` | Connect directly to a server using the Bolt protocol. |
128
- | `bolt+s://` | Same as `bolt://` but with full TLS encryption. |
129
- | `bolt+ssc://` | Same as `bolt://` but with full TLS encryption, without hostname verification. |
130
-
131
- Example:
132
-
133
- ```ruby
134
- # Connect to a single instance
135
- driver = Neo4j::Driver::GraphDatabase.driver(
136
- 'bolt://localhost:7687',
137
- Neo4j::Driver::AuthTokens.basic('neo4j', 'password')
138
- )
139
-
140
- # Connect to a cluster
141
- driver = Neo4j::Driver::GraphDatabase.driver(
142
- 'neo4j://graph.example.com:7687',
143
- Neo4j::Driver::AuthTokens.basic('neo4j', 'password')
144
- )
145
- ```
146
-
147
- #### Authentication
65
+ Use a `bolt://` URL for a single instance, `neo4j://` for a routed (cluster)
66
+ connection, and the `+s`/`+ssc` variants for TLS.
148
67
 
149
- The driver provides multiple authentication methods:
68
+ ### Parameterized queries
150
69
 
151
70
  ```ruby
152
- # Basic authentication
153
- auth = Neo4j::Driver::AuthTokens.basic('neo4j', 'password')
71
+ driver.session do |session|
72
+ node = session.run(
73
+ 'CREATE (n:Person {name: $name, age: $age}) RETURN n',
74
+ name: 'Alice', age: 30
75
+ ).single[:n]
154
76
 
155
- # With realm specification
156
- auth = Neo4j::Driver::AuthTokens.basic('neo4j', 'password', 'realm')
157
-
158
- # Kerberos authentication
159
- auth = Neo4j::Driver::AuthTokens.kerberos('ticket')
160
-
161
- # Bearer authentication
162
- auth = Neo4j::Driver::AuthTokens.bearer('token')
163
-
164
- # Custom authentication
165
- auth = Neo4j::Driver::AuthTokens.custom('principal', 'credentials', 'realm', 'scheme')
166
-
167
- # No authentication
168
- auth = Neo4j::Driver::AuthTokens.none
169
- ```
170
-
171
- #### Configuration
172
-
173
- You can configure the driver with additional options:
174
-
175
- ```ruby
176
- config = {
177
- connection_timeout: 15.seconds,
178
- connection_acquisition_timeout: 1.minute,
179
- max_transaction_retry_time: 30.seconds,
180
- encryption: true,
181
- trust_strategy: :trust_all_certificates
182
- }
183
-
184
- driver = Neo4j::Driver::GraphDatabase.driver(
185
- 'neo4j://localhost:7687',
186
- Neo4j::Driver::AuthTokens.basic('neo4j', 'password'),
187
- **config
188
- )
189
- ```
190
-
191
- #### Connectivity check
192
-
193
- ```ruby
194
- if driver.verify_connectivity
195
- puts "Driver is connected to the database"
196
- else
197
- puts "Driver cannot connect to the database"
198
- end
199
- ```
200
-
201
- ### Sessions & transactions
202
-
203
- The driver provides sessions to interact with the database and to execute queries.
204
-
205
- #### Session
206
-
207
- Sessions are lightweight and disposable database connections. Always close your sessions when done:
208
-
209
- ```ruby
210
- session = driver.session(database: 'neo4j')
211
- begin
212
- session.run('MATCH (n) RETURN n LIMIT 10')
213
- ensure
214
- session.close
77
+ puts node[:name] # => "Alice"
215
78
  end
216
79
  ```
217
80
 
218
- Or use a block that automatically closes the session:
219
-
220
- ```ruby
221
- driver.session(database: 'neo4j') do |session|
222
- session.run('MATCH (n) RETURN n LIMIT 10')
223
- end
224
- ```
225
-
226
- Session options:
227
-
228
- ```ruby
229
- # Default database
230
- session = driver.session
231
-
232
- # Specific database
233
- session = driver.session(database: 'neo4j')
234
-
235
- # With access mode
236
- session = driver.session(database: 'neo4j', default_access_mode: Neo4j::Driver::AccessMode::READ)
237
-
238
- # With bookmarks for causal consistency
239
- session = driver.session(
240
- database: 'neo4j',
241
- bookmarks: [Neo4j::Driver::Bookmark.from('bookmark-1')]
242
- )
243
- ```
244
-
245
- #### Auto-commit transactions
246
-
247
- For simple, one-off queries, use auto-commit transactions:
81
+ `session.run(query, parameters = {}, config = {})` keeps parameters and config
82
+ as separate explicit hashes.
248
83
 
249
- ```ruby
250
- session.run('CREATE (n:Person {name: $name})', name: 'Alice')
251
- ```
84
+ ### Explicit transactions
252
85
 
253
- #### Explicit transactions
254
-
255
- For multiple queries that need to be executed as a unit, use explicit transactions:
86
+ Explicit transactions are **default-rollback** — you must call `tx.commit`.
256
87
 
257
88
  ```ruby
258
- tx = session.begin_transaction
259
- begin
260
- tx.run('CREATE (n:Person {name: $name})', name: 'Alice')
261
- tx.run('CREATE (n:Person {name: $name})', name: 'Bob')
262
- tx.commit
263
- rescue
264
- tx.rollback
265
- raise
89
+ driver.session do |session|
90
+ session.begin_transaction do |tx|
91
+ tx.run('CREATE (:Person {name: "Bob"})')
92
+ tx.run('CREATE (:Person {name: "Charlie"})')
93
+ tx.commit
94
+ end
266
95
  end
267
96
  ```
268
97
 
269
- #### Read transactions
98
+ ### Managed transaction functions
270
99
 
271
- Specifically for read operations:
100
+ Auto-commit on clean exit; transient failures are retried with exponential
101
+ backoff.
272
102
 
273
103
  ```ruby
274
- result = session.read_transaction do |tx|
275
- tx.run('MATCH (n:Person) RETURN n.name').map { |record| record['n.name'] }
276
- end
277
- puts result
278
- ```
279
-
280
- #### Write transactions
281
-
282
- Specifically for write operations:
104
+ driver.session do |session|
105
+ session.execute_write do |tx|
106
+ tx.run('CREATE (n:Person {name: "Diana"}) RETURN n').single
107
+ end
283
108
 
284
- ```ruby
285
- session.write_transaction do |tx|
286
- tx.run('CREATE (n:Person {name: $name})', name: 'Charlie')
109
+ people = session.execute_read do |tx|
110
+ tx.run('MATCH (p:Person) RETURN p.name AS name').collect { |r| r[:name] }
111
+ end
287
112
  end
288
113
  ```
289
114
 
290
115
  ### Working with results
291
116
 
292
117
  ```ruby
293
- result = session.run('MATCH (n:Person) RETURN n.name AS name, n.age AS age')
118
+ result = session.run('MATCH (p:Person) RETURN p.name AS name, p.age AS age')
294
119
 
295
- # Process results
296
- result.each do |record|
297
- puts "#{record['name']} is #{record['age']} years old"
298
- end
120
+ result.each { |record| puts "#{record[:name]} is #{record[:age]}" }
299
121
 
300
- # Check if there are more results
301
- puts "Has more results: #{result.has_next?}"
122
+ record = result.single # exactly one row
123
+ name = record[:name] # by key (string or symbol)
124
+ age = record[1] # or by index
302
125
 
303
- # Get a single record
304
- single = result.single
305
- puts single['name'] if single
306
-
307
- # Get keys available in the result
308
- puts "Keys: #{result.keys}"
309
-
310
- # Access by field index
311
- result.each do |record|
312
- puts "First field: #{record[0]}"
313
- end
314
-
315
- # Convert to array
316
126
  records = result.to_a
317
127
  ```
318
128
 
319
- #### Accessing Node and Relationship data
129
+ ## Architecture
320
130
 
321
- Working with graph entities:
131
+ The development tree is split so shared code lives in one place and each
132
+ implementation adds only its own wire layer:
322
133
 
323
- ```ruby
324
- result = session.run('MATCH (p:Person)-[r:KNOWS]->(friend) RETURN p, r, friend')
325
-
326
- result.each do |record|
327
- # Working with nodes
328
- person = record['p']
329
- puts "Node ID: #{person.id}"
330
- puts "Labels: #{person.labels.join(', ')}"
331
- puts "Properties: #{person.properties}"
332
- puts "Name property: #{person.properties['name']}"
333
-
334
- # Working with relationships
335
- relationship = record['r']
336
- puts "Relationship ID: #{relationship.id}"
337
- puts "Type: #{relationship.type}"
338
- puts "Properties: #{relationship.properties}"
339
-
340
- # Start and end nodes of the relationship
341
- puts "Relationship: #{relationship.start_node_id} -> #{relationship.end_node_id}"
342
- end
343
134
  ```
344
-
345
- #### Working with Paths
346
-
347
- Processing paths returned from Cypher:
348
-
349
- ```ruby
350
- result = session.run('MATCH p = (:Person)-[:KNOWS*]->(:Person) RETURN p')
351
-
352
- result.each do |record|
353
- path = record['p']
354
-
355
- # Get all nodes in the path
356
- nodes = path.nodes
357
- puts "Nodes in path: #{nodes.map { |n| n.properties['name'] }.join(' -> ')}"
358
-
359
- # Get all relationships in the path
360
- relationships = path.relationships
361
- puts "Relationship types: #{relationships.map(&:type).join(', ')}"
362
-
363
- # Iterate through the path segments
364
- path.each do |segment|
365
- puts "#{segment.start_node.properties['name']} -[#{segment.relationship.type}]-> #{segment.end_node.properties['name']}"
366
- end
367
- end
368
- ```
369
-
370
- #### Working with temporal types
371
-
372
- Creating a node with properties of temporal types:
373
-
374
- ```ruby
375
- session.run(
376
- 'CREATE (e:Event {datetime: $datetime, duration: $duration})',
377
- datetime: DateTime.new(2025, 5, 5, 5, 55, 55), duration: 1.hour
378
- )
379
- ```
380
-
381
- Querying temporal values:
382
-
383
- ```ruby
384
- session.run('MATCH (e:Event) LIMIT 1 RETURN e.datetime, e.duration').single.to_h
385
- # => {"e.datetime": 2025-05-05 05:55:55 +0000, "e.duration": 3600 seconds}
386
- ```
387
-
388
- ### Type mapping
389
-
390
- The Neo4j Ruby Driver maps Cypher types to Ruby types:
391
-
392
- | Cypher Type | Ruby Type |
393
- |----------------|-----------------------------------------------|
394
- | null | nil |
395
- | List | Enumerable |
396
- | Map | Hash (symbolized keys) |
397
- | Boolean | TrueClass/FalseClass |
398
- | Integer | Integer/String[^1] |
399
- | Float | Float |
400
- | String | String/Symbol[^2] (encoding: UTF-8) |
401
- | ByteArray | String (encoding: BINARY) |
402
- | Date | Date |
403
- | Zoned Time | Neo4j::Driver::Types::OffsetTime |
404
- | Local Time | Neo4j::Driver::Types::LocalTime |
405
- | Zoned DateTime | Time/ActiveSupport::TimeWithZone/DateTime[^3] |
406
- | Local DateTime | Neo4j::Driver::Types::LocalDateTime |
407
- | Duration | ActiveSupport::Duration |
408
- | Point | Neo4j::Driver::Types::Point |
409
- | Node | Neo4j::Driver::Types::Node |
410
- | Relationship | Neo4j::Driver::Types::Relationship |
411
- | Path | Neo4j::Driver::Types::Path |
412
-
413
- [^1]: An Integer smaller than -2 ** 63 or larger than 2 ** 63 will always be implicitly converted to String
414
- [^2]: A Symbol passed as a parameter will always be implicitly converted to String. All Strings other than BINARY
415
- encoded are converted to UTF-8 when stored in Neo4j
416
- [^3]: A Ruby DateTime passed as a parameter will always be implicitly converted to Time
417
-
418
- ### Advanced
419
-
420
- #### Connection pooling
421
-
422
- The driver handles connection pooling automatically. Configure the connection pool:
423
-
424
- ```ruby
425
- config = {
426
- max_connection_pool_size: 100,
427
- max_connection_lifetime: 1.hour
428
- }
429
-
430
- driver = Neo4j::Driver::GraphDatabase.driver('neo4j://localhost:7687', auth, **config)
135
+ lib/
136
+ ├── shared/ # public API + shared types, loaded by both implementations
137
+ ├── mri/ # pure-Ruby Bolt protocol, PackStream, connection pool
138
+ └── jruby/ # thin wrapper over the official Java driver jars
431
139
  ```
432
140
 
433
- #### Logging
141
+ The published gem is flattened to a single `lib/` for the platform via a staged
142
+ build (see `JRUBY.md`). See `CLAUDE.md` for the layout and conventions,
143
+ `DEVELOPMENT.md` for the dev loop, and `DECISIONS.md` for architectural history.
434
144
 
435
- Configure logging for the driver:
145
+ ### Dependencies
436
146
 
437
- ```ruby
438
- config = {
439
- logger: Logger.new(STDOUT).tap { |log| log.level = Logger::DEBUG }
440
- }
441
-
442
- driver = Neo4j::Driver::GraphDatabase.driver('neo4j://localhost:7687', auth, **config)
443
- ```
444
-
445
- ## For Driver Engineers
147
+ - **MRI**: `tzinfo`, `zeitwerk`, `connection_pool` — no Java, no server-side
148
+ components.
149
+ - **JRuby**: the official `neo4j-java-driver` jars, resolved by
150
+ `jar-dependencies`; runs on a JVM (Java 17+).
446
151
 
447
- This gem includes 2 different implementations: a Java driver wrapper and a pure Ruby driver, so you will have to run
448
- this command every time you switch the Ruby engine:
152
+ ## Testing
449
153
 
450
154
  ```bash
451
- bin/setup
452
- ```
453
-
454
- ### Testing
455
-
456
- There are two sets of tests for the driver. To run the specs placed in this repository, use a normal rspec command:
155
+ export TEST_NEO4J_URL=bolt://localhost:7687
156
+ export TEST_NEO4J_USER=neo4j
157
+ export TEST_NEO4J_PASS=password
457
158
 
458
- ```bash
459
- rspec spec
159
+ bundle exec rspec
460
160
  ```
461
161
 
462
- To run the [Testkit](https://github.com/neo4j-drivers/testkit) that is used to test all Neo4j driver implementations,
463
- use the following:
162
+ - `spec/shared/integration/` end-to-end against a running Neo4j instance
163
+ - `spec/shared/neo4j/driver/` — public-API unit tests
164
+ - `spec/mri/`, `spec/jruby/` — implementation-specific tests
464
165
 
465
- ```bash
466
- git clone git@github.com:neo4j-drivers/testkit.git
467
- cd testkit
468
- export TEST_DRIVER_NAME=ruby
469
- export TEST_DRIVER_REPO=`realpath ../neo4j-ruby-driver`
470
- export TEST_NEO4J_PASS=password
471
- python3 main.py --tests UNIT_TESTS --configs 4.3-enterprise
472
- ```
473
-
474
- Please refer to the [Testkit](https://github.com/neo4j-drivers/testkit) documentation to learn more about its features.
166
+ Conformance is additionally exercised through the Neo4j
167
+ [testkit](https://github.com/neo4j-drivers/testkit) suite via the Ruby backend
168
+ under `testkit-backend/`.
475
169
 
476
170
  ## Contributing
477
171
 
478
- Suggestions, improvements, bug reports and pull requests are welcome on GitHub
479
- at https://github.com/neo4jrb/neo4j-ruby-driver.
172
+ Contributions are welcome. Keep the public API flavour-agnostic (no
173
+ implementation type may leak across it), follow the conventions in `CLAUDE.md`,
174
+ and add coverage on both implementations. See `CHANGELOG.md` for recent changes.
175
+
176
+ ## References
177
+
178
+ - [Bolt Protocol Specification](https://neo4j.com/docs/bolt/current/)
179
+ - [PackStream Specification](https://neo4j.com/docs/bolt/current/packstream/)
180
+ - [neo4j-java-driver](https://github.com/neo4j/neo4j-java-driver) (reference implementation)
181
+ - [Neo4j testkit](https://github.com/neo4j-drivers/testkit)
480
182
 
481
183
  ## License
482
184
 
483
- The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
185
+ Released under the [MIT License](LICENSE.txt).
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Neo4j
4
+ module Driver
5
+ # JRuby flavour of the auth-token manager. Same Proc-based public
6
+ # surface as the MRI sibling, but the class `include`s
7
+ # `org.neo4j.driver.AuthTokenManager` in its body — JRuby only
8
+ # generates the Java-side proxy at class-definition time, so a
9
+ # retroactive include (via prepend or class_eval) doesn't produce
10
+ # a usable interface impl. Subclasses inherit the interface, so a
11
+ # user-written `class MyManager < Neo4j::Driver::AuthTokenManager`
12
+ # slots into the Java driver the same as on MRI.
13
+ #
14
+ # The Java interface asks for `CompletionStage<AuthToken>` from
15
+ # `getToken`; our sync Proc gets wrapped in a pre-completed future.
16
+ # The `handleSecurityException` callback receives a Java exception
17
+ # which we map to its Ruby counterpart before handing to the user.
18
+ class AuthTokenManager < Internal::InternalAuthTokenManager
19
+ include Java::OrgNeo4jDriver::AuthTokenManager
20
+ include Ext::ExceptionMapper
21
+
22
+ def get_token = java.util.concurrent.CompletableFuture.completed_future(super)
23
+
24
+ def handle_security_exception(token, exception)
25
+ super(token, mapped_exception_with_cause(exception))
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Neo4j
4
+ module Driver
5
+ # JRuby flavour of a client-certificate manager: supply a block that
6
+ # returns the current client certificate — or `nil` when nothing has
7
+ # changed since the last call — and pass an instance to
8
+ # `GraphDatabase.driver` via `client_certificate_manager:`. The driver
9
+ # calls back whenever it needs a (possibly rotated) certificate for the
10
+ # mutual-TLS handshake.
11
+ #
12
+ # The class `include`s org.neo4j.driver.ClientCertificateManager in its
13
+ # body because JRuby only builds the Java interface proxy at
14
+ # class-definition time; a retroactive include wouldn't produce a usable
15
+ # impl, and subclasses inherit the interface. The Java SPI asks for a
16
+ # CompletionStage<ClientCertificate>, so the synchronous block result is
17
+ # wrapped in a pre-completed future (a `nil` result is a valid "no
18
+ # change" answer).
19
+ class ClientCertificateManager
20
+ include Java::OrgNeo4jDriver::ClientCertificateManager
21
+
22
+ def initialize(&get_client_certificate)
23
+ @get_client_certificate = get_client_certificate
24
+ end
25
+
26
+ def get_client_certificate
27
+ java.util.concurrent.CompletableFuture.completed_future(@get_client_certificate.call)
28
+ end
29
+ end
30
+ end
31
+ end