neo4j-core 3.1.1 → 4.0.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.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +7 -12
  3. data/README.md +7 -7
  4. data/lib/neo4j-core.rb +3 -2
  5. data/lib/neo4j-core/active_entity.rb +8 -10
  6. data/lib/neo4j-core/cypher_translator.rb +61 -59
  7. data/lib/neo4j-core/hash_with_indifferent_access.rb +31 -22
  8. data/lib/neo4j-core/helpers.rb +15 -17
  9. data/lib/neo4j-core/label.rb +7 -6
  10. data/lib/neo4j-core/query.rb +271 -268
  11. data/lib/neo4j-core/query_clauses.rb +371 -355
  12. data/lib/neo4j-core/query_find_in_batches.rb +26 -26
  13. data/lib/neo4j-core/version.rb +1 -1
  14. data/lib/neo4j-embedded.rb +2 -2
  15. data/lib/neo4j-embedded/cypher_response.rb +40 -41
  16. data/lib/neo4j-embedded/embedded_database.rb +21 -22
  17. data/lib/neo4j-embedded/embedded_ha_session.rb +13 -11
  18. data/lib/neo4j-embedded/embedded_impermanent_session.rb +9 -8
  19. data/lib/neo4j-embedded/embedded_label.rb +64 -70
  20. data/lib/neo4j-embedded/embedded_node.rb +68 -73
  21. data/lib/neo4j-embedded/embedded_relationship.rb +6 -13
  22. data/lib/neo4j-embedded/embedded_session.rb +128 -132
  23. data/lib/neo4j-embedded/embedded_transaction.rb +34 -33
  24. data/lib/neo4j-embedded/property.rb +84 -77
  25. data/lib/neo4j-embedded/to_java.rb +24 -23
  26. data/lib/neo4j-server.rb +1 -1
  27. data/lib/neo4j-server/cypher_authentication.rb +105 -103
  28. data/lib/neo4j-server/cypher_label.rb +25 -23
  29. data/lib/neo4j-server/cypher_node.rb +180 -177
  30. data/lib/neo4j-server/cypher_node_uncommited.rb +11 -9
  31. data/lib/neo4j-server/cypher_relationship.rb +101 -102
  32. data/lib/neo4j-server/cypher_response.rb +171 -170
  33. data/lib/neo4j-server/cypher_session.rb +209 -205
  34. data/lib/neo4j-server/cypher_transaction.rb +66 -48
  35. data/lib/neo4j-server/resource.rb +17 -22
  36. data/lib/neo4j/entity_equality.rb +3 -4
  37. data/lib/neo4j/label.rb +13 -16
  38. data/lib/neo4j/node.rb +30 -34
  39. data/lib/neo4j/property_container.rb +3 -3
  40. data/lib/neo4j/property_validator.rb +4 -5
  41. data/lib/neo4j/relationship.rb +17 -22
  42. data/lib/neo4j/session.rb +19 -21
  43. data/lib/neo4j/tasks/config_server.rb +2 -3
  44. data/lib/neo4j/tasks/neo4j_server.rake +82 -74
  45. data/lib/neo4j/transaction.rb +23 -22
  46. data/neo4j-core.gemspec +21 -16
  47. metadata +72 -2
@@ -1,61 +1,79 @@
1
- module Neo4j::Server
2
- class CypherTransaction
3
- include Neo4j::Transaction::Instance
4
- include Neo4j::Core::CypherTranslator
5
- include Resource
1
+ module Neo4j
2
+ module Server
3
+ # The CypherTransaction object lifecycle is as follows:
4
+ # * It is initialized with the transactional endpoint URL and the connection object to use for communication. It does not communicate with the server to create this.
5
+ # * The first query within the transaction sets the commit and execution addresses, :commit_url and :exec_url.
6
+ # * At any time, `failure` can be called to mark a transaction failed and trigger a rollback upon closure.
7
+ # * `close` is called to end the transaction. It calls `_commit_tx` or `_delete_tx`.
8
+ #
9
+ # If a transaction is created and then closed without performing any queries, an OpenStruct is returned that behaves like a successfully closed query.
10
+ class CypherTransaction
11
+ include Neo4j::Transaction::Instance
12
+ include Neo4j::Core::CypherTranslator
13
+ include Resource
6
14
 
7
- attr_reader :commit_url, :exec_url
15
+ attr_reader :commit_url, :exec_url, :base_url, :connection
8
16
 
