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.
- checksums.yaml +4 -4
- data/Gemfile +7 -12
- data/README.md +7 -7
- data/lib/neo4j-core.rb +3 -2
- data/lib/neo4j-core/active_entity.rb +8 -10
- data/lib/neo4j-core/cypher_translator.rb +61 -59
- data/lib/neo4j-core/hash_with_indifferent_access.rb +31 -22
- data/lib/neo4j-core/helpers.rb +15 -17
- data/lib/neo4j-core/label.rb +7 -6
- data/lib/neo4j-core/query.rb +271 -268
- data/lib/neo4j-core/query_clauses.rb +371 -355
- data/lib/neo4j-core/query_find_in_batches.rb +26 -26
- data/lib/neo4j-core/version.rb +1 -1
- data/lib/neo4j-embedded.rb +2 -2
- data/lib/neo4j-embedded/cypher_response.rb +40 -41
- data/lib/neo4j-embedded/embedded_database.rb +21 -22
- data/lib/neo4j-embedded/embedded_ha_session.rb +13 -11
- data/lib/neo4j-embedded/embedded_impermanent_session.rb +9 -8
- data/lib/neo4j-embedded/embedded_label.rb +64 -70
- data/lib/neo4j-embedded/embedded_node.rb +68 -73
- data/lib/neo4j-embedded/embedded_relationship.rb +6 -13
- data/lib/neo4j-embedded/embedded_session.rb +128 -132
- data/lib/neo4j-embedded/embedded_transaction.rb +34 -33
- data/lib/neo4j-embedded/property.rb +84 -77
- data/lib/neo4j-embedded/to_java.rb +24 -23
- data/lib/neo4j-server.rb +1 -1
- data/lib/neo4j-server/cypher_authentication.rb +105 -103
- data/lib/neo4j-server/cypher_label.rb +25 -23
- data/lib/neo4j-server/cypher_node.rb +180 -177
- data/lib/neo4j-server/cypher_node_uncommited.rb +11 -9
- data/lib/neo4j-server/cypher_relationship.rb +101 -102
- data/lib/neo4j-server/cypher_response.rb +171 -170
- data/lib/neo4j-server/cypher_session.rb +209 -205
- data/lib/neo4j-server/cypher_transaction.rb +66 -48
- data/lib/neo4j-server/resource.rb +17 -22
- data/lib/neo4j/entity_equality.rb +3 -4
- data/lib/neo4j/label.rb +13 -16
- data/lib/neo4j/node.rb +30 -34
- data/lib/neo4j/property_container.rb +3 -3
- data/lib/neo4j/property_validator.rb +4 -5
- data/lib/neo4j/relationship.rb +17 -22
- data/lib/neo4j/session.rb +19 -21
- data/lib/neo4j/tasks/config_server.rb +2 -3
- data/lib/neo4j/tasks/neo4j_server.rake +82 -74
- data/lib/neo4j/transaction.rb +23 -22
- data/neo4j-core.gemspec +21 -16
- metadata +72 -2
@@ -1,61 +1,79 @@
|
|
1
|
-
module Neo4j
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
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
|
-
|
15
|
+
attr_reader :commit_url, :exec_url, :base_url, :connection
|
8
16
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
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
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
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
|
-
|
29
|
-
|
30
|
-
|
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
|
-
|
36
|
-
|
33
|
+
def _delete_tx
|
34
|
+
_tx_query(:delete, exec_url, headers: resource_headers)
|
35
|
+
end
|
37
36
|
|
38
|
-
|
39
|
-
|
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
|
-
|
49
|
-
response = @connection.delete(@exec_url, headers: resource_headers)
|
50
|
-
expect_response_code(response,200)
|
51
|
-
response
|
52
|
-
end
|
41
|
+
private
|
53
42
|
|
54
|
-
|
55
|
-
|
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
|
-
|
58
|
-
|
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
|
-
|
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
|
-
|
15
|
+
|
15
16
|
self
|
16
17
|
end
|
17
18
|
|
18
|
-
|
19
|
-
|
20
|
-
url
|
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(
|
31
|
-
return @resource_url if
|
32
|
-
|
33
|
-
|
34
|
-
|
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=
|
38
|
-
|
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=
|
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, :
|
55
|
+
JSON.parse(value, quirks_mode: true)
|
61
56
|
end
|
62
57
|
end
|
63
58
|
end
|
data/lib/neo4j/label.rb
CHANGED
@@ -7,23 +7,23 @@ module Neo4j
|
|
7
7
|
|
8
8
|
# @abstract
|
9
9
|
def name
|
10
|
-
|
10
|
+
fail 'not implemented'
|
11
11
|
end
|
12
12
|
|
13
13
|
# @abstract
|
14
14
|
def create_index(*properties)
|
15
|
-
|
15
|
+
fail 'not implemented'
|
16
16
|
end
|
17
17
|
|
18
18
|
# @abstract
|
19
19
|
def drop_index(*properties)
|
20
|
-
|
20
|
+
fail 'not implemented'
|
21
21
|
end
|
22
22
|
|
23
23
|
# List indices for a label
|
24
24
|
# @abstract
|
25
25
|
def indexes
|
26
|
-
|
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
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
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
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
102
|
+
fail 'not implemented'
|
105
103
|
end
|
106
104
|
|
107
105
|
# Removes given labels
|
108
106
|
def remove_label(*labels)
|
109
|
-
|
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
|
-
|
112
|
+
def labels
|
113
|
+
fail 'not implemented'
|
116
114
|
end
|
117
115
|
|
118
116
|
# Deletes this node from the database
|
119
|
-
def del
|
120
|
-
|
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
|
-
|
123
|
+
fail 'not implemented'
|
126
124
|
end
|
127
125
|
|
128
126
|
# @return all the Neo4j labels for this node
|
129
127
|
def labels
|
130
|
-
|
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
|
-
|
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
|
-
|
150
|
+
fail 'not implemented'
|
153
151
|
end
|
154
152
|
|
155
153
|
def _rel(spec = {})
|
156
|
-
|
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
|
-
|
160
|
+
fail 'not implemented'
|
163
161
|
end
|
164
162
|
|
165
163
|
# @return [Boolean] true if the node exists
|
166
164
|
def exist?
|
167
|
-
|
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
|
-
|
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
|