neo4j-core 8.1.4 → 9.0.0.alpha.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/README.md +71 -8
- data/lib/neo4j-core.rb +3 -49
- data/lib/neo4j/core.rb +4 -0
- data/lib/neo4j/core/config.rb +13 -0
- data/lib/neo4j/core/cypher_session/adaptors.rb +15 -15
- data/lib/neo4j/core/cypher_session/adaptors/bolt.rb +39 -48
- data/lib/neo4j/core/cypher_session/adaptors/bolt/chunk_writer_io.rb +0 -4
- data/lib/neo4j/core/cypher_session/adaptors/bolt/pack_stream.rb +7 -3
- data/lib/neo4j/core/cypher_session/adaptors/embedded.rb +1 -2
- data/lib/neo4j/core/cypher_session/adaptors/has_uri.rb +4 -0
- data/lib/neo4j/core/cypher_session/adaptors/http.rb +1 -3
- data/lib/neo4j/core/cypher_session/responses.rb +1 -1
- data/lib/neo4j/core/cypher_session/responses/bolt.rb +0 -17
- data/lib/neo4j/core/cypher_session/responses/embedded.rb +9 -7
- data/lib/neo4j/core/cypher_session/responses/http.rb +3 -4
- data/lib/neo4j/core/cypher_session/transactions.rb +2 -0
- data/lib/{neo4j-core → neo4j/core}/helpers.rb +1 -14
- data/lib/neo4j/core/logging.rb +44 -0
- data/lib/{neo4j-core → neo4j/core}/query.rb +7 -6
- data/lib/{neo4j-core → neo4j/core}/query_clauses.rb +9 -16
- data/lib/{neo4j-core → neo4j/core}/query_find_in_batches.rb +3 -5
- data/lib/{neo4j-core → neo4j/core}/version.rb +1 -1
- data/lib/neo4j/transaction.rb +6 -8
- data/neo4j-core.gemspec +13 -11
- metadata +46 -50
- data/lib/ext/kernel.rb +0 -9
- data/lib/neo4j-core/active_entity.rb +0 -11
- data/lib/neo4j-core/label.rb +0 -9
- data/lib/neo4j-embedded.rb +0 -16
- data/lib/neo4j-embedded/cypher_response.rb +0 -71
- data/lib/neo4j-embedded/embedded_database.rb +0 -26
- data/lib/neo4j-embedded/embedded_ha_session.rb +0 -30
- data/lib/neo4j-embedded/embedded_impermanent_session.rb +0 -17
- data/lib/neo4j-embedded/embedded_label.rb +0 -88
- data/lib/neo4j-embedded/embedded_node.rb +0 -206
- data/lib/neo4j-embedded/embedded_relationship.rb +0 -77
- data/lib/neo4j-embedded/embedded_session.rb +0 -203
- data/lib/neo4j-embedded/embedded_transaction.rb +0 -30
- data/lib/neo4j-embedded/label.rb +0 -66
- data/lib/neo4j-embedded/property.rb +0 -106
- data/lib/neo4j-embedded/to_java.rb +0 -44
- data/lib/neo4j-server.rb +0 -12
- data/lib/neo4j-server/cypher_label.rb +0 -35
- data/lib/neo4j-server/cypher_node.rb +0 -221
- data/lib/neo4j-server/cypher_relationship.rb +0 -142
- data/lib/neo4j-server/cypher_response.rb +0 -248
- data/lib/neo4j-server/cypher_session.rb +0 -263
- data/lib/neo4j-server/cypher_transaction.rb +0 -100
- data/lib/neo4j-server/label.rb +0 -40
- data/lib/neo4j-server/resource.rb +0 -57
- data/lib/neo4j/entity_equality.rb +0 -8
- data/lib/neo4j/entity_marshal.rb +0 -20
- data/lib/neo4j/label.rb +0 -90
- data/lib/neo4j/node.rb +0 -216
- data/lib/neo4j/property_container.rb +0 -17
- data/lib/neo4j/property_validator.rb +0 -22
- data/lib/neo4j/relationship.rb +0 -161
- data/lib/neo4j/session.rb +0 -222
@@ -1,142 +0,0 @@
|
|
1
|
-
module Neo4j
|
2
|
-
module Server
|
3
|
-
class CypherRelationship < Neo4j::Relationship
|
4
|
-
include Neo4j::Server::Resource
|
5
|
-
include Neo4j::Core::ActiveEntity
|
6
|
-
|
7
|
-
MARSHAL_INSTANCE_VARIABLES = %i[@rel_type @props @start_node_neo_id @end_node_neo_id @id]
|
8
|
-
|
9
|
-
def initialize(session, value)
|
10
|
-
@session = session
|
11
|
-
@response_hash = value
|
12
|
-
@rel_type = @response_hash[:type]
|
13
|
-
@props = @response_hash[:data]
|
14
|
-
@start_node_neo_id = neo_id_integer(@response_hash[:start])
|
15
|
-
@end_node_neo_id = neo_id_integer(@response_hash[:end])
|
16
|
-
@id = @response_hash[:id]
|
17
|
-
end
|
18
|
-
|
19
|
-
def ==(other)
|
20
|
-
other.class == self.class && other.neo_id == neo_id
|
21
|
-
end
|
22
|
-
alias eql? ==
|
23
|
-
|
24
|
-
attr_reader :id
|
25
|
-
|
26
|
-
def neo_id
|
27
|
-
id
|
28
|
-
end
|
29
|
-
|
30
|
-
def inspect
|
31
|
-
"CypherRelationship #{neo_id}"
|
32
|
-
end
|
33
|
-
|
34
|
-
def load_resource
|
35
|
-
return if resource_data_present?
|
36
|
-
|
37
|
-
@resource_data = @session._query_or_fail("#{match_start} RETURN n", true, neo_id: neo_id) # r.first_data
|
38
|
-
end
|
39
|
-
|
40
|
-
attr_reader :start_node_neo_id
|
41
|
-
|
42
|
-
attr_reader :end_node_neo_id
|
43
|
-
|
44
|
-
def _start_node_id
|
45
|
-
@start_node_neo_id ||= get_node_id(:start)
|
46
|
-
end
|
47
|
-
|
48
|
-
def _end_node_id
|
49
|
-
@end_node_neo_id ||= get_node_id(:end)
|
50
|
-
end
|
51
|
-
|
52
|
-
def _start_node
|
53
|
-
@_start_node ||= Neo4j::Node._load(start_node_neo_id)
|
54
|
-
end
|
55
|
-
|
56
|
-
def _end_node
|
57
|
-
load_resource
|
58
|
-
@_end_node ||= Neo4j::Node._load(end_node_neo_id)
|
59
|
-
end
|
60
|
-
|
61
|
-
def get_node_id(direction)
|
62
|
-
load_resource
|
63
|
-
resource_url_id(resource_url(direction))
|
64
|
-
end
|
65
|
-
|
66
|
-
def get_property(key)
|
67
|
-
@session._query_or_fail("#{match_start} RETURN n.`#{key}`", true, neo_id: neo_id)
|
68
|
-
end
|
69
|
-
|
70
|
-
def set_property(key, value)
|
71
|
-
@session._query_or_fail("#{match_start} SET n.`#{key}` = {value}", false, value: value, neo_id: neo_id)
|
72
|
-
end
|
73
|
-
|
74
|
-
def remove_property(key)
|
75
|
-
@session._query_or_fail("#{match_start} REMOVE n.`#{key}`", false, neo_id: neo_id)
|
76
|
-
end
|
77
|
-
|
78
|
-
# (see Neo4j::Relationship#props)
|
79
|
-
def props
|
80
|
-
if @props
|
81
|
-
@props
|
82
|
-
else
|
83
|
-
hash = @session._query_entity_data("#{match_start} RETURN n", nil, neo_id: neo_id)
|
84
|
-
@props = Hash[hash[:data].map { |k, v| [k, v] }]
|
85
|
-
end
|
86
|
-
end
|
87
|
-
|
88
|
-
# (see Neo4j::Relationship#props=)
|
89
|
-
def props=(properties)
|
90
|
-
@session._query_or_fail("#{match_start} SET n = { props }", false, props: properties, neo_id: neo_id)
|
91
|
-
properties
|
92
|
-
end
|
93
|
-
|
94
|
-
# (see Neo4j::Relationship#update_props)
|
95
|
-
def update_props(properties)
|
96
|
-
return if properties.empty?
|
97
|
-
|
98
|
-
params = {}
|
99
|
-
q = "#{match_start} SET " + properties.keys.each_with_index.map do |k, _i|
|
100
|
-
param = k.to_s.tr_s('^a-zA-Z0-9', '_').gsub(/^_+|_+$/, '')
|
101
|
-
params[param] = properties[k]
|
102
|
-
|
103
|
-
"n.`#{k}`= {#{param}}"
|
104
|
-
end.join(',')
|
105
|
-
|
106
|
-
@session._query_or_fail(q, false, params.merge(neo_id: neo_id))
|
107
|
-
|
108
|
-
properties
|
109
|
-
end
|
110
|
-
|
111
|
-
def rel_type
|
112
|
-
@rel_type.to_sym
|
113
|
-
end
|
114
|
-
|
115
|
-
def del
|
116
|
-
@session._query("#{match_start} DELETE n", neo_id: neo_id)
|
117
|
-
end
|
118
|
-
alias delete del
|
119
|
-
alias destroy del
|
120
|
-
|
121
|
-
def exist?
|
122
|
-
response = @session._query("#{match_start} RETURN n", neo_id: neo_id)
|
123
|
-
# binding.pry
|
124
|
-
(response.data.nil? || response.data.empty?) ? false : true
|
125
|
-
end
|
126
|
-
|
127
|
-
private
|
128
|
-
|
129
|
-
def match_start(identifier = 'n')
|
130
|
-
"MATCH (node)-[#{identifier}]-() WHERE ID(#{identifier}) = {neo_id}"
|
131
|
-
end
|
132
|
-
|
133
|
-
def resource_data_present?
|
134
|
-
!resource_data.nil? && !resource_data.empty?
|
135
|
-
end
|
136
|
-
|
137
|
-
def neo_id_integer(id_or_url)
|
138
|
-
id_or_url.is_a?(Integer) ? id_or_url : id_or_url.split('/').last.to_i
|
139
|
-
end
|
140
|
-
end
|
141
|
-
end
|
142
|
-
end
|
@@ -1,248 +0,0 @@
|
|
1
|
-
module Neo4j
|
2
|
-
module Server
|
3
|
-
class CypherResponse
|
4
|
-
attr_reader :data, :columns, :error_msg, :error_status, :error_code, :response
|
5
|
-
|
6
|
-
class ResponseError < StandardError
|
7
|
-
attr_reader :status, :code
|
8
|
-
|
9
|
-
def initialize(msg, status, code)
|
10
|
-
super(msg)
|
11
|
-
@status = status
|
12
|
-
@code = code
|
13
|
-
end
|
14
|
-
end
|
15
|
-
|
16
|
-
class ConstraintViolationError < ResponseError; end
|
17
|
-
|
18
|
-
class HashEnumeration
|
19
|
-
include Enumerable
|
20
|
-
extend Forwardable
|
21
|
-
def_delegator :@response, :error_msg
|
22
|
-
def_delegator :@response, :error_status
|
23
|
-
def_delegator :@response, :error_code
|
24
|
-
def_delegator :@response, :columns
|
25
|
-
def_delegator :@response, :struct
|
26
|
-
|
27
|
-
def initialize(response, query)
|
28
|
-
@response = response
|
29
|
-
@query = query
|
30
|
-
end
|
31
|
-
|
32
|
-
def to_s
|
33
|
-
@query
|
34
|
-
end
|
35
|
-
|
36
|
-
def inspect
|
37
|
-
"Enumerable query: '#{@query}'"
|
38
|
-
end
|
39
|
-
|
40
|
-
def each
|
41
|
-
@response.each_data_row { |row| yield struct_rows(row) }
|
42
|
-
end
|
43
|
-
|
44
|
-
def struct_rows(row)
|
45
|
-
struct.new.tap do |result|
|
46
|
-
row.each_with_index { |value, i| result[columns[i]] = value }
|
47
|
-
end
|
48
|
-
end
|
49
|
-
end
|
50
|
-
|
51
|
-
EMPTY_STRING = ''
|
52
|
-
def to_struct_enumeration(cypher = EMPTY_STRING)
|
53
|
-
HashEnumeration.new(self, cypher)
|
54
|
-
end
|
55
|
-
|
56
|
-
def to_node_enumeration(cypher = EMPTY_STRING, session = Neo4j::Session.current)
|
57
|
-
Enumerator.new do |yielder|
|
58
|
-
to_struct_enumeration(cypher).each do |row|
|
59
|
-
yielder << row_pair_in_struct(row, session)
|
60
|
-
end
|
61
|
-
end
|
62
|
-
end
|
63
|
-
|
64
|
-
def row_pair_in_struct(row, session)
|
65
|
-
@struct.new.tap do |result|
|
66
|
-
row.each_pair do |column, value|
|
67
|
-
result[column] = map_row_value(value, session)
|
68
|
-
end
|
69
|
-
end
|
70
|
-
end
|
71
|
-
|
72
|
-
def map_row_value(value, session)
|
73
|
-
if value.is_a?(Hash) && looks_like_an_object?(value)
|
74
|
-
hash_value_as_object(value, session)
|
75
|
-
elsif value.is_a?(Array)
|
76
|
-
value.map! { |v| map_row_value(v, session) }
|
77
|
-
else
|
78
|
-
value
|
79
|
-
end
|
80
|
-
end
|
81
|
-
|
82
|
-
def hash_value_as_object(value, session)
|
83
|
-
return value unless %i[node relationship].include?(identify_entity(value))
|
84
|
-
add_entity_id(value)
|
85
|
-
|
86
|
-
basic_obj = (node?(value) ? CypherNode : CypherRelationship).new(session, value)
|
87
|
-
unwrapped? ? basic_obj : basic_obj.wrapper
|
88
|
-
end
|
89
|
-
|
90
|
-
def identify_entity(data)
|
91
|
-
self_string = data[:self]
|
92
|
-
if self_string
|
93
|
-
if self_string.include?('node')
|
94
|
-
:node
|
95
|
-
elsif self_string.include?('relationship')
|
96
|
-
:relationship
|
97
|
-
end
|
98
|
-
elsif %i[nodes relationships start end length].all? { |k| data.key?(k) }
|
99
|
-
:path
|
100
|
-
end
|
101
|
-
end
|
102
|
-
|
103
|
-
def looks_like_an_object?(value)
|
104
|
-
value[:labels] || value[:type]
|
105
|
-
end
|
106
|
-
|
107
|
-
def unwrapped!
|
108
|
-
@_unwrapped_obj = true
|
109
|
-
end
|
110
|
-
|
111
|
-
def unwrapped?
|
112
|
-
!!@_unwrapped_obj
|
113
|
-
end
|
114
|
-
|
115
|
-
def node?(value)
|
116
|
-
value[:labels]
|
117
|
-
end
|
118
|
-
|
119
|
-
attr_reader :struct
|
120
|
-
|
121
|
-
def initialize(response, uncommited = false)
|
122
|
-
@response = response
|
123
|
-
@uncommited = uncommited
|
124
|
-
end
|
125
|
-
|
126
|
-
def entity_data(id = nil)
|
127
|
-
if @uncommited
|
128
|
-
data = @data.first[:row].first
|
129
|
-
data.is_a?(Hash) ? {data: data, id: id} : data
|
130
|
-
else
|
131
|
-
data = @data[0][0]
|
132
|
-
data.is_a?(Hash) ? add_entity_id(data) : data
|
133
|
-
end
|
134
|
-
end
|
135
|
-
|
136
|
-
def first_data
|
137
|
-
if @uncommited
|
138
|
-
@data.first[:row].first
|
139
|
-
else
|
140
|
-
data = @data[0][0]
|
141
|
-
data.is_a?(Hash) ? add_entity_id(data) : data
|
142
|
-
end
|
143
|
-
end
|
144
|
-
|
145
|
-
def add_entity_id(data)
|
146
|
-
data[:id] = if data[:metadata] && data[:metadata][:id]
|
147
|
-
data[:metadata][:id]
|
148
|
-
else
|
149
|
-
data[:self].split('/')[-1].to_i
|
150
|
-
end
|
151
|
-
data
|
152
|
-
end
|
153
|
-
|
154
|
-
def error?
|
155
|
-
!!@error
|
156
|
-
end
|
157
|
-
|
158
|
-
def raise_if_cypher_error!
|
159
|
-
raise_cypher_error if error?
|
160
|
-
end
|
161
|
-
|
162
|
-
RETRYABLE_ERROR_STATUSES = %w[DeadlockDetectedException AcquireLockTimeoutException ExternalResourceFailureException UnknownFailureException]
|
163
|
-
def retryable_error?
|
164
|
-
return unless error?
|
165
|
-
RETRYABLE_ERROR_STATUSES.include?(@error_status)
|
166
|
-
end
|
167
|
-
|
168
|
-
def data?
|
169
|
-
!response.body[:data].nil?
|
170
|
-
end
|
171
|
-
|
172
|
-
def raise_unless_response_code(code)
|
173
|
-
fail "Response code #{response.status}, expected #{code} for #{response.headers[:location]}, #{response.body}" unless response.status == code
|
174
|
-
end
|
175
|
-
|
176
|
-
def each_data_row
|
177
|
-
data.each do |r|
|
178
|
-
yieldable = if @uncommitted
|
179
|
-
r[:row]
|
180
|
-
else
|
181
|
-
transaction_row?(r) ? r[:rest] : r
|
182
|
-
end
|
183
|
-
yield yieldable
|
184
|
-
end
|
185
|
-
end
|
186
|
-
|
187
|
-
def transaction_response?
|
188
|
-
response.respond_to?(:body) && !response.body[:commit].nil?
|
189
|
-
end
|
190
|
-
|
191
|
-
def set_data(response)
|
192
|
-
@data = response[:data]
|
193
|
-
@columns = response[:columns]
|
194
|
-
@struct = @columns.empty? ? Object.new : Struct.new(*@columns.map(&:to_sym))
|
195
|
-
self
|
196
|
-
end
|
197
|
-
|
198
|
-
def set_error(error)
|
199
|
-
@error = true
|
200
|
-
@error_msg = error[:message]
|
201
|
-
@error_status = error[:status] || error[:exception] || error[:code]
|
202
|
-
@error_code = error[:code] || error[:fullname]
|
203
|
-
self
|
204
|
-
end
|
205
|
-
|
206
|
-
CONSTRAINT_ERROR = 'Neo.ClientError.Schema.ConstraintViolation'
|
207
|
-
def raise_error
|
208
|
-
fail 'Tried to raise error without an error' unless @error
|
209
|
-
error_class = constraint_error? ? ConstraintViolationError : ResponseError
|
210
|
-
fail error_class.new(@error_msg, @error_status, @error_code)
|
211
|
-
end
|
212
|
-
|
213
|
-
def constraint_error?
|
214
|
-
@error_code == CONSTRAINT_ERROR || (@error_msg || '').include?('already exists with')
|
215
|
-
end
|
216
|
-
|
217
|
-
def raise_cypher_error
|
218
|
-
fail 'Tried to raise error without an error' unless @error
|
219
|
-
fail Neo4j::Session::CypherError.new(@error_msg, @error_code, @error_status)
|
220
|
-
end
|
221
|
-
|
222
|
-
|
223
|
-
def self.create_with_no_tx(response)
|
224
|
-
case response.status
|
225
|
-
when 200 then new(response).set_data(response.body)
|
226
|
-
when 400 then new(response).set_error(response.body)
|
227
|
-
else
|
228
|
-
fail "Unknown response code #{response.status} for #{response.env[:url]}"
|
229
|
-
end
|
230
|
-
end
|
231
|
-
|
232
|
-
def self.create_with_tx(response)
|
233
|
-
fail "Unknown response code #{response.status} for #{response.request_uri}" unless response.status == 200
|
234
|
-
|
235
|
-
new(response, true).tap do |cr|
|
236
|
-
body = response.body
|
237
|
-
body[:errors].empty? ? cr.set_data(body[:results].first) : cr.set_error(body[:errors].first)
|
238
|
-
end
|
239
|
-
end
|
240
|
-
|
241
|
-
private
|
242
|
-
|
243
|
-
def transaction_row?(row)
|
244
|
-
row.is_a?(Hash) && row[:rest] && row[:row]
|
245
|
-
end
|
246
|
-
end
|
247
|
-
end
|
248
|
-
end
|
@@ -1,263 +0,0 @@
|
|
1
|
-
require 'uri'
|
2
|
-
|
3
|
-
module Neo4j
|
4
|
-
module Server
|
5
|
-
Neo4j::Session.register_db(:server_db) do |endpoint_url, url_opts|
|
6
|
-
Neo4j::Server::CypherSession.open(endpoint_url, url_opts)
|
7
|
-
end
|
8
|
-
|
9
|
-
class CypherSession < Neo4j::Session
|
10
|
-
include Resource
|
11
|
-
|
12
|
-
alias super_query query
|
13
|
-
attr_reader :connection
|
14
|
-
|
15
|
-
def initialize(data_url, connection)
|
16
|
-
@connection = connection
|
17
|
-
Neo4j::Session.register(self)
|
18
|
-
initialize_resource(data_url)
|
19
|
-
Neo4j::Session._notify_listeners(:session_available, self)
|
20
|
-
end
|
21
|
-
|
22
|
-
# @param [Hash] params could be empty or contain basic authentication user and password
|
23
|
-
# @return [Faraday]
|
24
|
-
# @see https://github.com/lostisland/faraday
|
25
|
-
def self.create_connection(params, url = nil)
|
26
|
-
init_params = params[:initialize] && params.delete(:initialize)
|
27
|
-
conn = Faraday.new(url, init_params) do |b|
|
28
|
-
b.request :basic_auth, params[:basic_auth][:username], params[:basic_auth][:password] if params[:basic_auth]
|
29
|
-
b.request :multi_json
|
30
|
-
# b.response :logger, ::Logger.new(STDOUT), bodies: true
|
31
|
-
|
32
|
-
b.response :multi_json, symbolize_keys: true, content_type: 'application/json'
|
33
|
-
# b.use Faraday::Response::RaiseError
|
34
|
-
require 'typhoeus'
|
35
|
-
require 'typhoeus/adapters/faraday'
|
36
|
-
b.adapter :typhoeus
|
37
|
-
# b.adapter Faraday.default_adapter
|
38
|
-
end
|
39
|
-
conn.headers = {'Content-Type' => 'application/json', 'User-Agent' => ::Neo4j::Session.user_agent_string}
|
40
|
-
conn
|
41
|
-
end
|
42
|
-
|
43
|
-
# Opens a session to the database
|
44
|
-
# @see Neo4j::Session#open
|
45
|
-
#
|
46
|
-
# @param [String] endpoint_url - the url to the neo4j server, defaults to 'http://localhost:7474'
|
47
|
-
# @param [Hash] params faraday params, see #create_connection or an already created faraday connection
|
48
|
-
def self.open(endpoint_url = nil, params = {})
|
49
|
-
extract_basic_auth(endpoint_url, params)
|
50
|
-
url = endpoint_url || 'http://localhost:7474'
|
51
|
-
connection = params[:connection] || create_connection(params, url)
|
52
|
-
response = connection.get(url)
|
53
|
-
fail "Server not available on #{url} (response code #{response.status})" unless response.status == 200
|
54
|
-
establish_session(response.body, connection)
|
55
|
-
end
|
56
|
-
|
57
|
-
def self.establish_session(root_data, connection)
|
58
|
-
data_url = root_data[:data]
|
59
|
-
data_url << '/' unless data_url.nil? || data_url.end_with?('/')
|
60
|
-
CypherSession.new(data_url, connection)
|
61
|
-
end
|
62
|
-
|
63
|
-
def self.extract_basic_auth(url, params)
|
64
|
-
return unless url && URI(url).userinfo
|
65
|
-
params[:basic_auth] = {username: URI(url).user, password: URI(url).password}
|
66
|
-
end
|
67
|
-
|
68
|
-
private_class_method :extract_basic_auth
|
69
|
-
|
70
|
-
def db_type
|
71
|
-
:server_db
|
72
|
-
end
|
73
|
-
|
74
|
-
def to_s
|
75
|
-
"#{self.class} url: '#{@resource_url}'"
|
76
|
-
end
|
77
|
-
|
78
|
-
def inspect
|
79
|
-
"#{self} version: '#{version}'"
|
80
|
-
end
|
81
|
-
|
82
|
-
def version
|
83
|
-
resource_data ? resource_data[:neo4j_version] : ''
|
84
|
-
end
|
85
|
-
|
86
|
-
def initialize_resource(data_url)
|
87
|
-
response = @connection.get(data_url)
|
88
|
-
expect_response_code!(response, 200)
|
89
|
-
data_resource = response.body
|
90
|
-
fail "No data_resource for #{response.body}" unless data_resource
|
91
|
-
# store the resource data
|
92
|
-
init_resource_data(data_resource, data_url)
|
93
|
-
end
|
94
|
-
|
95
|
-
def self.transaction_class
|
96
|
-
Neo4j::Server::CypherTransaction
|
97
|
-
end
|
98
|
-
|
99
|
-
# Duplicate of CypherSession::Adaptor::Base#transaction
|
100
|
-
def transaction
|
101
|
-
return self.class.transaction_class.new(self) if !block_given?
|
102
|
-
|
103
|
-
begin
|
104
|
-
tx = transaction
|
105
|
-
|
106
|
-
yield tx
|
107
|
-
rescue Exception => e # rubocop:disable Lint/RescueException
|
108
|
-
tx.mark_failed
|
109
|
-
|
110
|
-
raise e
|
111
|
-
ensure
|
112
|
-
tx.close
|
113
|
-
end
|
114
|
-
end
|
115
|
-
|
116
|
-
def create_node(props = nil, labels = [])
|
117
|
-
label_string = labels.empty? ? '' : (':' + labels.map { |k| "`#{k}`" }.join(':'))
|
118
|
-
if !props.nil?
|
119
|
-
prop = '{props}'
|
120
|
-
props.each_key { |k| props.delete(k) if props[k].nil? }
|
121
|
-
end
|
122
|
-
|
123
|
-
id = _query_or_fail("CREATE (n#{label_string} #{prop}) RETURN ID(n)", true, props: props)
|
124
|
-
CypherNode.new(self, props.nil? ? id : {id: id, metadata: {labels: labels}, data: props})
|
125
|
-
end
|
126
|
-
|
127
|
-
def load_node(neo_id)
|
128
|
-
query.unwrapped.match(:n).where(n: {neo_id: neo_id}).pluck(:n).first
|
129
|
-
end
|
130
|
-
|
131
|
-
def load_relationship(neo_id)
|
132
|
-
query.unwrapped.optional_match('(n)-[r]-()').where(r: {neo_id: neo_id}).pluck(:r).first
|
133
|
-
rescue Neo4j::Session::CypherError => cypher_error
|
134
|
-
return nil if cypher_error.message =~ /not found$/
|
135
|
-
|
136
|
-
raise cypher_error
|
137
|
-
end
|
138
|
-
|
139
|
-
def create_label(name)
|
140
|
-
CypherLabel.new(self, name)
|
141
|
-
end
|
142
|
-
|
143
|
-
def uniqueness_constraints(label)
|
144
|
-
schema_properties("/db/data/schema/constraint/#{label}/uniqueness")
|
145
|
-
end
|
146
|
-
|
147
|
-
def indexes(label)
|
148
|
-
schema_properties("/db/data/schema/index/#{label}")
|
149
|
-
end
|
150
|
-
|
151
|
-
def schema_properties(query_string)
|
152
|
-
response = @connection.get(query_string)
|
153
|
-
expect_response_code!(response, 200)
|
154
|
-
{property_keys: response.body.map! { |row| row[:property_keys].map(&:to_sym) }}
|
155
|
-
end
|
156
|
-
|
157
|
-
def find_all_nodes(label_name)
|
158
|
-
search_result_to_enumerable_first_column(_query_or_fail("MATCH (n:`#{label_name}`) RETURN ID(n)"))
|
159
|
-
end
|
160
|
-
|
161
|
-
def find_nodes(label_name, key, value)
|
162
|
-
value = "'#{value}'" if value.is_a? String
|
163
|
-
|
164
|
-
response = _query_or_fail("MATCH (n:`#{label_name}`) WHERE n.#{key} = #{value} RETURN ID(n)")
|
165
|
-
search_result_to_enumerable_first_column(response)
|
166
|
-
end
|
167
|
-
|
168
|
-
def query(*args)
|
169
|
-
if [[String], [String, Hash]].include?(args.map(&:class))
|
170
|
-
response = _query(*args)
|
171
|
-
response.raise_error if response.error?
|
172
|
-
response.to_node_enumeration(args[0])
|
173
|
-
else
|
174
|
-
options = args[0] || {}
|
175
|
-
Neo4j::Core::Query.new(options.merge(session: self))
|
176
|
-
end
|
177
|
-
end
|
178
|
-
|
179
|
-
def _query_data(query)
|
180
|
-
r = _query_or_fail(query, true)
|
181
|
-
Neo4j::Transaction.current ? r : r[:data]
|
182
|
-
end
|
183
|
-
|
184
|
-
DEFAULT_RETRY_COUNT = ENV['NEO4J_RETRY_COUNT'].nil? ? 10 : ENV['NEO4J_RETRY_COUNT'].to_i
|
185
|
-
|
186
|
-
def _query_or_fail(query, single_row = false, params = {}, retry_count = DEFAULT_RETRY_COUNT)
|
187
|
-
query, params = query_and_params(query, params)
|
188
|
-
|
189
|
-
response = _query(query, params)
|
190
|
-
if response.error?
|
191
|
-
_retry_or_raise(query, params, single_row, retry_count, response)
|
192
|
-
else
|
193
|
-
single_row ? response.first_data : response
|
194
|
-
end
|
195
|
-
end
|
196
|
-
|
197
|
-
def query_and_params(query_or_query_string, params)
|
198
|
-
if query_or_query_string.is_a?(::Neo4j::Core::Query)
|
199
|
-
cypher = query_or_query_string.to_cypher
|
200
|
-
[cypher, query_or_query_string.send(:merge_params).merge(params)]
|
201
|
-
else
|
202
|
-
[query_or_query_string, params]
|
203
|
-
end
|
204
|
-
end
|
205
|
-
|
206
|
-
def _retry_or_raise(query, params, single_row, retry_count, response)
|
207
|
-
response.raise_error unless response.retryable_error?
|
208
|
-
retry_count > 0 ? _query_or_fail(query, single_row, params, retry_count - 1) : response.raise_error
|
209
|
-
end
|
210
|
-
|
211
|
-
def _query_entity_data(query, id = nil, params = {})
|
212
|
-
_query(query, params).tap(&:raise_if_cypher_error!).entity_data(id)
|
213
|
-
end
|
214
|
-
|
215
|
-
def _query(query, params = {}, options = {})
|
216
|
-
query, params = query_and_params(query, params)
|
217
|
-
|
218
|
-
ActiveSupport::Notifications.instrument('neo4j.cypher_query', params: params, context: options[:context],
|
219
|
-
cypher: query, pretty_cypher: options[:pretty_cypher]) do
|
220
|
-
if current_transaction
|
221
|
-
current_transaction._query(query, params)
|
222
|
-
else
|
223
|
-
query = params.nil? ? {'query' => query} : {'query' => query, 'params' => params}
|
224
|
-
response = @connection.post(resource_url(:cypher), query)
|
225
|
-
CypherResponse.create_with_no_tx(response)
|
226
|
-
end
|
227
|
-
end
|
228
|
-
end
|
229
|
-
|
230
|
-
def search_result_to_enumerable_first_column(response)
|
231
|
-
return [] unless response.data
|
232
|
-
|
233
|
-
Enumerator.new do |yielder|
|
234
|
-
response.data.each do |data|
|
235
|
-
if current_transaction
|
236
|
-
data[:row].each do |id|
|
237
|
-
yielder << CypherNode.new(self, id).wrapper
|
238
|
-
end
|
239
|
-
else
|
240
|
-
yielder << CypherNode.new(self, data[0]).wrapper
|
241
|
-
end
|
242
|
-
end
|
243
|
-
end
|
244
|
-
end
|
245
|
-
|
246
|
-
def current_transaction
|
247
|
-
Neo4j::Transaction.current_for(self)
|
248
|
-
end
|
249
|
-
|
250
|
-
EMPTY = ''
|
251
|
-
NEWLINE_W_SPACES = "\n "
|
252
|
-
def self.log_with
|
253
|
-
ActiveSupport::Notifications.subscribe('neo4j.cypher_query') do |_, start, finish, _id, payload|
|
254
|
-
ms = (finish - start) * 1000
|
255
|
-
params_string = (payload[:params] && !payload[:params].empty? ? "| #{payload[:params].inspect}" : EMPTY)
|
256
|
-
cypher = payload[:pretty_cypher] ? NEWLINE_W_SPACES + payload[:pretty_cypher].gsub(/\n/, NEWLINE_W_SPACES) : payload[:cypher]
|
257
|
-
|
258
|
-
yield(" #{ANSI::CYAN}#{payload[:context] || 'CYPHER'}#{ANSI::CLEAR} #{ANSI::YELLOW}#{ms.round}ms#{ANSI::CLEAR} #{cypher} #{params_string}")
|
259
|
-
end
|
260
|
-
end
|
261
|
-
end
|
262
|
-
end
|
263
|
-
end
|