neo4j_bolt 0.2.1 → 0.4.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fb231b117247cda9063d3d8c7ee9922ebef33a345291a8ea7261ec51a903fe4f
4
- data.tar.gz: f269e02fe8e439f404d211767224866a9d623356c893befa682d19eee31030fe
3
+ metadata.gz: a0582f714d9d1c8793edb70481db6a2ff5548200b14ad55c1f44b0859270b7d9
4
+ data.tar.gz: 73e300f77c941909738411fdbd9ce6b72ea6ef73a72a10184395e2228ce6a52c
5
5
  SHA512:
6
- metadata.gz: dd7b2031de0005b704fef01043f9ca393df3f8d4a79b07d879d348a17cc9b3eeb018b2282f25a31a9e0c691e47280fadde04bc21fc3390253c3f092d16807a2c
7
- data.tar.gz: 698bbc17d5acd365458ada7feea3d802def624bc2052652f34c1c13e75d6d8b3ebd67b20210ce9300b5d0d8aa278f66f0bc514710f5f52119d2ad9db2e8aa5b5
6
+ metadata.gz: 8e5debab26d8b655b1fa3fc1e4c877308e8221345aec76844d2b423ef4fe3061bc535241566f9af63ba889274b944c81efbad9daeadf1cc9b0ed166c82a18417
7
+ data.tar.gz: 65ed32647603bdeaf9bbca19335a0ca6fdccf5b17f3aaa11e19ef48868b242731a34b3cba74ff9475dbd5db94e6bf15f44f60110dca5adb8fe83d9709ef45a3d
data/README.md CHANGED
@@ -1,196 +1,235 @@
1
1
  # Neo4jBolt
2
2
 
