neo4j_bolt 0.4.4 → 0.4.5
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 +15 -10
- data/bin/neo4j_bolt +6 -3
- data/lib/neo4j_bolt/version.rb +1 -1
- data/lib/neo4j_bolt.rb +47 -4
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: cc5ccd396277de9c75f73bd338f89a2af476475940d53e0399cfbbeaa9589dc6
|
|
4
|
+
data.tar.gz: 90a393bb3f4819245613ca34206d24932487de5af4959b27e26232137766e15f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e4f8c0aa87b72f4081d19476dbb684ced5fed6851b729f7db4bb7d8b06f7e41f1607581498b1195043edc63433fe91b8ca7aadf0c58c3d8c7f14bf79dcae1395
|
|
7
|
+
data.tar.gz: '028fc3fc677e0a8d83f6f5554d851ff29b1ab7bf6b0c059ea646f18062f646e4c43554f612bb6c69055658af56e4f08bda30c5756b57899475b98178b91d6e87'
|
data/README.md
CHANGED
|
@@ -4,14 +4,14 @@ Neo4jBolt 0.4 is a small compatibility and convenience layer for Ruby applicatio
|
|
|
4
4
|
|
|
5
5
|
Neo4jBolt no longer implements the Bolt wire protocol itself.
|
|
6
6
|
|
|
7
|
-
`0.4.
|
|
7
|
+
`0.4.5` requires Ruby 3.4 or newer and pins `neo4j-ruby-driver` to `6.2.1.beta.4`. Applications on older Rubies can remain on the Neo4jBolt 0.3.x line; this prerelease is intentionally not an automatic upgrade for them.
|
|
8
8
|
|
|
9
9
|
## Installation
|
|
10
10
|
|
|
11
11
|
For this prerelease, specify the version explicitly:
|
|
12
12
|
|
|
13
13
|
```ruby
|
|
14
|
-
gem "neo4j_bolt", "0.4.
|
|
14
|
+
gem "neo4j_bolt", "0.4.5"
|
|
15
15
|
```
|
|
16
16
|
|
|
17
17
|
Then run `bundle install`. A running Neo4j database is required.
|
|
@@ -26,17 +26,20 @@ Neo4jBolt.bolt_port = 7687
|
|
|
26
26
|
Neo4jBolt.bolt_verbosity = 0
|
|
27
27
|
```
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
The connection endpoint, authentication, and database selection can use the standard Neo4j client environment variables:
|
|
30
30
|
|
|
31
31
|
```bash
|
|
32
|
+
export NEO4J_URI=neo4j://localhost:7687
|
|
32
33
|
export NEO4J_USERNAME=neo4j
|
|
33
34
|
export NEO4J_PASSWORD=secret
|
|
34
35
|
export NEO4J_DATABASE=neo4j
|
|
35
36
|
```
|
|
36
37
|
|
|
37
|
-
They are read when `neo4j_bolt` is loaded. Explicit Ruby configuration overrides those defaults:
|
|
38
|
+
They are read when `neo4j_bolt` is loaded. `NEO4J_URI` accepts `neo4j://HOST[:PORT]` and `bolt://HOST[:PORT]` endpoint notation; Neo4jBolt uses the host and port while retaining its existing direct Bolt connection behavior. The default port is 7687. Explicit Ruby configuration overrides those defaults:
|
|
38
39
|
|
|
39
40
|
```ruby
|
|
41
|
+
Neo4jBolt.bolt_host = "localhost"
|
|
42
|
+
Neo4jBolt.bolt_port = 7687
|
|
40
43
|
Neo4jBolt.bolt_username = "neo4j"
|
|
41
44
|
Neo4jBolt.bolt_password = "secret"
|
|
42
45
|
Neo4jBolt.bolt_database = "movies"
|
|
@@ -186,7 +189,7 @@ neo4j_bolt load --color bright-blue database.dump
|
|
|
186
189
|
|
|
187
190
|
The `0, 1, 2, ...` IDs are synthetic dump-local IDs. Database-internal IDs and element IDs are never written to the persistent format. Dumping keeps the count, node, and relationship reads in one transaction, so the driver-provided entity identity used to connect the two streamed result sets stays within Neo4j's transaction-scoped identity guarantee. To keep dump numbering deterministic, ordering uses `elementId()` on modern Neo4j and an internal `id()` fallback only on Neo4j 4.4, where `elementId()` does not exist. Old Neo4jBolt dumps remain loadable.
|
|
188
191
|
|
|
189
|
-
For relational loads, the adapter assigns a random temporary label and
|
|
192
|
+
For relational loads, the adapter assigns a random temporary label and the fixed reserved property `__neo4j_bolt_load_id` to imported nodes. After the nodes are committed it builds a temporary `neo4j_bolt_` index on that property, uses indexed lookups while creating relationships, drops the index, and removes the temporary metadata in batches. The fixed property key is reused by every load instead of registering a new random property-key token each time. Neo4j can keep the property key registered after all values have been removed, so it may remain visible in Browser's property-key list; no imported node retains the property after successful cleanup. Relational loads refuse to run if the dump itself, or existing data during `force_append`, currently uses the reserved property. No database-internal identity is carried from one load transaction to another. Loading a relational dump therefore requires permission to create and drop an index.
|
|
190
193
|
|
|
191
194
|
Loads start with batches of 5,000 records by default. This is an initial ceiling rather than a claimed optimum: if Neo4j returns a transaction-memory/resource error whose server semantics guarantee rollback, the loader halves the failed batch and retries it, then keeps the smaller size for the rest of that phase. Node and relationship phases adapt independently. Other errors are not retried, because a generic connection failure cannot safely prove that a `CREATE` transaction did not commit. Callers can change the initial ceiling with `initial_batch_size:`; the CLI exposes the same setting as `--batch-size`.
|
|
192
195
|
|
|
@@ -206,7 +209,7 @@ The `neo4j_bolt` executable retains these commands:
|
|
|
206
209
|
| `neo4j_bolt index rm --force` | Remove all constraints and indexes |
|
|
207
210
|
| `neo4j_bolt visualize` | Generate a GraphViz document |
|
|
208
211
|
|
|
209
|
-
Use `--host HOST:PORT`, `--username USER`, `--password PASSWORD`, and `--database DATABASE` to override connection settings.
|
|
212
|
+
Use `--host HOST:PORT`, `--username USER`, `--password PASSWORD`, and `--database DATABASE` to override connection settings. Host and port otherwise inherit `NEO4J_URI` when present, falling back to `localhost:7687`; username, password, and database inherit `NEO4J_USERNAME`, `NEO4J_PASSWORD`, and `NEO4J_DATABASE`. Prefer the password environment variable for routine use because command-line arguments may be visible in shell history or process listings. `gli` remains a runtime dependency for the CLI, and `pry` remains for the separate `bin/console` executable.
|
|
210
213
|
|
|
211
214
|
## Tested Neo4j versions
|
|
212
215
|
|
|
@@ -216,7 +219,7 @@ The same complete integration suite is run against these exact Community images:
|
|
|
216
219
|
- `neo4j:5.26.28-community`
|
|
217
220
|
- `neo4j:2026.06.0-community`
|
|
218
221
|
|
|
219
|
-
No compatibility beyond this matrix is claimed for `0.4.
|
|
222
|
+
No compatibility beyond this matrix is claimed for `0.4.5`.
|
|
220
223
|
|
|
221
224
|
Run one modern LTS target:
|
|
222
225
|
|
|
@@ -254,13 +257,15 @@ To point RSpec itself at an already disposable database, set `NEO4J_BOLT_TEST_HO
|
|
|
254
257
|
| CLI commands | Preserved |
|
|
255
258
|
| `BoltSocket`, `BoltBuffer`, protocol markers/state/parser/packer | Intentionally removed private implementation details |
|
|
256
259
|
|
|
257
|
-
## 0.4.
|
|
260
|
+
## 0.4.5 migration notes
|
|
258
261
|
|
|
259
262
|
- Ruby 3.4 or newer is required; Ruby 2.x/3.0–3.3 applications should stay on 0.3.x until upgraded.
|
|
260
263
|
- The exact prerelease upstream dependency is pinned while no stable `neo4j-ruby-driver` 6.2.x exists.
|
|
261
264
|
- Connections are pooled and safe for concurrent use instead of one mutable socket per including object.
|
|
262
|
-
-
|
|
263
|
-
-
|
|
265
|
+
- `NEO4J_URI` now supplies the default host and port for `bolt://` and `neo4j://` endpoint notation; an explicit CLI `--host` or Ruby `bolt_host`/`bolt_port` setting overrides it.
|
|
266
|
+
- Basic authentication and database selection are exposed through `bolt_username`, `bolt_password`, and `bolt_database`, with `NEO4J_USERNAME`, `NEO4J_PASSWORD`, and `NEO4J_DATABASE` as environment defaults.
|
|
267
|
+
- TLS, routing behavior, URI query parameters, and other upstream-driver configuration are still not exposed through the compatibility API.
|
|
268
|
+
- Relational loads reuse one reserved `__neo4j_bolt_load_id` property key while retaining the existing indexed lookup algorithm, preventing a new property-key token from being registered on every load.
|
|
264
269
|
- `BoltSocket`, `BoltBuffer`, `ServerState`, `BoltMarker`, `UnexpectedServerResponse`, `State`, and `CypherError` were undocumented wire internals and are removed.
|
|
265
270
|
|
|
266
271
|
## Development
|
data/bin/neo4j_bolt
CHANGED
|
@@ -21,15 +21,18 @@ class App
|
|
|
21
21
|
version Neo4jBolt::VERSION
|
|
22
22
|
|
|
23
23
|
flag [:v, :verbosity], :default_value => 0
|
|
24
|
-
flag [:h, :host], :default_value =>
|
|
24
|
+
flag [:h, :host], :default_value => nil,
|
|
25
|
+
:desc => 'Neo4j host:port (default: NEO4J_URI or localhost:7687)'
|
|
25
26
|
flag [:u, :username], :default_value => nil, :desc => 'Neo4j username (default: NEO4J_USERNAME)'
|
|
26
27
|
flag [:p, :password], :default_value => nil, :desc => 'Neo4j password (default: NEO4J_PASSWORD)'
|
|
27
28
|
flag [:d, :database], :default_value => nil, :desc => 'Neo4j database (default: NEO4J_DATABASE)'
|
|
28
29
|
|
|
29
30
|
pre do |global_options, command, options, args|
|
|
30
31
|
host = global_options[:host]
|
|
31
|
-
|
|
32
|
-
|
|
32
|
+
unless host.nil?
|
|
33
|
+
Neo4jBolt.bolt_host = host.split(':').first
|
|
34
|
+
Neo4jBolt.bolt_port = host.split(':').last.to_i
|
|
35
|
+
end
|
|
33
36
|
Neo4jBolt.bolt_verbosity = global_options[:verbosity].to_i
|
|
34
37
|
Neo4jBolt.bolt_username = global_options[:username] unless global_options[:username].nil?
|
|
35
38
|
Neo4jBolt.bolt_password = global_options[:password] unless global_options[:password].nil?
|
data/lib/neo4j_bolt/version.rb
CHANGED
data/lib/neo4j_bolt.rb
CHANGED
|
@@ -4,6 +4,7 @@ require "json"
|
|
|
4
4
|
require "securerandom"
|
|
5
5
|
require "set"
|
|
6
6
|
require "thread"
|
|
7
|
+
require "uri"
|
|
7
8
|
require "neo4j/driver"
|
|
8
9
|
require "neo4j_bolt/version"
|
|
9
10
|
|
|
@@ -18,8 +19,9 @@ module Neo4jBolt
|
|
|
18
19
|
LOAD_MEMORY_GQL_STATUSES = %w[51N72 51N73].freeze
|
|
19
20
|
LOAD_PROGRESS_STEP = 5_000
|
|
20
21
|
DUMP_PROGRESS_STEP = 1_000
|
|
22
|
+
LOAD_ID_PROPERTY = "__neo4j_bolt_load_id"
|
|
21
23
|
private_constant :LOAD_MEMORY_ERROR_CODES, :LOAD_MEMORY_GQL_STATUSES,
|
|
22
|
-
:LOAD_PROGRESS_STEP, :DUMP_PROGRESS_STEP
|
|
24
|
+
:LOAD_PROGRESS_STEP, :DUMP_PROGRESS_STEP, :LOAD_ID_PROPERTY
|
|
23
25
|
|
|
24
26
|
class ProgressReporter
|
|
25
27
|
UPDATE_INTERVAL = 0.5
|
|
@@ -396,8 +398,29 @@ module Neo4jBolt
|
|
|
396
398
|
end
|
|
397
399
|
end
|
|
398
400
|
|
|
399
|
-
|
|
400
|
-
|
|
401
|
+
neo4j_uri = ENV["NEO4J_URI"]
|
|
402
|
+
if neo4j_uri.nil? || neo4j_uri.empty?
|
|
403
|
+
self.bolt_host = "localhost"
|
|
404
|
+
self.bolt_port = 7687
|
|
405
|
+
else
|
|
406
|
+
begin
|
|
407
|
+
parsed_neo4j_uri = URI.parse(neo4j_uri)
|
|
408
|
+
rescue URI::InvalidURIError => error
|
|
409
|
+
raise Error, "Invalid NEO4J_URI: #{error.message}"
|
|
410
|
+
end
|
|
411
|
+
|
|
412
|
+
unless %w[bolt neo4j].include?(parsed_neo4j_uri.scheme) && parsed_neo4j_uri.host
|
|
413
|
+
raise Error, "NEO4J_URI must be a bolt:// or neo4j:// URI with a host"
|
|
414
|
+
end
|
|
415
|
+
unless parsed_neo4j_uri.userinfo.nil? &&
|
|
416
|
+
["", "/"].include?(parsed_neo4j_uri.path.to_s) &&
|
|
417
|
+
parsed_neo4j_uri.query.nil? && parsed_neo4j_uri.fragment.nil?
|
|
418
|
+
raise Error, "NEO4J_URI may only contain scheme, host, and port"
|
|
419
|
+
end
|
|
420
|
+
|
|
421
|
+
self.bolt_host = parsed_neo4j_uri.host
|
|
422
|
+
self.bolt_port = parsed_neo4j_uri.port || 7687
|
|
423
|
+
end
|
|
401
424
|
self.bolt_verbosity = 0
|
|
402
425
|
self.bolt_username = ENV["NEO4J_USERNAME"]
|
|
403
426
|
self.bolt_password = ENV["NEO4J_PASSWORD"]
|
|
@@ -650,12 +673,32 @@ module Neo4jBolt
|
|
|
650
673
|
|
|
651
674
|
temporary_token = SecureRandom.hex(12)
|
|
652
675
|
temporary_label = "__neo4j_bolt_load_#{temporary_token}"
|
|
653
|
-
temporary_id_property =
|
|
676
|
+
temporary_id_property = LOAD_ID_PROPERTY
|
|
654
677
|
temporary_index = "neo4j_bolt_load_#{temporary_token}"
|
|
655
678
|
quoted_temporary_label = quote_identifier(temporary_label)
|
|
656
679
|
quoted_temporary_id = quote_identifier(temporary_id_property)
|
|
657
680
|
quoted_temporary_index = quote_identifier(temporary_index)
|
|
658
681
|
needs_relationship_lookup = total_relationships.positive?
|
|
682
|
+
|
|
683
|
+
if needs_relationship_lookup
|
|
684
|
+
dump_uses_reserved_property = node_batches.each_value.any? do |nodes|
|
|
685
|
+
nodes.any? { |node| node.fetch("properties", {}).key?(LOAD_ID_PROPERTY) }
|
|
686
|
+
end
|
|
687
|
+
if dump_uses_reserved_property
|
|
688
|
+
raise Error, "Dump contains reserved Neo4jBolt load property #{LOAD_ID_PROPERTY.inspect}."
|
|
689
|
+
end
|
|
690
|
+
|
|
691
|
+
if force_append
|
|
692
|
+
reserved_property_in_use = neo4j_query(
|
|
693
|
+
"MATCH (n) WHERE n.#{quoted_temporary_id} IS NOT NULL RETURN 1 AS present LIMIT 1"
|
|
694
|
+
).any?
|
|
695
|
+
if reserved_property_in_use
|
|
696
|
+
raise Error,
|
|
697
|
+
"Database contains reserved Neo4jBolt load property #{LOAD_ID_PROPERTY.inspect}."
|
|
698
|
+
end
|
|
699
|
+
end
|
|
700
|
+
end
|
|
701
|
+
|
|
659
702
|
loaded_nodes = 0
|
|
660
703
|
loaded_relationships = 0
|
|
661
704
|
index_created = false
|