9
- class CypherError < StandardError
10
- attr_reader :code, :status
11
- def initialize(code, status, message)
12
- super(message)
13
- @code = code
14
- @status = status
17
+ def initialize(url, session_connection)
18
+ @base_url = url
19
+ @connection = session_connection
20
+ register_instance
15
21
  end
16
- end
17
22
 
18
- def initialize(db, response, url, connection)
19
- @connection = connection
20
- @commit_url = response.body['commit']
21
- @exec_url = response.headers['Location']
22
- raise "NO ENDPOINT URL #{@connection} : HEAD: #{response.headers.inspect}" if !@exec_url || @exec_url.empty?
23
- init_resource_data(response.body, url)
24
- expect_response_code(response,201)
25
- register_instance
26
- end
23
+ ROW_REST = %w(row REST)
24
+ def _query(cypher_query, params = nil)
25
+ fail 'Transaction expired, unable to perform query' if expired?
26
+ statement = {statement: cypher_query, parameters: params, resultDataContents: ROW_REST}
27
+ body = {statements: [statement]}
27
28
 
28
- def _query(cypher_query, params=nil)
29
- statement = { statement: cypher_query, parameters: params, resultDataContents: ['row', 'REST'] }
30
- body = { statements: [statement] }
31
- response = @connection.post(@exec_url, body)
32
- _create_cypher_response(response)
33
- end
29
+ response = exec_url && commit_url ? connection.post(exec_url, body) : register_urls(body)
30
+ _create_cypher_response(response)
31
+ end
34
32
 
35
- def _create_cypher_response(response)
36
- first_result = response.body['results'][0]
33
+ def _delete_tx
34
+ _tx_query(:delete, exec_url, headers: resource_headers)
35
+ end
37
36
 
38
- cr = CypherResponse.new(response, true)
39
- if !response.body['errors'].empty?
40
- first_error = response.body['errors'].first
41
- cr.set_error(first_error['message'], first_error['code'], first_error['code'])
42
- else
43
- cr.set_data(first_result['data'], first_result['columns'])
37
+ def _commit_tx
38
+ _tx_query(:post, commit_url, nil)
44
39
  end
45
- cr
46
- end
47
40
 
48
- def _delete_tx
49
- response = @connection.delete(@exec_url, headers: resource_headers)
50
- expect_response_code(response,200)
51
- response
52
- end
41
+ private
53
42
 
54
- def _commit_tx
55
- response = @connection.post(@commit_url)
43
+ def _tx_query(action, endpoint, headers = {})
44
+ return empty_response if !commit_url || expired?
45
+ response = connection.send(action, endpoint, headers)
46
+ expect_response_code(response, 200)
47
+ response
48
+ end
49
+
50
+ def register_urls(body)
51
+ response = connection.post(base_url, body)
52
+ @commit_url = response.body['commit']
53
+ @exec_url = response.headers['Location']
54
+ fail "NO ENDPOINT URL #{connection} : HEAD: #{response.headers.inspect}" if !exec_url || exec_url.empty?
55
+ init_resource_data(response.body, base_url)
56
+ expect_response_code(response, 201)
57
+ response
58
+ end
59
+
60
+ def _create_cypher_response(response)
61
+ first_result = response.body['results'][0]
56
62
 
57
- expect_response_code(response,200)
58
- response
63
+ cr = CypherResponse.new(response, true)
64
+ if response.body['errors'].empty?
65
+ cr.set_data(first_result['data'], first_result['columns'])
66
+ else
67
+ first_error = response.body['errors'].first
68
+ expired if first_error['message'].match(/Unrecognized transaction id/)
69
+ cr.set_error(first_error['message'], first_error['code'], first_error['code'])
70
+ end
71
+ cr
72
+ end
73
+
74
+ def empty_response
75
+ OpenStruct.new(status: 200, body: '')
76
+ end
59
77
  end
60
78
  end
61
- end
79
+ end
@@ -1,44 +1,39 @@
1
1
  module Neo4j
2
2
  module Server
3
3
  module Resource
4
-
5
4
  class ServerException < Exception
6
5
  end
7
6
 
8
7
  attr_reader :resource_data, :resource_url
9
8
 
10
9
  def init_resource_data(resource_data, resource_url)
