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.
- checksums.yaml +4 -4
- data/README.md +113 -411
- data/lib/neo4j/driver/auth_token_manager.rb +29 -0
- data/lib/neo4j/driver/client_certificate_manager.rb +31 -0
- data/lib/neo4j/driver/exceptions/neo4j_exception.rb +16 -7
- data/lib/neo4j/driver/exceptions/no_such_record_exception.rb +1 -1
- data/lib/neo4j/driver/exceptions/protocol_exception.rb +0 -5
- data/lib/neo4j/driver/exceptions/security_retryable_exception.rb +14 -0
- data/lib/neo4j/driver/exceptions/transaction_terminated_exception.rb +13 -0
- data/lib/neo4j/driver/ext/auth_token.rb +17 -0
- data/{jruby → lib}/neo4j/driver/ext/auth_tokens.rb +5 -1
- data/lib/neo4j/driver/ext/bookmark_managers.rb +21 -0
- data/lib/neo4j/driver/ext/client_certificates.rb +21 -0
- data/{jruby → lib}/neo4j/driver/ext/config_converter.rb +36 -7
- data/{jruby → lib}/neo4j/driver/ext/exception_checkable.rb +3 -3
- data/lib/neo4j/driver/ext/exception_mapper.rb +173 -0
- data/lib/neo4j/driver/ext/graph_database.rb +25 -0
- data/lib/neo4j/driver/ext/internal/clock_adapter.rb +33 -0
- data/lib/neo4j/driver/ext/internal/cluster/routing_table_registry_impl.rb +28 -0
- data/lib/neo4j/driver/ext/internal/driver_factory.rb +54 -0
- data/lib/neo4j/driver/ext/internal/internal_notification_severity.rb +15 -0
- data/lib/neo4j/driver/ext/internal/reflection.rb +22 -0
- data/lib/neo4j/driver/ext/internal/summary/internal_gql_notification.rb +22 -0
- data/lib/neo4j/driver/ext/internal/summary/internal_gql_status_object.rb +18 -0
- data/{jruby → lib}/neo4j/driver/ext/internal/summary/internal_plan.rb +3 -5
- data/lib/neo4j/driver/ext/internal/summary/internal_query_profile.rb +36 -0
- data/lib/neo4j/driver/ext/internal/summary/internal_result_summary.rb +44 -0
- data/lib/neo4j/driver/ext/internal_driver.rb +102 -0
- data/{jruby → lib}/neo4j/driver/ext/internal_result.rb +2 -2
- data/{jruby → lib}/neo4j/driver/ext/internal_session.rb +5 -1
- data/lib/neo4j/driver/ext/logger.rb +55 -0
- data/lib/neo4j/driver/ext/map_converter.rb +13 -0
- data/lib/neo4j/driver/ext/neo_converter.rb +70 -0
- data/lib/neo4j/driver/ext/plain_map_converter.rb +11 -0
- data/{jruby → lib}/neo4j/driver/ext/ruby_converter.rb +20 -6
- data/lib/neo4j/driver/ext/unsupported_type.rb +14 -0
- data/lib/neo4j/driver/internal/deprecator.rb +9 -5
- data/lib/neo4j/driver/internal/duration_normalizer.rb +14 -39
- data/lib/neo4j/driver/internal/internal_auth_token_manager.rb +37 -0
- data/lib/neo4j/driver/internal/validator.rb +16 -2
- data/lib/neo4j/driver/summary/query_type.rb +5 -1
- data/lib/neo4j/driver/types/duration.rb +76 -0
- data/lib/neo4j/driver/types/local_date_time.rb +77 -8
- data/lib/neo4j/driver/types/local_time.rb +50 -7
- data/lib/neo4j/driver/types/offset_time.rb +78 -7
- data/lib/neo4j/driver/types/point.rb +38 -18
- data/lib/neo4j/driver/types/temporal_value.rb +44 -0
- data/lib/neo4j/driver/types/unresolvable_zoned_date_time.rb +38 -0
- data/{jruby → lib}/neo4j/driver/version.rb +1 -1
- data/{jruby → lib}/neo4j/driver.rb +31 -7
- data/lib/neo4j-ruby-driver_jars.rb +14 -0
- data/lib/neo4j-ruby-driver_loader.rb +34 -20
- data/lib/org/neo4j/driver/neo4j-java-driver-all/6.2.1/neo4j-java-driver-all-6.2.1.jar +0 -0
- data/lib/org/neo4j/driver/neo4j-java-driver-observation-metrics/6.2.1/neo4j-java-driver-observation-metrics-6.2.1.jar +0 -0
- data/lib/org/reactivestreams/reactive-streams/1.0.4/reactive-streams-1.0.4.jar +0 -0
- metadata +76 -226
- data/jruby/neo4j/driver/ext/async_converter.rb +0 -55
- data/jruby/neo4j/driver/ext/exception_mapper.rb +0 -92
- data/jruby/neo4j/driver/ext/graph_database.rb +0 -33
- data/jruby/neo4j/driver/ext/internal/async/internal_async_session.rb +0 -23
- data/jruby/neo4j/driver/ext/internal/cluster/routing_table_registry_impl.rb +0 -15
- data/jruby/neo4j/driver/ext/internal/cursor/disposable_async_result_cursor.rb +0 -15
- data/jruby/neo4j/driver/ext/internal/driver_factory.rb +0 -24
- data/jruby/neo4j/driver/ext/internal/summary/internal_result_summary.rb +0 -34
- data/jruby/neo4j/driver/ext/internal_driver.rb +0 -52
- data/jruby/neo4j/driver/ext/logger.rb +0 -60
- data/jruby/neo4j/driver/ext/map_converter.rb +0 -14
- data/jruby/neo4j/driver/ext/neo_converter.rb +0 -59
- data/lib/neo4j/driver/internal/bolt_server_address.rb +0 -97
- data/lib/neo4j/driver/synchronizable.rb +0 -23
- data/lib/neo4j/driver/types/time.rb +0 -45
- /data/{jruby → lib}/neo4j/driver/ext/internal/abstract_query_runner.rb +0 -0
- /data/{jruby → lib}/neo4j/driver/ext/internal/eager_result_value.rb +0 -0
- /data/{jruby → lib}/neo4j/driver/ext/internal/metrics/internal_connection_pool_metrics.rb +0 -0
- /data/{jruby → lib}/neo4j/driver/ext/internal/summary/internal_notification.rb +0 -0
- /data/{jruby → lib}/neo4j/driver/ext/internal_entity.rb +0 -0
- /data/{jruby → lib}/neo4j/driver/ext/internal_keys.rb +0 -0
- /data/{jruby → lib}/neo4j/driver/ext/internal_node.rb +0 -0
- /data/{jruby → lib}/neo4j/driver/ext/internal_record.rb +0 -0
- /data/{jruby → lib}/neo4j/driver/ext/internal_relationship.rb +0 -0
- /data/{jruby → lib}/neo4j/driver/ext/internal_transaction.rb +0 -0
- /data/{jruby → lib}/neo4j/driver/ext/query.rb +0 -0
- /data/{jruby → lib}/neo4j/driver/ext/run_override.rb +0 -0
- /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:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8c23247c73132c7a801d9c4236df0e5ec06f7f89ca62e6a4bb5b5613a9ba0ca9
|
|
4
|
+
data.tar.gz: ad8bff687e3c0429cc26cc4c3663a7fcceb63f9c32c362dcb9a2cc968d416b38
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
-
|
|
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
|
-
###
|
|
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
|
|
94
|
-
|
|
95
|
-
puts
|
|
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
|
-
|
|
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
|
-
|
|
68
|
+
### Parameterized queries
|
|
150
69
|
|
|
151
70
|
```ruby
|
|
152
|
-
|
|
153
|
-
|
|
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
|
-
#
|
|
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
|
-
|
|
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
|
-
|
|
250
|
-
session.run('CREATE (n:Person {name: $name})', name: 'Alice')
|
|
251
|
-
```
|
|
84
|
+
### Explicit transactions
|
|
252
85
|
|
|
253
|
-
|
|
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
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
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
|
-
|
|
98
|
+
### Managed transaction functions
|
|
270
99
|
|
|
271
|
-
|
|
100
|
+
Auto-commit on clean exit; transient failures are retried with exponential
|
|
101
|
+
backoff.
|
|
272
102
|
|
|
273
103
|
```ruby
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
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
|
-
|
|
285
|
-
|
|
286
|
-
|
|
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 (
|
|
118
|
+
result = session.run('MATCH (p:Person) RETURN p.name AS name, p.age AS age')
|
|
294
119
|
|
|
295
|
-
#
|
|
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
|
-
|
|
301
|
-
|
|
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
|
-
|
|
129
|
+
## Architecture
|
|
320
130
|
|
|
321
|
-
|
|
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
|
-
|
|
346
|
-
|
|
347
|
-
|
|
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
|
-
|
|
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
|
-
|
|
145
|
+
### Dependencies
|
|
436
146
|
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
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
|
-
|
|
448
|
-
this command every time you switch the Ruby engine:
|
|
152
|
+
## Testing
|
|
449
153
|
|
|
450
154
|
```bash
|
|
451
|
-
|
|
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
|
-
|
|
459
|
-
rspec spec
|
|
159
|
+
bundle exec rspec
|
|
460
160
|
```
|
|
461
161
|
|
|
462
|
-
|
|
463
|
-
|
|
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
|
-
|
|
466
|
-
|
|
467
|
-
|
|
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
|
-
|
|
479
|
-
|
|
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
|
-
|
|
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
|