neo4j-core 4.0.7 → 5.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/lib/ext/kernel.rb +9 -0
- data/lib/neo4j/label.rb +2 -1
- data/lib/neo4j/node.rb +8 -11
- data/lib/neo4j/property_container.rb +2 -7
- data/lib/neo4j/property_validator.rb +1 -1
- data/lib/neo4j/session.rb +24 -11
- data/lib/neo4j/tasks/config_server.rb +4 -1
- data/lib/neo4j/tasks/neo4j_server.rake +86 -109
- data/lib/neo4j/transaction.rb +17 -16
- data/lib/neo4j-core/cypher_translator.rb +1 -1
- data/lib/neo4j-core/query.rb +103 -47
- data/lib/neo4j-core/query_clauses.rb +180 -112
- data/lib/neo4j-core/query_find_in_batches.rb +19 -11
- data/lib/neo4j-core/version.rb +1 -1
- data/lib/neo4j-core.rb +3 -0
- data/lib/neo4j-embedded/cypher_response.rb +20 -5
- data/lib/neo4j-embedded/embedded_ha_session.rb +9 -2
- data/lib/neo4j-embedded/embedded_impermanent_session.rb +1 -1
- data/lib/neo4j-embedded/embedded_node.rb +26 -28
- data/lib/neo4j-embedded/embedded_session.rb +14 -7
- data/lib/neo4j-embedded/embedded_transaction.rb +2 -2
- data/lib/neo4j-embedded/label.rb +65 -0
- data/lib/neo4j-embedded/property.rb +5 -5
- data/lib/neo4j-embedded/to_java.rb +7 -13
- data/lib/neo4j-embedded.rb +1 -0
- data/lib/neo4j-server/cypher_node.rb +57 -67
- data/lib/neo4j-server/cypher_node_uncommited.rb +1 -1
- data/lib/neo4j-server/cypher_relationship.rb +10 -6
- data/lib/neo4j-server/cypher_response.rb +96 -52
- data/lib/neo4j-server/cypher_session.rb +80 -93
- data/lib/neo4j-server/cypher_transaction.rb +42 -33
- data/lib/neo4j-server/label.rb +40 -0
- data/lib/neo4j-server/resource.rb +11 -12
- data/lib/neo4j-server.rb +2 -0
- data/neo4j-core.gemspec +3 -0
- metadata +47 -3
- data/lib/neo4j-core/graph_json.rb +0 -35
|
@@ -36,54 +36,87 @@ module Neo4j
|
|
|
36
36
|
"Enumerable query: '#{@query}'"
|
|
37
37
|
end
|
|
38
38
|
|
|
39
|
-
def each
|
|
39
|
+
def each
|
|
40
40
|
@response.each_data_row do |row|
|
|
41
|
-
yield(row
|
|
42
|
-
|
|
43
|
-
|
|
41
|
+
yield struct_rows(row)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def struct_rows(row)
|
|
46
|
+
struct.new.tap do |result|
|
|
47
|
+
row.each_with_index { |value, i| result[columns[i]] = value }
|
|
44
48
|
end
|
|
45
49
|
end
|
|
46
50
|
end
|
|
47
51
|
|
|
48
|
-
|
|
52
|
+
EMPTY_STRING = ''
|
|
53
|
+
def to_struct_enumeration(cypher = EMPTY_STRING)
|
|
49
54
|
HashEnumeration.new(self, cypher)
|
|
50
55
|
end
|
|
51
56
|
|
|
52
|
-
def to_node_enumeration(cypher =
|
|
57
|
+
def to_node_enumeration(cypher = EMPTY_STRING, session = Neo4j::Session.current)
|
|
53
58
|
Enumerator.new do |yielder|
|
|
54
59
|
@result_index = 0
|
|
55
60
|
to_struct_enumeration(cypher).each do |row|
|
|
56
61
|
@row_index = 0
|
|
57
|
-
yielder << row
|
|
58
|
-
result[column] = map_row_value(value, session)
|
|
59
|
-
@row_index += 1
|
|
60
|
-
end
|
|
62
|
+
yielder << row_pair_in_struct(row, session)
|
|
61
63
|
@result_index += 1
|
|
62
64
|
end
|
|
63
65
|
end
|
|
64
66
|
end
|
|
65
67
|
|
|
68
|
+
def row_pair_in_struct(row, session)
|
|
69
|
+
@struct.new.tap do |result|
|
|
70
|
+
row.each_pair do |column, value|
|
|
71
|
+
result[column] = map_row_value(value, session)
|
|
72
|
+
@row_index += 1
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
66
77
|
def map_row_value(value, session)
|
|
67
|
-
if value.is_a?(Hash)
|
|
78
|
+
if value.is_a?(Hash) && looks_like_an_object?(value)
|
|
68
79
|
hash_value_as_object(value, session)
|
|
69
80
|
elsif value.is_a?(Array)
|
|
70
|
-
value.map { |v| map_row_value(v, session) }
|
|
81
|
+
value.map! { |v| map_row_value(v, session) }
|
|
71
82
|
else
|
|
72
83
|
value
|
|
73
84
|
end
|
|
74
85
|
end
|
|
75
86
|
|
|
76
87
|
def hash_value_as_object(value, session)
|
|
77
|
-
|
|
88
|
+
data = case
|
|
89
|
+
when transaction_response?
|
|
90
|
+
add_transaction_entity_id
|
|
91
|
+
mapped_rest_data
|
|
92
|
+
when value[:labels] || value[:type]
|
|
93
|
+
add_entity_id(value)
|
|
94
|
+
value
|
|
95
|
+
else
|
|
96
|
+
return value
|
|
97
|
+
end
|
|
98
|
+
basic_obj = (node?(value) ? CypherNode : CypherRelationship).new(session, data)
|
|
99
|
+
unwrapped? ? basic_obj : basic_obj.wrapper
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def looks_like_an_object?(value)
|
|
103
|
+
if transaction_response?
|
|
104
|
+
mapped_rest_data[:outgoing_relationships] || (mapped_rest_data[:start] && mapped_rest_data[:properties])
|
|
105
|
+
else
|
|
106
|
+
value[:labels] || value[:type]
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def unwrapped!
|
|
111
|
+
@_unwrapped_obj = true
|
|
112
|
+
end
|
|
78
113
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
end
|
|
86
|
-
(is_node ? CypherNode : CypherRelationship).new(session, data).wrapper
|
|
114
|
+
def unwrapped?
|
|
115
|
+
!!@_unwrapped_obj
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def node?(value)
|
|
119
|
+
transaction_response? ? !mapped_rest_data[:start] : value[:labels]
|
|
87
120
|
end
|
|
88
121
|
|
|
89
122
|
attr_reader :struct
|
|
@@ -95,17 +128,17 @@ module Neo4j
|
|
|
95
128
|
|
|
96
129
|
def entity_data(id = nil)
|
|
97
130
|
if @uncommited
|
|
98
|
-
data = @data.first[
|
|
99
|
-
data.is_a?(Hash) ? {
|
|
131
|
+
data = @data.first[:row].first
|
|
132
|
+
data.is_a?(Hash) ? {data: data, id: id} : data
|
|
100
133
|
else
|
|
101
134
|
data = @data[0][0]
|
|
102
135
|
data.is_a?(Hash) ? add_entity_id(data) : data
|
|
103
136
|
end
|
|
104
137
|
end
|
|
105
138
|
|
|
106
|
-
def first_data
|
|
139
|
+
def first_data
|
|
107
140
|
if @uncommited
|
|
108
|
-
|
|
141
|
+
@data.first[:row].first
|
|
109
142
|
# data.is_a?(Hash) ? {'data' => data, 'id' => id} : data
|
|
110
143
|
else
|
|
111
144
|
data = @data[0][0]
|
|
@@ -114,45 +147,57 @@ module Neo4j
|
|
|
114
147
|
end
|
|
115
148
|
|
|
116
149
|
def add_entity_id(data)
|
|
117
|
-
data
|
|
150
|
+
data[:id] = if data[:metadata] && data[:metadata][:id]
|
|
151
|
+
data[:metadata][:id]
|
|
152
|
+
else
|
|
153
|
+
self.class.id_from_url(data[:self])
|
|
154
|
+
end
|
|
155
|
+
data
|
|
118
156
|
end
|
|
119
157
|
|
|
120
158
|
def add_transaction_entity_id
|
|
121
|
-
mapped_rest_data
|
|
159
|
+
mapped_rest_data[:id] = mapped_rest_data[:self].split('/').last.to_i
|
|
160
|
+
mapped_rest_data
|
|
122
161
|
end
|
|
123
162
|
|
|
124
163
|
def error?
|
|
125
164
|
!!@error
|
|
126
165
|
end
|
|
127
166
|
|
|
167
|
+
RETRYABLE_ERROR_STATUSES = %w(DeadlockDetectedException AcquireLockTimeoutException ExternalResourceFailureException UnknownFailureException)
|
|
168
|
+
def retryable_error?
|
|
169
|
+
return unless error?
|
|
170
|
+
RETRYABLE_ERROR_STATUSES.include?(@error_status)
|
|
171
|
+
end
|
|
172
|
+
|
|
128
173
|
def data?
|
|
129
|
-
!response.body[
|
|
174
|
+
!response.body[:data].nil?
|
|
130
175
|
end
|
|
131
176
|
|
|
132
177
|
def raise_unless_response_code(code)
|
|
133
|
-
fail "Response code #{response.status}, expected #{code} for #{response.headers[
|
|
178
|
+
fail "Response code #{response.status}, expected #{code} for #{response.headers[:location]}, #{response.body}" unless response.status == code
|
|
134
179
|
end
|
|
135
180
|
|
|
136
181
|
def each_data_row
|
|
137
182
|
if @uncommited
|
|
138
|
-
data.each { |r| yield r[
|
|
183
|
+
data.each { |r| yield r[:row] }
|
|
139
184
|
else
|
|
140
185
|
data.each { |r| yield r }
|
|
141
186
|
end
|
|
142
187
|
end
|
|
143
188
|
|
|
144
|
-
def set_data(
|
|
145
|
-
@data = data
|
|
146
|
-
@columns = columns
|
|
147
|
-
@struct = columns.empty? ? Object.new : Struct.new(
|
|
189
|
+
def set_data(response)
|
|
190
|
+
@data = response[:data]
|
|
191
|
+
@columns = response[:columns]
|
|
192
|
+
@struct = @columns.empty? ? Object.new : Struct.new(*@columns.map(&:to_sym))
|
|
148
193
|
self
|
|
149
194
|
end
|
|
150
195
|
|
|
151
|
-
def set_error(
|
|
196
|
+
def set_error(error)
|
|
152
197
|
@error = true
|
|
153
|
-
@error_msg =
|
|
154
|
-
@error_status =
|
|
155
|
-
@error_code =
|
|
198
|
+
@error_msg = error[:message]
|
|
199
|
+
@error_status = error[:status] || error[:exception] || error[:code]
|
|
200
|
+
@error_code = error[:code] || error[:fullname]
|
|
156
201
|
self
|
|
157
202
|
end
|
|
158
203
|
|
|
@@ -170,9 +215,9 @@ module Neo4j
|
|
|
170
215
|
def self.create_with_no_tx(response)
|
|
171
216
|
case response.status
|
|
172
217
|
when 200
|
|
173
|
-
|
|
218
|
+
new(response).set_data(response.body)
|
|
174
219
|
when 400
|
|
175
|
-
|
|
220
|
+
new(response).set_error(response.body)
|
|
176
221
|
else
|
|
177
222
|
fail "Unknown response code #{response.status} for #{response.env[:url]}"
|
|
178
223
|
end
|
|
@@ -181,20 +226,18 @@ module Neo4j
|
|
|
181
226
|
def self.create_with_tx(response)
|
|
182
227
|
fail "Unknown response code #{response.status} for #{response.request_uri}" unless response.status == 200
|
|
183
228
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
cr.set_error(first_error['message'], first_error['status'], first_error['code'])
|
|
229
|
+
new(response, true).tap do |cr|
|
|
230
|
+
body = response.body
|
|
231
|
+
if body[:errors].empty?
|
|
232
|
+
cr.set_data(body[:results].first)
|
|
233
|
+
else
|
|
234
|
+
cr.set_error(body[:errors].first)
|
|
235
|
+
end
|
|
192
236
|
end
|
|
193
|
-
cr
|
|
194
237
|
end
|
|
195
238
|
|
|
196
239
|
def transaction_response?
|
|
197
|
-
response.respond_to?(
|
|
240
|
+
response.respond_to?(:body) && !response.body[:commit].nil?
|
|
198
241
|
end
|
|
199
242
|
|
|
200
243
|
def rest_data
|
|
@@ -203,7 +246,8 @@ module Neo4j
|
|
|
203
246
|
end
|
|
204
247
|
|
|
205
248
|
def rest_data_with_id
|
|
206
|
-
rest_data
|
|
249
|
+
rest_data[:id] = mapped_rest_data[:self].split('/').last.to_i
|
|
250
|
+
rest_data
|
|
207
251
|
end
|
|
208
252
|
|
|
209
253
|
class << self
|
|
@@ -219,7 +263,7 @@ module Neo4j
|
|
|
219
263
|
attr_reader :result_index
|
|
220
264
|
|
|
221
265
|
def mapped_rest_data
|
|
222
|
-
response.body[
|
|
266
|
+
response.body[:results][0][:data][result_index][:rest][row_index]
|
|
223
267
|
end
|
|
224
268
|
end
|
|
225
269
|
end
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
module Neo4j
|
|
2
2
|
module Server
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
Neo4j::Server::CypherSession.open(*url_opts)
|
|
3
|
+
Neo4j::Session.register_db(:server_db) do |endpoint_url, url_opts|
|
|
4
|
+
Neo4j::Server::CypherSession.open(endpoint_url, url_opts)
|
|
6
5
|
end
|
|
7
6
|
|
|
8
7
|
class CypherSession < Neo4j::Session
|
|
@@ -26,9 +25,10 @@ module Neo4j
|
|
|
26
25
|
init_params = params[:initialize] && params.delete(:initialize)
|
|
27
26
|
conn = Faraday.new(url, init_params) do |b|
|
|
28
27
|
b.request :basic_auth, params[:basic_auth][:username], params[:basic_auth][:password] if params[:basic_auth]
|
|
29
|
-
b.request :
|
|
28
|
+
b.request :multi_json
|
|
30
29
|
# b.response :logger
|
|
31
|
-
|
|
30
|
+
|
|
31
|
+
b.response :multi_json, symbolize_keys: true, content_type: 'application/json'
|
|
32
32
|
# b.use Faraday::Response::RaiseError
|
|
33
33
|
b.use Faraday::Adapter::NetHttpPersistent
|
|
34
34
|
# b.adapter Faraday.default_adapter
|
|
@@ -52,17 +52,14 @@ module Neo4j
|
|
|
52
52
|
end
|
|
53
53
|
|
|
54
54
|
def self.establish_session(root_data, connection)
|
|
55
|
-
data_url = root_data[
|
|
55
|
+
data_url = root_data[:data]
|
|
56
56
|
data_url << '/' unless data_url.nil? || data_url.end_with?('/')
|
|
57
57
|
CypherSession.new(data_url, connection)
|
|
58
58
|
end
|
|
59
59
|
|
|
60
60
|
def self.extract_basic_auth(url, params)
|
|
61
61
|
return unless url && URI(url).userinfo
|
|
62
|
-
params[:basic_auth] = {
|
|
63
|
-
username: URI(url).user,
|
|
64
|
-
password: URI(url).password
|
|
65
|
-
}
|
|
62
|
+
params[:basic_auth] = {username: URI(url).user, password: URI(url).password}
|
|
66
63
|
end
|
|
67
64
|
|
|
68
65
|
private_class_method :extract_basic_auth
|
|
@@ -80,12 +77,12 @@ module Neo4j
|
|
|
80
77
|
end
|
|
81
78
|
|
|
82
79
|
def version
|
|
83
|
-
resource_data ? resource_data[
|
|
80
|
+
resource_data ? resource_data[:neo4j_version] : ''
|
|
84
81
|
end
|
|
85
82
|
|
|
86
83
|
def initialize_resource(data_url)
|
|
87
84
|
response = @connection.get(data_url)
|
|
88
|
-
expect_response_code(response, 200)
|
|
85
|
+
expect_response_code!(response, 200)
|
|
89
86
|
data_resource = response.body
|
|
90
87
|
fail "No data_resource for #{response.body}" unless data_resource
|
|
91
88
|
# store the resource data
|
|
@@ -98,43 +95,27 @@ module Neo4j
|
|
|
98
95
|
end
|
|
99
96
|
|
|
100
97
|
def begin_tx
|
|
101
|
-
|
|
102
|
-
# Handle nested transaction "placebo transaction"
|
|
103
|
-
Neo4j::Transaction.current.push_nested!
|
|
104
|
-
else
|
|
105
|
-
wrap_resource(@connection)
|
|
106
|
-
end
|
|
98
|
+
Neo4j::Transaction.current ? Neo4j::Transaction.current.push_nested! : wrap_resource(@connection)
|
|
107
99
|
Neo4j::Transaction.current
|
|
108
100
|
end
|
|
109
101
|
|
|
110
102
|
def create_node(props = nil, labels = [])
|
|
111
|
-
id = _query_or_fail(cypher_string(labels, props), true, cypher_prop_list(props))
|
|
112
|
-
value = props.nil? ? id : {
|
|
103
|
+
id = _query_or_fail(cypher_string(labels, props), true, cypher_prop_list!(props))
|
|
104
|
+
value = props.nil? ? id : {id: id, metadata: {labels: labels}, data: props}
|
|
113
105
|
CypherNode.new(self, value)
|
|
114
106
|
end
|
|
115
107
|
|
|
116
108
|
def load_node(neo_id)
|
|
117
|
-
|
|
109
|
+
query.unwrapped.match(:n).where(n: {neo_id: neo_id}).pluck(:n).first
|
|
118
110
|
end
|
|
119
111
|
|
|
120
112
|
def load_relationship(neo_id)
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
return nil if cypher_response.data.nil? || cypher_response.data[0].nil?
|
|
126
|
-
data = if cypher_response.transaction_response?
|
|
127
|
-
cypher_response.rest_data_with_id
|
|
128
|
-
else
|
|
129
|
-
cypher_response.first_data
|
|
130
|
-
end
|
|
131
|
-
|
|
132
|
-
if cypher_response.error?
|
|
133
|
-
cypher_response.raise_error
|
|
134
|
-
elsif cypher_response.error_msg =~ /not found/ # Ugly that the Neo4j API gives us this error message
|
|
135
|
-
return nil
|
|
113
|
+
query.unwrapped.optional_match('(n)-[r]-()').where(r: {neo_id: neo_id}).pluck(:r).first
|
|
114
|
+
rescue Neo4j::Session::CypherError => cypher_error
|
|
115
|
+
if cypher_error.message.match(/not found$/)
|
|
116
|
+
nil
|
|
136
117
|
else
|
|
137
|
-
|
|
118
|
+
raise cypher_error
|
|
138
119
|
end
|
|
139
120
|
end
|
|
140
121
|
|
|
@@ -143,17 +124,17 @@ module Neo4j
|
|
|
143
124
|
end
|
|
144
125
|
|
|
145
126
|
def uniqueness_constraints(label)
|
|
146
|
-
schema_properties("
|
|
127
|
+
schema_properties("/db/data/schema/constraint/#{label}/uniqueness")
|
|
147
128
|
end
|
|
148
129
|
|
|
149
130
|
def indexes(label)
|
|
150
|
-
schema_properties("
|
|
131
|
+
schema_properties("/db/data/schema/index/#{label}")
|
|
151
132
|
end
|
|
152
133
|
|
|
153
134
|
def schema_properties(query_string)
|
|
154
135
|
response = @connection.get(query_string)
|
|
155
|
-
expect_response_code(response, 200)
|
|
156
|
-
{property_keys: response.body.map { |row| row[
|
|
136
|
+
expect_response_code!(response, 200)
|
|
137
|
+
{property_keys: response.body.map! { |row| row[:property_keys].map(&:to_sym) }}
|
|
157
138
|
end
|
|
158
139
|
|
|
159
140
|
def find_all_nodes(label_name)
|
|
@@ -163,17 +144,15 @@ module Neo4j
|
|
|
163
144
|
def find_nodes(label_name, key, value)
|
|
164
145
|
value = "'#{value}'" if value.is_a? String
|
|
165
146
|
|
|
166
|
-
response = _query_or_fail
|
|
167
|
-
MATCH (n:`#{label_name}`)
|
|
168
|
-
WHERE n.#{key} = #{value}
|
|
169
|
-
RETURN ID(n)
|
|
170
|
-
CYPHER
|
|
147
|
+
response = _query_or_fail("MATCH (n:`#{label_name}`) WHERE n.#{key} = #{value} RETURN ID(n)")
|
|
171
148
|
search_result_to_enumerable_first_column(response)
|
|
172
149
|
end
|
|
173
150
|
|
|
174
151
|
def query(*args)
|
|
175
152
|
if [[String], [String, Hash]].include?(args.map(&:class))
|
|
176
|
-
query
|
|
153
|
+
query = args[0]
|
|
154
|
+
params = args[1]
|
|
155
|
+
|
|
177
156
|
response = _query(query, params)
|
|
178
157
|
response.raise_error if response.error?
|
|
179
158
|
response.to_node_enumeration(query)
|
|
@@ -183,77 +162,85 @@ module Neo4j
|
|
|
183
162
|
end
|
|
184
163
|
end
|
|
185
164
|
|
|
186
|
-
def _query_data(
|
|
187
|
-
r = _query_or_fail(
|
|
188
|
-
|
|
189
|
-
Neo4j::Transaction.current ? r : r['data']
|
|
165
|
+
def _query_data(query)
|
|
166
|
+
r = _query_or_fail(query, true)
|
|
167
|
+
Neo4j::Transaction.current ? r : r[:data]
|
|
190
168
|
end
|
|
191
169
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
170
|
+
DEFAULT_RETRY_COUNT = ENV['NEO4J_RETRY_COUNT'].nil? ? 10 : ENV['NEO4J_RETRY_COUNT'].to_i
|
|
171
|
+
|
|
172
|
+
def _query_or_fail(query, single_row = false, params = {}, retry_count = DEFAULT_RETRY_COUNT)
|
|
173
|
+
query, params = query_and_params(query, params)
|
|
174
|
+
|
|
175
|
+
response = _query(query, params)
|
|
176
|
+
if response.error?
|
|
177
|
+
_retry_or_raise(query, params, single_row, retry_count, response)
|
|
178
|
+
else
|
|
179
|
+
single_row ? response.first_data : response
|
|
197
180
|
end
|
|
181
|
+
end
|
|
198
182
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
183
|
+
def query_and_params(query_or_query_string, params)
|
|
184
|
+
if query_or_query_string.is_a?(::Neo4j::Core::Query)
|
|
185
|
+
cypher = query_or_query_string.to_cypher
|
|
186
|
+
[cypher, query_or_query_string.send(:merge_params).merge(params)]
|
|
187
|
+
else
|
|
188
|
+
[query_or_query_string, params]
|
|
189
|
+
end
|
|
202
190
|
end
|
|
203
191
|
|
|
204
|
-
def
|
|
205
|
-
response
|
|
206
|
-
|
|
207
|
-
response.entity_data(id)
|
|
192
|
+
def _retry_or_raise(query, params, single_row, retry_count, response)
|
|
193
|
+
response.raise_error unless response.retryable_error?
|
|
194
|
+
retry_count > 0 ? _query_or_fail(query, single_row, params, retry_count - 1) : response.raise_error
|
|
208
195
|
end
|
|
209
196
|
|
|
210
|
-
def
|
|
211
|
-
|
|
197
|
+
def _query_entity_data(query, id = nil, params = {})
|
|
198
|
+
_query_response(query, params).entity_data(id)
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
def _query_response(query, params = {})
|
|
202
|
+
_query(query, params).tap do |response|
|
|
203
|
+
response.raise_error if response.error?
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
def _query(query, params = {})
|
|
208
|
+
query, params = query_and_params(query, params)
|
|
209
|
+
|
|
212
210
|
curr_tx = Neo4j::Transaction.current
|
|
213
211
|
if curr_tx
|
|
214
|
-
curr_tx._query(
|
|
212
|
+
curr_tx._query(query, params)
|
|
215
213
|
else
|
|
216
|
-
url = resource_url(
|
|
217
|
-
|
|
218
|
-
response = @connection.post(url,
|
|
214
|
+
url = resource_url(:cypher)
|
|
215
|
+
query = params.nil? ? {'query' => query} : {'query' => query, 'params' => params}
|
|
216
|
+
response = @connection.post(url, query)
|
|
219
217
|
CypherResponse.create_with_no_tx(response)
|
|
220
218
|
end
|
|
221
219
|
end
|
|
222
220
|
|
|
223
221
|
def search_result_to_enumerable_first_column(response)
|
|
224
222
|
return [] unless response.data
|
|
225
|
-
if Neo4j::Transaction.current
|
|
226
|
-
search_result_to_enumerable_first_column_with_tx(response)
|
|
227
|
-
else
|
|
228
|
-
search_result_to_enumerable_first_column_without_tx(response)
|
|
229
|
-
end
|
|
230
|
-
end
|
|
231
223
|
|
|
232
|
-
def search_result_to_enumerable_first_column_with_tx(response)
|
|
233
224
|
Enumerator.new do |yielder|
|
|
234
225
|
response.data.each do |data|
|
|
235
|
-
|
|
236
|
-
|
|
226
|
+
if Neo4j::Transaction.current
|
|
227
|
+
data[:row].each do |id|
|
|
228
|
+
yielder << CypherNode.new(self, id).wrapper
|
|
229
|
+
end
|
|
230
|
+
else
|
|
231
|
+
yielder << CypherNode.new(self, data[0]).wrapper
|
|
237
232
|
end
|
|
238
233
|
end
|
|
239
234
|
end
|
|
240
235
|
end
|
|
241
236
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
def map_column(key, map, data)
|
|
251
|
-
if map[key] == :node
|
|
252
|
-
CypherNode.new(self, data).wrapper
|
|
253
|
-
elsif map[key] == :rel || map[:key] || :relationship
|
|
254
|
-
CypherRelationship.new(self, data)
|
|
255
|
-
else
|
|
256
|
-
data
|
|
237
|
+
EMPTY = ''
|
|
238
|
+
def self.log_with
|
|
239
|
+
clear, yellow, cyan = %W(\e[0m \e[33m \e[36m)
|
|
240
|
+
ActiveSupport::Notifications.subscribe('neo4j.cypher_query') do |_, start, finish, _id, payload|
|
|
241
|
+
ms = (finish - start) * 1000
|
|
242
|
+
params_string = (payload[:params].size > 0 ? "| #{payload[:params].inspect}" : EMPTY)
|
|
243
|
+
yield(" #{cyan}#{payload[:context]}#{clear} #{yellow}#{ms.round}ms#{clear} #{payload[:cypher]} #{params_string}")
|
|
257
244
|
end
|
|
258
245
|
end
|
|
259
246
|
end
|
|
@@ -2,9 +2,9 @@ module Neo4j
|
|
|
2
2
|
module Server
|
|
3
3
|
# The CypherTransaction object lifecycle is as follows:
|
|
4
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 :
|
|
5
|
+
# * The first query within the transaction sets the commit and execution addresses, :commit_url and :query_url.
|
|
6
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 `
|
|
7
|
+
# * `close` is called to end the transaction. It calls `commit` or `delete`.
|
|
8
8
|
#
|
|
9
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
10
|
class CypherTransaction
|
|
@@ -12,7 +12,7 @@ module Neo4j
|
|
|
12
12
|
include Neo4j::Core::CypherTranslator
|
|
13
13
|
include Resource
|
|
14
14
|
|
|
15
|
-
attr_reader :commit_url, :
|
|
15
|
+
attr_reader :commit_url, :query_url, :base_url, :connection
|
|
16
16
|
|
|
17
17
|
def initialize(url, session_connection)
|
|
18
18
|
@base_url = url
|
|
@@ -26,49 +26,58 @@ module Neo4j
|
|
|
26
26
|
statement = {statement: cypher_query, parameters: params, resultDataContents: ROW_REST}
|
|
27
27
|
body = {statements: [statement]}
|
|
28
28
|
|
|
29
|
-
response =
|
|
30
|
-
|
|
29
|
+
response = @query_url ? query(body) : start(body)
|
|
30
|
+
|
|
31
|
+
create_cypher_response(response)
|
|
31
32
|
end
|
|
32
33
|
|
|
33
|
-
def
|
|
34
|
-
|
|
34
|
+
def start(body)
|
|
35
|
+
request(:post, @base_url, 201, body).tap do |response|
|
|
36
|
+
@commit_url = response.body[:commit]
|
|
37
|
+
@query_url = response.headers[:Location]
|
|
38
|
+
|
|
39
|
+
fail "NO ENDPOINT URL #{connection} : HEAD: #{response.headers.inspect}" if !@query_url || @query_url.empty?
|
|
40
|
+
|
|
41
|
+
init_resource_data(response.body, @base_url)
|
|
42
|
+
end
|
|
35
43
|
end
|
|
36
44
|
|
|
37
|
-
def
|
|
38
|
-
|
|
45
|
+
def query(body)
|
|
46
|
+
request(:post, @query_url, 200, body)
|
|
39
47
|
end
|
|
40
48
|
|
|
41
|
-
|
|
49
|
+
def delete
|
|
50
|
+
return empty_response if !@commit_url || expired?
|
|
42
51
|
|
|
43
|
-
|
|
44
|
-
return empty_response if !commit_url || expired?
|
|
45
|
-
response = connection.send(action, endpoint, headers)
|
|
46
|
-
expect_response_code(response, 200)
|
|
47
|
-
response
|
|
52
|
+
request(:delete, @query_url, 200, nil, resource_headers)
|
|
48
53
|
end
|
|
49
54
|
|
|
50
|
-
def
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
@
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
55
|
+
def commit
|
|
56
|
+
return empty_response if !@commit_url || expired?
|
|
57
|
+
|
|
58
|
+
request(:post, @commit_url, 200, nil, resource_headers)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
private
|
|
62
|
+
|
|
63
|
+
def request(action, endpoint_url, expected_code = 200, body = nil, headers = {})
|
|
64
|
+
connection.send(action, endpoint_url, body, headers).tap do |response|
|
|
65
|
+
expect_response_code!(response, expected_code)
|
|
66
|
+
end
|
|
58
67
|
end
|
|
59
68
|
|
|
60
|
-
def
|
|
61
|
-
first_result = response.body[
|
|
69
|
+
def create_cypher_response(response)
|
|
70
|
+
first_result = response.body[:results][0]
|
|
62
71
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
72
|
+
CypherResponse.new(response, true).tap do |cypher_response|
|
|
73
|
+
if response.body[:errors].empty?
|
|
74
|
+
cypher_response.set_data(first_result)
|
|
75
|
+
else
|
|
76
|
+
first_error = response.body[:errors].first
|
|
77
|
+
mark_expired if first_error[:message].match(/Unrecognized transaction id/)
|
|
78
|
+
cypher_response.set_error(first_error)
|
|
79
|
+
end
|
|
70
80
|
end
|
|
71
|
-
cr
|
|
72
81
|
end
|
|
73
82
|
|
|
74
83
|
def empty_response
|