11
- raise "Exception #{resource_data['exception']}" if resource_data['exception']
10
+ fail "Exception #{resource_data['exception']}" if resource_data['exception']
11
+ fail "Expected @resource_data to be Hash got #{resource_data.inspect}" unless resource_data.respond_to?(:[])
12
+
12
13
  @resource_url = resource_url
13
14
  @resource_data = resource_data
14
- raise "expected @resource_data to be Hash got #{@resource_data.class}" unless @resource_data.respond_to?(:[])
15
+
15
16
  self
16
17
  end
17
18
 
18
-
19
- def wrap_resource(db, rel, resource_class, verb = :get, statement = {}, connection)
20
- url = resource_url(rel)
21
- payload = statement.empty? ? nil : statement
22
- response = case verb
23
- when :get then connection.get(url, payload)
24
- when :post then connection.post(url, payload)
25
- else raise "Illegal verb #{verb}"
26
- end
27
- response.status == 404 ? nil : resource_class.new(db, response, url, connection)
19
+ def wrap_resource(connection = Neo4j::Session.current)
20
+ url = resource_url('transaction')
21
+ CypherTransaction.new(url, connection)
28
22
  end
29
23
 
30
- def resource_url(rel = nil)
31
- return @resource_url if rel.nil?
32
- url = resource_data[rel.to_s]
33
- raise "No resource rel '#{rel}', available #{@resource_data.keys.inspect}" unless url
34
- url
24
+ def resource_url(key = nil)
25
+ return @resource_url if key.nil?
26
+
27
+ @resource_data.fetch key.to_s
28
+ rescue KeyError
29
+ raise "No resource key '#{key}', available #{@resource_data.keys.inspect}"
35
30
  end
36
31
 
37
- def handle_response_error(response, msg="Error for request" )
38
- raise ServerException.new("#{msg} #{response.env && response.env[:url].to_s}, #{response.status}, #{response.status}")
32
+ def handle_response_error(response, msg = 'Error for request')
33
+ fail ServerException, "#{msg} #{response.env && response.env[:url].to_s}, #{response.status}, #{response.status}"
39
34
  end
40
35
 
41
- def expect_response_code(response, expected_code, msg="Error for request" )
36
+ def expect_response_code(response, expected_code, msg = 'Error for request')
42
37
  handle_response_error(response, "Expected response code #{expected_code} #{msg}") unless response.status == expected_code
43
38
  response
44
39
  end
@@ -57,7 +52,7 @@ module Neo4j
57
52
  end
58
53
 
59
54
  def convert_from_json_value(value)
60
- JSON.parse(value, :quirks_mode => true)
55
+ JSON.parse(value, quirks_mode: true)
61
56
  end
62
57
  end
63
58
  end
@@ -1,9 +1,8 @@
1
1
  module Neo4j
2
2
  module EntityEquality
3
- def ==(o)
4
- o.class == self.class && o.neo_id == neo_id
3
+ def ==(other)
4
+ other.class == self.class && other.neo_id == neo_id
5
5
  end
6
6
  alias_method :eql?, :==
7
-
8
7
  end
9
- end
8
+ end
data/lib/neo4j/label.rb CHANGED
@@ -7,23 +7,23 @@ module Neo4j
7
7
 
8
8
  # @abstract
9
9
  def name
10
- raise 'not implemented'
10
+ fail 'not implemented'
11
11
  end
12
12
 
13
13
  # @abstract
14
14
  def create_index(*properties)
15
- raise 'not implemented'
15
+ fail 'not implemented'
16
16
  end
17
17
 
18
18
  # @abstract
19
19
  def drop_index(*properties)
20
- raise 'not implemented'
20
+ fail 'not implemented'
21
21
  end
22
22
 
23
23
  # List indices for a label
24
24
  # @abstract
25
25
  def indexes
26
- raise 'not implemented'
26
+ fail 'not implemented'
27
27
  end
28
28
 
29
29
  # Creates a neo4j constraint on a property
@@ -34,11 +34,11 @@ module Neo4j
34
34
  #
35
35
  def create_constraint(property, constraints, session = Neo4j::Session.current)
36
36
  cypher = case constraints[:type]
37
- when :unique
38
- "CREATE CONSTRAINT ON (n:`#{name}`) ASSERT n.`#{property}` IS UNIQUE"
39
- else
40
- raise "Not supported constrain #{constraints.inspect} for property #{property} (expected :type => :unique)"
41
- end
37
+ when :unique
38
+ "CREATE CONSTRAINT ON (n:`#{name}`) ASSERT n.`#{property}` IS UNIQUE"
39
+ else
40
+ fail "Not supported constrain #{constraints.inspect} for property #{property} (expected :type => :unique)"
41
+ end
42
42
  session._query_or_fail(cypher)
