neo4j-core 4.0.7 → 5.0.0.rc.1
Sign up to get free protection for your applications and to get access to all the features.
- 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 +177 -109
- 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_node.rb +26 -28
- data/lib/neo4j-embedded/embedded_session.rb +7 -6
- 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 +87 -51
- 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 +4 -1
- metadata +50 -6
- data/lib/neo4j-core/graph_json.rb +0 -35
@@ -36,54 +36,79 @@ 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
78
|
if value.is_a?(Hash)
|
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 unwrapped!
|
103
|
+
@_unwrapped_obj = true
|
104
|
+
end
|
105
|
+
|
106
|
+
def unwrapped?
|
107
|
+
!!@_unwrapped_obj
|
108
|
+
end
|
78
109
|
|
79
|
-
|
80
|
-
|
81
|
-
[!mapped_rest_data['start'], mapped_rest_data]
|
82
|
-
elsif value['labels'] || value['type']
|
83
|
-
add_entity_id(value)
|
84
|
-
[value['labels'], value]
|
85
|
-
end
|
86
|
-
(is_node ? CypherNode : CypherRelationship).new(session, data).wrapper
|
110
|
+
def node?(value)
|
111
|
+
transaction_response? ? !mapped_rest_data[:start] : value[:labels]
|
87
112
|
end
|
88
113
|
|
89
114
|
attr_reader :struct
|
@@ -95,17 +120,17 @@ module Neo4j
|
|
95
120
|
|
96
121
|
def entity_data(id = nil)
|
97
122
|
if @uncommited
|
98
|
-
data = @data.first[
|
99
|
-
data.is_a?(Hash) ? {
|
123
|
+
data = @data.first[:row].first
|
124
|
+
data.is_a?(Hash) ? {data: data, id: id} : data
|
100
125
|
else
|
101
126
|
data = @data[0][0]
|
102
127
|
data.is_a?(Hash) ? add_entity_id(data) : data
|
103
128
|
end
|
104
129
|
end
|
105
130
|
|
106
|
-
def first_data
|
131
|
+
def first_data
|
107
132
|
if @uncommited
|
108
|
-
|
133
|
+
@data.first[:row].first
|
109
134
|
# data.is_a?(Hash) ? {'data' => data, 'id' => id} : data
|
110
135
|
else
|
111
136
|
data = @data[0][0]
|
@@ -114,45 +139,57 @@ module Neo4j
|
|
114
139
|
end
|
115
140
|
|
116
141
|
def add_entity_id(data)
|
117
|
-
data
|
142
|
+
data[:id] = if data[:metadata] && data[:metadata][:id]
|
143
|
+
data[:metadata][:id]
|
144
|
+
else
|
145
|
+
self.class.id_from_url(data[:self])
|
146
|
+
end
|
147
|
+
data
|
118
148
|
end
|
119
149
|
|
120
150
|
def add_transaction_entity_id
|
121
|
-
mapped_rest_data
|
151
|
+
mapped_rest_data[:id] = mapped_rest_data[:self].split('/').last.to_i
|
152
|
+
mapped_rest_data
|
122
153
|
end
|
123
154
|
|
124
155
|
def error?
|
125
156
|
!!@error
|
126
157
|
end
|
127
158
|
|
159
|
+
RETRYABLE_ERROR_STATUSES = %w(DeadlockDetectedException AcquireLockTimeoutException ExternalResourceFailureException UnknownFailureException)
|
160
|
+
def retryable_error?
|
161
|
+
return unless error?
|
162
|
+
RETRYABLE_ERROR_STATUSES.include?(@error_status)
|
163
|
+
end
|
164
|
+
|
128
165
|
def data?
|
129
|
-
!response.body[
|
166
|
+
!response.body[:data].nil?
|
130
167
|
end
|
131
168
|
|
132
169
|
def raise_unless_response_code(code)
|
133
|
-
fail "Response code #{response.status}, expected #{code} for #{response.headers[
|
170
|
+
fail "Response code #{response.status}, expected #{code} for #{response.headers[:location]}, #{response.body}" unless response.status == code
|
134
171
|
end
|
135
172
|
|
136
173
|
def each_data_row
|
137
174
|
if @uncommited
|
138
|
-
data.each { |r| yield r[
|
175
|
+
data.each { |r| yield r[:row] }
|
139
176
|
else
|
140
177
|
data.each { |r| yield r }
|
141
178
|
end
|
142
179
|
end
|
143
180
|
|
144
|
-
def set_data(
|
145
|
-
@data = data
|
146
|
-
@columns = columns
|
147
|
-
@struct = columns.empty? ? Object.new : Struct.new(
|
181
|
+
def set_data(response)
|
182
|
+
@data = response[:data]
|
183
|
+
@columns = response[:columns]
|
184
|
+
@struct = @columns.empty? ? Object.new : Struct.new(*@columns.map(&:to_sym))
|
148
185
|
self
|
149
186
|
end
|
150
187
|
|
151
|
-
def set_error(
|
188
|
+
def set_error(error)
|
152
189
|
@error = true
|
153
|
-
@error_msg =
|
154
|
-
@error_status =
|
155
|
-
@error_code =
|
190
|
+
@error_msg = error[:message]
|
191
|
+
@error_status = error[:status] || error[:exception] || error[:code]
|
192
|
+
@error_code = error[:code] || error[:fullname]
|
156
193
|
self
|
157
194
|
end
|
158
195
|
|
@@ -170,9 +207,9 @@ module Neo4j
|
|
170
207
|
def self.create_with_no_tx(response)
|
171
208
|
case response.status
|
172
209
|
when 200
|
173
|
-
|
210
|
+
new(response).set_data(response.body)
|
174
211
|
when 400
|
175
|
-
|
212
|
+
new(response).set_error(response.body)
|
176
213
|
else
|
177
214
|
fail "Unknown response code #{response.status} for #{response.env[:url]}"
|
178
215
|
end
|
@@ -181,20 +218,18 @@ module Neo4j
|
|
181
218
|
def self.create_with_tx(response)
|
182
219
|
fail "Unknown response code #{response.status} for #{response.request_uri}" unless response.status == 200
|
183
220
|
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
cr.set_error(first_error['message'], first_error['status'], first_error['code'])
|
221
|
+
new(response, true).tap do |cr|
|
222
|
+
body = response.body
|
223
|
+
if body[:errors].empty?
|
224
|
+
cr.set_data(body[:results].first)
|
225
|
+
else
|
226
|
+
cr.set_error(body[:errors].first)
|
227
|
+
end
|
192
228
|
end
|
193
|
-
cr
|
194
229
|
end
|
195
230
|
|
196
231
|
def transaction_response?
|
197
|
-
response.respond_to?(
|
232
|
+
response.respond_to?(:body) && !response.body[:commit].nil?
|
198
233
|
end
|
199
234
|
|
200
235
|
def rest_data
|
@@ -203,7 +238,8 @@ module Neo4j
|
|
203
238
|
end
|
204
239
|
|
205
240
|
def rest_data_with_id
|
206
|
-
rest_data
|
241
|
+
rest_data[:id] = mapped_rest_data[:self].split('/').last.to_i
|
242
|
+
rest_data
|
207
243
|
end
|
208
244
|
|
209
245
|
class << self
|
@@ -219,7 +255,7 @@ module Neo4j
|
|
219
255
|
attr_reader :result_index
|
220
256
|
|
221
257
|
def mapped_rest_data
|
222
|
-
response.body[
|
258
|
+
response.body[:results][0][:data][result_index][:rest][row_index]
|
223
259
|
end
|
224
260
|
end
|
225
261
|
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
|