3
- A Neo4j/Bolt driver written in pure Ruby. **Currently only supporting Neo4j 4.4 with Bolt 4.4.** Contains a CLI tool which can dump databases, load database dumps, and visualize database contents.
4
-
5
- Caution: This gem is not feature complete regarding Neo4j and Bolt. Nevertheless, it is used successfully in production – make sure it supports the features you need.
6
-
7
- - PackStream:
8
- - data types:
9
- - supported: Null, Boolean, Integer, Float, String, List, Dictionary, Structure
10
- - not supported: Bytes
11
- - structures:
12
- - supported: Node, Relationship
13
- - not supported: UnboundRelationship, Path, Date, Time, LocalTime, DateTime, DateTimeZoneId, LocalDateTime, Duration, Point2D, Point3D
14
- - Bolt Protocol:
15
- - supported: transactions
16
- - not supported:
17
- - auto transactions (all transactions are explicit in Neo4jBolt)
18
- - routing
19
- - interrupt
3
+ Neo4jBolt 0.4 is a small compatibility and convenience layer for Ruby applications, backed by [neo4j-ruby-driver](https://github.com/neo4jrb/neo4j-ruby-driver). It preserves the straightforward `Neo4jBolt` application API while delegating connections, pooling, Bolt negotiation, protocol state, PackStream, reconnects, and modern Neo4j value support to the upstream driver.
4
+
5
+ Neo4jBolt no longer implements the Bolt wire protocol itself.
6
+
7
+ `0.4.0.pre1` 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.
20
8
 
21
9
  ## Installation
22
10
 
23
- Add this line to your application's Gemfile:
11
+ For this prerelease, specify the version explicitly:
24
12
 
25
13
  ```ruby
26
- gem 'neo4j_bolt'
14
+ gem "neo4j_bolt", "0.4.0.pre1"
27
15
  ```
28
16
 
29
- And then execute:
30
-
31
- $ bundle install
17
+ Then run `bundle install`. A running Neo4j database is required.
32
18
 
33
- Or install it yourself as:
19
+ ## Connecting
34
20
 
35
- $ gem install neo4j_bolt
21
+ The existing host, port, and verbosity settings remain available:
36
22
 
37
- In order to use this gem, you need a running Neo4j database. Chances are you already have a Neo4j database running if you're reading this. Otherwise, you can start one via Docker using the following command:
38
-
39
- ```
40
- docker run --rm --env NEO4J_AUTH=none --publish 7687:7687 neo4j:4.4-community
23
+ ```ruby
24
+ Neo4jBolt.bolt_host = "localhost"
25
+ Neo4jBolt.bolt_port = 7687
26
+ Neo4jBolt.bolt_verbosity = 0
41
27
  ```
42
28
 
43
- If you want the Browser interface at http://localhost:7474/, additionaly specify `--publish 7474:7474`.
44
-
45
- ## Connecting to a Neo4j database
46
-
47
- Specify your Bolt host and port (if you omit this it will be localhost:7687 by default):
29
+ Applications can include the module as before:
48
30
 
49
31
  ```ruby
50
- Neo4jBolt.bolt_host = 'localhost'
51
- Neo4jBolt.bolt_port = 7687
32
+ include Neo4jBolt
52
33
  ```
53
34
 
54
- Use `Neo4jBolt::cleanup_neo4j` to disconnect (this is important when running a web app it might be a good idea to close a socket once we're done with it so we don't run out of available ports).
35
+ They can also extend another object or call the convenience methods directly on `Neo4jBolt`.
36
+
37
+ One lazily initialized upstream driver owns a thread-safe connection pool. Each standalone query uses a short-lived session and returns its connection to that pool after its result has been consumed. Queries from several Puma threads are not globally serialized.
38
+
39
+ `cleanup_neo4j` safely retires the driver and its pool after active operations finish. A later query creates a new driver. Do not call cleanup from inside an active query or transaction on the same thread.
55
40
 
56
41
  ## Running queries
57
42
 
58
- Use `neo4j_query` to run a query and receive all results:
43
+ Materialize all result rows:
59
44
 
60
45
  ```ruby
61
- entries = neo4j_query("MATCH (n) RETURN n;")
46
+ rows = neo4j_query("MATCH (person:Person) RETURN person.name AS name")
47
+ puts rows.first["name"]
62
48
  ```
63
- Alternatively, specify a block to make use of Neo4j's streaming capabilities and receive entries one by one:
49
+
50
+ Or process records incrementally without first materializing the complete result:
64
51
 
65
52
  ```ruby
66
- neo4j_query("MATCH (n) RETURN n;") do |entry|
67
- # handle entry here
53
+ neo4j_query("MATCH (person:Person) RETURN person") do |row|
54
+ puts row["person"][:name]
68
55
  end
69
56
  ```
70
57
 
71
- Using streaming avoids memory hog since it prevents having to read all entries into memory before handling them. Nodes are returned as `Neo4jBolt::Node`, relationships as `Neo4jBolt::Relationship`. Both are subclasses of `Hash`, providing access to all properties plus a few extra details:
58
+ The block form returns `nil`. Its upstream session, result, and pooled connection remain alive for the duration of iteration.
72
59
 
73
- - `Neo4jBolt::Node` attributes: `id`, `labels`
74
- - `Neo4jBolt::Relationship` attributes: `id`, `start_node_id`, `end_node_id`, `type`
60
+ Parameters stay separate from Cypher:
75
61
 
76
62
  ```ruby
77
- node = neo4j_query_expect_one("CREATE (n:Node {a: 1, b: 2}) RETURN n;")['n']
78
- # All nodes returned from Neo4j are a Neo4jBolt::Node
79
- # It's a subclass of Hash and it stores all the node's
80
- # properties plus two attributes called id and labels:
81
- puts node.id
82
- puts node.labels
83
- puts node.keys
84
- node.each_pair { |k, v| puts "#{k}: #{v}" }
85
- puts node.to_json
86
- puts "a: #{node[:a]}"
63
+ row = neo4j_query_expect_one(
64
+ "MATCH (person:Person {email: $email}) RETURN person",
65
+ email: "ada@example.test"
66
+ )
87
67
  ```
88
68
 
89
- While values returned by Neo4j can be accesses via string keys (`['n']` in the example above), property keys of nodes and relationships are converted to symbols (`node[:a]`).
69
+ `neo4j_query_expect_one` raises `Neo4jBolt::ExpectedOneResultError` unless the query produces exactly one row.
70
+
71
+ ### Result compatibility
72
+
73
+ Result-row keys are strings:
90
74
 
91
- Use `neo4j_query_expect_one` if you want to make sure there's exactly one entry to be returned:
75
+ ```ruby
76
+ row["person"]
77
+ ```
78
+
79
+ Nodes and relationships remain Hash subclasses whose property keys are symbols:
92
80
 
93
81
  ```ruby
94
- node = neo4j_query_expect_one("MATCH (n) RETURN n LIMIT 1;")['n']
82
+ node = row["person"]
83
+ node[:name]
84
+ node.id
85
+ node.labels
86
+ node.element_id
95
87
  ```
96
88
 
97
- If there's zero, two, or more results, this will raise a `ExpectedOneResultError`.
89
+ `Neo4jBolt::Node` preserves `id` and `labels` and additively exposes `element_id`.
90
+
91
+ `Neo4jBolt::Relationship` preserves `id`, `start_node_id`, `end_node_id`, and `type`, and additively exposes `element_id`, `start_node_element_id`, and `end_node_element_id`.
92
+
93
+ Arrays and maps are adapted recursively. Map/property keys inside values are symbols, matching Neo4jBolt 0.3 behavior. Modern upstream values that 0.3 could not represent—such as temporal, spatial, path, byte, duration, and UUID values—pass through as upstream value objects. This is additive support.
98
94
 
99
- ## Using transactions
95
+ Signed 64-bit integer limits are checked before transport. Out-of-range integers raise `Neo4jBolt::IntegerOutOfRangeError`.
100
96
 
101
- Any Neo4j query will run in its own transaction by default. If you want to group multiple queries into a transaction to make sure they either succeed completely or fail completely, use `transaction` like this:
97
+ ## Transactions
98
+
99
+ The convenient compatibility API is unchanged:
102
100
 
103
101
  ```ruby
104
102
  transaction do
105
- neo4j_query("CREATE (n:Node {a: 1});")
106
- neo4j_query("CREATE (n:Node {b: 1});")
103
+ neo4j_query("CREATE (:Person {name: 'Ada'})")
104
+ neo4j_query("CREATE (:Person {name: 'Grace'})")
107
105
  end
108
106
  ```
109
107
 
110
- Transactions can be nested, with the inner transactions doing nothing and the outermost transaction being committed unless something goes wrong in which case the outermost transaction gets rolled back. No matter how often you nest transactions, there's only one transaction from the perspective of Neo4j.
108
+ Nested `transaction` blocks reuse one actual upstream session and transaction. Applications do not receive or pass an upstream transaction object.
111
109
 
112
- ## Setting up constraints and indexes
110
+ Transaction context is isolated by calling thread and by the object using `Neo4jBolt`. Two threads using the same object receive independent upstream transactions and pooled connections.
113
111
 
114
- Use `setup_constraints_and_indexes` like this:
112
+ Transactions are rollback-only after any nested operation raises, even if application code rescues the exception later. This includes database failures and `ExpectedOneResultError`:
115
113
 
116
114
  ```ruby
117
- CONSTRAINTS_LIST = ['User/email', 'Session/sid']
118
- INDEX_LIST = ['Session/expires']
119
- setup_constraints_and_indexes(CONSTRAINTS_LIST, INDEX_LIST)
115
+ transaction do
116
+ neo4j_query("CREATE (:Marker)")
117
+
118
+ begin
119
+ neo4j_query("invalid cypher")
120
+ rescue Neo4jBolt::SyntaxError
121
+ end
122
+ end
123
+ # Nothing is committed.
120
124
  ```
121
125
 
122
- This setup up two uniqueness constraints and one index:
123
- - the `email` property of all nodes with label `User` must be unique
124
- - the `sid` property of all nodes with label `Session` must be unique
125
- - the `expires` property of all nodes with label `Session` gets indexed for faster lookup
126
+ Calling `rollback` inside a transaction marks that outer transaction rollback-only and returns `nil`. Calling it outside a transaction raises `Neo4jBolt::Error`; the old 0.3 implementation exposed the method but accidentally delegated to a nonexistent private implementation.
126
127
 
127
- Neo4jBolt prefixes all constraints and indexes declared this way with `neo4j_bolt_` and it will remove all such entries previously declared (as detected by the prefix) and not passed to `setup_constraints_and_indexes`. That way, constraints and indexes can be added and removed.
128
+ Upstream server/driver failures are translated to `Neo4jBolt::Error`. Syntax and uniqueness-constraint failures are translated narrowly to `Neo4jBolt::SyntaxError` and `Neo4jBolt::ConstraintValidationFailedError`. The upstream exception remains available as `error.cause`.
128
129
 
129
- Neo4jBolt does not currently support putting constraints on relationships or declaring indexes on relationships or combining several properties into one uniqueness constraint. You are, however, free to declare these constraints and indexes via Cypher yourself.
130
+ ## Constraints and indexes
130
131
 
131
- ## Housekeeping and inspection
132
+ The existing setup format remains supported:
132
133
 
133
- Use the `neo4j_bolt` command line tool to perform various tasks regarding your database:
134
+ ```ruby
135
+ setup_constraints_and_indexes(
136
+ ["User/email", "Session/sid"],
137
+ ["Session/expires"]
138
+ )
139
+ ```
134
140
 
135
- | Command | Description |
136
- | ------- | ----------- |
137
- | `neo4j_bolt console` | launch Pry console with Neo4jBolt |
138
- | `neo4j_bolt clear` | remove all nodes and relationships, needs `--srsly` argument |
139
- | `neo4j_bolt dump` | dump database contents |
140
- | `neo4j_bolt load` | load database dump |
141
- | `neo4j_bolt index ls` | list all database constraints and indexes |
142
- | `neo4j_bolt index rm` | remove all constraints and indexes, needs `-f` |
143
- | `neo4j_bolt visualize` | generates a visual representation of the current datbase contents |
141
+ Managed entries retain the `neo4j_bolt_` prefix. Setup removes obsolete entries with that prefix only; it does not remove arbitrary application-defined indexes or constraints.
144
142
 
145
- Specify you Neo4j host and port using `--host` if your database is not running on localhost:7687.
143
+ ## Dump and load
146
144
 
147
- ### Dump database contents
145
+ The textual format is unchanged:
148
146
 
149
- When you dump a database, output will go to `/dev/stdout` by default, but it can be redirected to any file via `--out-file`. In the export, node IDs start at 0, regardless of the actual node IDs in the database, and start node / end node IDs in the relationship dumps are adjusted accodingly. Don't rely on actual node IDs within your database, as they may change during export and import. Relationship IDs are omitted in the export.
147
+ ```text
148
+ n {"id":0,"labels":["Person"],"properties":{"name":"Ada"}}
149
+ n {"id":1,"labels":["Person"],"properties":{"name":"Grace"}}
150
+ r {"from":0,"to":1,"type":"KNOWS","properties":{"since":2024}}
151
+ ```
150
152
 
151
- ### Load database dump
153
+ ```ruby
154
+ File.open("database.dump", "w") { |io| dump_database(io) }
155
+ File.open("database.dump", "r") { |io| load_database_dump(io) }
156
+ ```
152
157
 
153
- A database dump can only be loaded if the database is empty. Otherwise, you'll have to specify `--force`.
158
+ The `0, 1, 2, ...` IDs are synthetic dump-local IDs. Database-internal IDs and element IDs are never written to the persistent format. The adapter uses driver-provided entity identity while dumping and a random temporary property (removed in `ensure`) while loading, so relationship reconstruction does not depend on deprecated persistent integer IDs. 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.
154
159
 
155
- ### List constraints and indexes
160
+ Loading requires an empty database unless `force_append: true` is passed.
156
161
 
157
- Use the command `neo4j_bolt index ls` to see which constraints and indexes are currently active in the database.
162
+ ## CLI
158
163
 
159
- ### Remove all constraints and indexes
164
+ The `neo4j_bolt` executable retains these commands:
160
165
 
161
- Use the command `neo4j_bolt index rm -f` to remove all constraints and indexes in the database but make sure you know what you're doing.
166
+ | Command | Purpose |
167
+ | --- | --- |
168
+ | `neo4j_bolt console` | Open an IRB console with Neo4jBolt loaded |
169
+ | `neo4j_bolt clear --srsly` | Delete all nodes and relationships |
170
+ | `neo4j_bolt dump` | Write the textual database dump |
171
+ | `neo4j_bolt load [--force] PATH` | Load a textual dump |
172
+ | `neo4j_bolt index ls` | List constraints and indexes |
173
+ | `neo4j_bolt index rm --force` | Remove all constraints and indexes |
174
+ | `neo4j_bolt visualize` | Generate a GraphViz document |
162
175
 
163
- ### Visualize database contents
176
+ Use `--host HOST:PORT` to select a server. `gli` remains a runtime dependency for the CLI, and `pry` remains for the separate `bin/console` executable.
164
177
 
165
- Use the command `neo4j_bolt visualize` to obtain a GraphViz-formatted document suitable for piping into `dot`:
178
+ ## Tested Neo4j versions
166
179
 
167
- ```bash
168
- ./bin/neo4j_bolt visualize | dot -Tsvg > graph.svg
169
- ```
180
+ The same complete integration suite is run against these exact Community images:
170
181
 
171
- If you don't have GraphViz installed, you can use a Docker image instead:
182
+ - `neo4j:4.4.48-community`
183
+ - `neo4j:5.26.28-community`
184
+ - `neo4j:2026.06.0-community`
172
185
 
173
- ```bash
174
- ./bin/neo4j_bolt visualize | docker run --rm -i nshine/dot dot -Tsvg > graph.svg
175
- ```
186
+ No compatibility beyond this matrix is claimed for `0.4.0.pre1`.
176
187
 
177
- The result looks like this for the movie graph example provided by Neo4j:
188
+ Run one modern LTS target:
178
189
 
179
- <img src="movie_graph.svg" />
190
+ ```bash
191
+ bundle exec rake spec
192
+ ```
180
193
 
181
- You can see nodes and relationships with their current numbers, plus all properties with their respective data types and for each data type the percentage of entities with that data type and min / mean / max values (for ints and floats) or min / mean / max lengths (for strings and lists).
194
+ Run the complete sequential matrix:
182
195
 
183
- Uniqueness constraints and indexes (if available) are shown as well if they are defined on a single node or relationship with a single attribute.
196
+ ```bash
197
+ bundle exec rake spec:matrix
198
+ ```
184
199
 
185
- If a property is an integer and has `ts` or `timestamp` in its name separated from other alphanumeric characters (like `ts_created` but not like `hits`), it gets treated as a UNIX timestamp (just for the visualization).
200
+ The harness creates uniquely named disposable containers with `NEO4J_AUTH=none`, dynamically publishes Bolt ports, waits by establishing a real driver/query connection, and cleans every container through shell traps. A developer does not need to start Neo4j manually.
201
+
202
+ To point RSpec itself at an already disposable database, set `NEO4J_BOLT_TEST_HOST` and `NEO4J_BOLT_TEST_PORT`. The explicit port requirement protects real databases from the destructive integration suite.
203
+
204
+ ## Compatibility inventory for 0.4
205
+
206
+ | API | Status |
207
+ | --- | --- |
208
+ | `bolt_host`, `bolt_port`, `bolt_verbosity` | Preserved |
209
+ | Included/extended/module-style use | Preserved |
210
+ | `neo4j_query`, including incremental block form | Preserved via adapter |
211
+ | `neo4j_query_expect_one` | Preserved via adapter |
212
+ | `transaction`, nesting, rollback-only behavior | Preserved via thread-local adapter |
213
+ | `rollback` | Preserved and repaired as rollback-only |
214
+ | `cleanup_neo4j`, `wait_for_neo4j` | Preserved via pooled driver lifecycle |
215
+ | `setup_constraints_and_indexes` | Preserved with current Cypher |
216
+ | `dump_database`, `load_database_dump` | Preserved; relationship reconstruction modernized |
217
+ | Named error classes | Preserved with narrow upstream translation |
218
+ | `Node` and `Relationship` Hash behavior | Preserved via recursive value adapter |
219
+ | Element identity and modern values | Additive behavior |
220
+ | CLI commands | Preserved |
221
+ | `BoltSocket`, `BoltBuffer`, protocol markers/state/parser/packer | Intentionally removed private implementation details |
222
+
223
+ ## 0.4.0.pre1 migration notes
224
+
225
+ - Ruby 3.4 or newer is required; Ruby 2.x/3.0–3.3 applications should stay on 0.3.x until upgraded.
226
+ - The exact prerelease upstream dependency is pinned while no stable `neo4j-ruby-driver` 6.2.x exists.
227
+ - Connections are pooled and safe for concurrent use instead of one mutable socket per including object.
228
+ - TLS, routing, authentication, database selection, and other upstream-driver configuration are not newly exposed through the legacy three-setting API in this compatibility prerelease.
229
+ - `BoltSocket`, `BoltBuffer`, `ServerState`, `BoltMarker`, `UnexpectedServerResponse`, `State`, and `CypherError` were undocumented wire internals and are removed.
186
230
 
187
231
  ## Development
188
232
 
189
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
190
-
191
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
192
-
193
- ## Contributing
194
-
195
- Bug reports and pull requests are welcome on GitHub at https://github.com/specht/neo4j_bolt.
233
+ Run `bin/setup`, then `bundle exec rake spec` or `bundle exec rake spec:matrix`. Build without publishing with `bundle exec rake build`.
196
234
 
235
+ Bug reports and pull requests are welcome at <https://github.com/specht/neo4j_bolt>.
data/bin/neo4j_bolt CHANGED
@@ -92,10 +92,10 @@ class App
92
92
  command :index do |c|
93
93
  c.command :ls do |c2|
94
94
  c2.action do |global_options, options, args|
95
- neo4j_query("SHOW ALL CONSTRAINTS") do |row|
95
+ neo4j_query("SHOW CONSTRAINTS") do |row|
96
96
  puts "#{row['type']} #{row['name']} #{(row['labelsOrTypes'] || []).join('/')}/#{(row['properties'] || []).join('/')}"
97
97
  end
98
- neo4j_query("SHOW ALL INDEXES") do |row|
98
+ neo4j_query("SHOW INDEXES") do |row|
99
99
  puts "#{row['uniqueness']} #{row['entityType']} #{row['state']} #{row['populationPercent']}% #{row['name']} #{(row['labelsOrTypes'] || []).join('/')}/#{(row['properties'] || []).join('/')}"
100
100
  end
101
101
  end
@@ -106,10 +106,10 @@ class App
106
106
  if options[:force]
107
107
  all_constraints = []
108
108
  all_indexes = []
109
- neo4j_query("SHOW ALL CONSTRAINTS") do |row|
109
+ neo4j_query("SHOW CONSTRAINTS") do |row|
110
110
  all_constraints << row['name']
111
111
  end
112
- neo4j_query("SHOW ALL INDEXES") do |row|
112
+ neo4j_query("SHOW INDEXES") do |row|
113
113
  all_indexes << row['name']
114
114
  end
115
115
  transaction do
@@ -242,7 +242,7 @@ class App
242
242
  'BTREE' => 'indexed',
243
243
  }
244
244
 
245
- ['SHOW ALL CONSTRAINTS', 'SHOW ALL INDEXES'].each do |query|
245
+ ['SHOW CONSTRAINTS', 'SHOW INDEXES'].each do |query|
246
246
  neo4j_query(query) do |row|
247
247
  # STDERR.puts row.to_json
248
248
  labels_or_types = row['labelsOrTypes'] || []
@@ -1,3 +1,3 @@
1
1
  module Neo4jBolt
2
- VERSION = "0.2.1"
2
+ VERSION = "0.4.0"
3
3
  end