43
43
  end
44
44
 
@@ -51,10 +51,10 @@ module Neo4j
51
51
  #
52
52
  def drop_constraint(property, constraint, session = Neo4j::Session.current)
53
53
  cypher = case constraint[:type]
54
- when :unique
55
- "DROP CONSTRAINT ON (n:`#{name}`) ASSERT n.`#{property}` IS UNIQUE"
56
- else
57
- raise "Not supported constrain #{constraint.inspect}"
54
+ when :unique
55
+ "DROP CONSTRAINT ON (n:`#{name}`) ASSERT n.`#{property}` IS UNIQUE"
56
+ else
57
+ fail "Not supported constrain #{constraint.inspect}"
58
58
  end
59
59
  session._query_or_fail(cypher)
60
60
  end
@@ -78,9 +78,6 @@ module Neo4j
78
78
  def find_nodes(label_name, key, value, session = Neo4j::Session.current)
79
79
  session.find_nodes(label_name, key, value)
80
80
  end
81
-
82
81
  end
83
-
84
82
  end
85
-
86
83
  end
data/lib/neo4j/node.rb CHANGED
@@ -1,9 +1,7 @@
1
1
  module Neo4j
2
-
3
2
  # The base class for both the Embedded and Server Neo4j Node
4
3
  # Notice this class is abstract and can't be instantiated
5
4
  class Node
6
-
7
5
  # A module that allows plugins to register wrappers around Neo4j::Node objects
8
6
  module Wrapper
9
7
  # Used by Neo4j::NodeMixin to wrap nodes
@@ -21,44 +19,44 @@ module Neo4j
21
19
  include PropertyContainer
22
20
 
23
21
  # @return [Hash<Symbol, Object>] all properties of the node
24
- def props()
25
- raise 'not implemented'
22
+ def props
23
+ fail 'not implemented'
26
24
  end
27
25
 
28
26
  # replace all properties with new properties
29
27
  # @param [Hash<Symbol, Object>] properties a hash of properties the node should have
30
28
  def props=(properties)
31
- raise 'not implemented'
29
+ fail 'not implemented'
32
30
  end
33
31
 
34
32
  # Refresh the properties by reading it from the database again next time an property value is requested.
35
33
  def refresh
36
- raise 'not implemented'
34
+ fail 'not implemented'
37
35
  end
38
36
 
39
37
  # Updates the properties, keeps old properties
40
38
  # @param [Hash<Symbol, Object>] properties hash of properties that should be updated on the node
41
39
  def update_props(properties)
42
- raise 'not implemented'
40
+ fail 'not implemented'
43
41
  end
44
42
 
45
43
  # Directly remove the property on the node (low level method, may need transaction)
46
44
  def remove_property(key)
47
- raise 'not implemented'
45
+ fail 'not implemented'
48
46
  end
49
47
 
50
48
  # Directly set the property on the node (low level method, may need transaction)
51
49
  # @param [Symbol, String] key
52
50
  # @param value see Neo4j::PropertyValidator::VALID_PROPERTY_VALUE_CLASSES for valid values
53
51
  def set_property(key, value)
54
- raise 'not implemented'
52
+ fail 'not implemented'
55
53
  end
56
54
 
57
55
  # Directly get the property on the node (low level method, may need transaction)
58
56
  # @param [Symbol, String] key
59
57
  # @return the value of the key
60
58
  def get_property(key, value)
61
- raise 'not implemented'
59
+ fail 'not implemented'
62
60
  end
63
61
 
64
62
  # Creates a relationship of given type to other_node with optionally properties
@@ -66,7 +64,7 @@ module Neo4j
66
64
  # @param [Neo4j::Node] other_node the other node
67
65
  # @param [Hash<Symbol, Object>] props optionally properties for the created relationship
68
66
  def create_rel(type, other_node, props = nil)
69
- raise 'not implemented'
67
+ fail 'not implemented'
70
68
  end
71
69
 
72
70
 
@@ -89,45 +87,45 @@ module Neo4j
89
87
  # node_a.rels(type: :friends, dir: :outgoing, between: node_b)
90
88
  #
91
89
  def rels(match = {dir: :both})
92
- raise 'not implemented'
90
+ fail 'not implemented'
93
91
  end
94
92
 
95
93
  # Adds one or more Neo4j labels on the node
96
94
  # @param [Array<Symbol>] labels one or more labels to add
97
95
  def add_label(*labels)
98
- raise 'not implemented'
96
+ fail 'not implemented'
99
97
  end
100
98
 
101
99
  # Sets label on the node. Any old labels will be removed
102
100
  # @param [Array<Symbol>] labels one or more labels to set
103
101
  def set_label(*labels)
104
- raise 'not implemented'
102
+ fail 'not implemented'
105
103
  end
106
104
 
107
105
  # Removes given labels
108
106
  def remove_label(*labels)
109
- raise 'not implemented'
107
+ fail 'not implemented'
110
108
  end
111
109
 
112
110
  #
113
111
  # @return [Array<Symbol>]all labels on the node
114
- def labels()
115
- raise 'not implemented'
112
+ def labels
113
+ fail 'not implemented'
116
114
  end
117
115
 
118
116
  # Deletes this node from the database
119
- def del()
120
- raise 'not implemented'
117
+ def del
118
+ fail 'not implemented'
121
119
  end
122
120
 
123
121
  # @return true if the node exists in the database
124
122
  def exist?
125
- raise 'not implemented'
123
+ fail 'not implemented'
126
124
  end
127
125
 
128
126
  # @return all the Neo4j labels for this node
129
127
  def labels
130
- raise 'not implemented'
128
+ fail 'not implemented'
131
129
  end
132
130
 
133
131
  # Returns the only node of a given type and direction that is attached to this node, or nil.
@@ -144,27 +142,27 @@ module Neo4j
144
142
  #
145
143
  # @param (see #rel)
146
144
  def node(specs = {})
147
- raise 'not implemented'
145
+ fail 'not implemented'
148
146
  end
149
147
 
150
148
  # Same as #node but returns the relationship. Notice it may raise an exception if there are more then one relationship matching.
151
149
  def rel(spec = {})
152
- raise 'not implemented'
150
+ fail 'not implemented'
153
151
  end
154
152
 
155
153
  def _rel(spec = {})
156
- raise 'not implemented'
154
+ fail 'not implemented'
157
155
  end
158
156
 
159
157
  # Returns true or false if there is one or more relationships
160
158
  # @return [Boolean]
161
159
  def rel?(spec = {})
162
- raise 'not implemented'
160
+ fail 'not implemented'
163
161
  end
164
162
 
165
163
  # @return [Boolean] true if the node exists
166
164
  def exist?
167
- raise 'not implemented'
165
+ fail 'not implemented'
168
166
  end
169
167
 
170
168
  # Works like #rels method but instead returns the nodes.
@@ -174,12 +172,12 @@ module Neo4j
174
172
  # @return [Enumerable<Neo4j::Node>] an Enumeration of either Neo4j::Node objects or wrapped Neo4j::Node objects
175
173
  # @note it's possible that the same node is returned more than once because of several relationship reaching to the same node, see #outgoing for alternative
176
174
  def nodes(specs = {})
177
- #rels(specs).map{|n| n.other_node(self)}
175
+ # rels(specs).map{|n| n.other_node(self)}
178
176
  end
179
177
 
180
178
  class << self
181
179
  # Creates a node
182
- def create(props=nil, *labels_or_db)
180
+ def create(props = nil, *labels_or_db)
183
181
  session = Neo4j::Core::ArgumentHelper.session(labels_or_db)
184
182
  session.create_node(props, labels_or_db)
185
183
  end
@@ -197,21 +195,19 @@ module Neo4j
197
195
  end
198
196
 
199
197
  # Find the node with given label and value
200
- def find_nodes(label, value=nil, session = Neo4j::Session.current!)
198
+ def find_nodes(label, value = nil, session = Neo4j::Session.current!)
201
199
  session.find_nodes(label, value)
202
200
  end
203
201
  end
204
202
 
205
203
  def initialize
206
- raise "Can't instantiate abstract class" if abstract_class?
204
+ fail "Can't instantiate abstract class" if abstract_class?
207
205
  end
208
206
 
209
207
  private
208
+
210
209
  def abstract_class?
211
210
  self.class == Node
212
211
  end
213
-
214
-
215
212
  end
216
-
217
- end
213
+ end