neo4j-core 3.1.1 → 4.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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,253 +1,257 @@
|
|
1
|
-
module Neo4j
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
1
|
+
module Neo4j
|
2
|
+
module Server
|
3
|
+
# Plugin
|
4
|
+
Neo4j::Session.register_db(:server_db) do |*url_opts|
|
5
|
+
Neo4j::Server::CypherSession.open(*url_opts)
|
6
|
+
end
|
7
7
|
|
8
|
-
|
9
|
-
|
10
|
-
|
8
|
+
class CypherSession < Neo4j::Session
|
9
|
+
include Resource
|
10
|
+
include Neo4j::Core::CypherTranslator
|
11
11
|
|
12
|
-
|
13
|
-
|
12
|
+
alias_method :super_query, :query
|
13
|
+
attr_reader :connection, :auth
|
14
14
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
15
|
+
def initialize(data_url, connection, auth_obj = nil)
|
16
|
+
@connection = connection
|
17
|
+
@auth = auth_obj if auth_obj
|
18
|
+
Neo4j::Session.register(self)
|
19
|
+
initialize_resource(data_url)
|
20
|
+
Neo4j::Session._notify_listeners(:session_available, self)
|
21
|
+
end
|
22
22
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
23
|
+
# @param [Hash] params could be empty or contain basic authentication user and password
|
24
|
+
# @return [Faraday]
|
25
|
+
# @see https://github.com/lostisland/faraday
|
26
|
+
def self.create_connection(params)
|
27
|
+
init_params = params[:initialize] && params.delete(:initialize)
|
28
|
+
conn = Faraday.new(init_params) do |b|
|
29
|
+
b.request :basic_auth, params[:basic_auth][:username], params[:basic_auth][:password] if params[:basic_auth]
|
30
|
+
b.request :json
|
31
|
+
# b.response :logger
|
32
|
+
b.response :json, content_type: 'application/json'
|
33
|
+
# b.use Faraday::Response::RaiseError
|
34
|
+
b.use Faraday::Adapter::NetHttpPersistent
|
35
|
+
# b.adapter Faraday.default_adapter
|
36
|
+
end
|
37
|
+
conn.headers = {'Content-Type' => 'application/json', 'User-Agent' => ::Neo4j::Session.user_agent_string}
|
38
|
+
conn
|
39
|
+
end
|
40
40
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
41
|
+
# Opens a session to the database
|
42
|
+
# @see Neo4j::Session#open
|
43
|
+
#
|
44
|
+
# @param [String] endpoint_url - the url to the neo4j server, defaults to 'http://localhost:7474'
|
45
|
+
# @param [Hash] params faraday params, see #create_connection or an already created faraday connection
|
46
|
+
def self.open(endpoint_url = nil, params = {})
|
47
|
+
extract_basic_auth(endpoint_url, params)
|
48
|
+
connection = params[:connection] || create_connection(params)
|
49
|
+
url = endpoint_url || 'http://localhost:7474'
|
50
|
+
auth_obj = CypherAuthentication.new(url, connection, params)
|
51
|
+
auth_obj.authenticate
|
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, auth_obj)
|
55
|
+
end
|
56
56
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
57
|
+
def self.establish_session(root_data, connection, auth_obj)
|
58
|
+
data_url = root_data['data']
|
59
|
+
data_url << '/' unless data_url.nil? || data_url.end_with?('/')
|
60
|
+
CypherSession.new(data_url, connection, auth_obj)
|
61
|
+
end
|
62
62
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
63
|
+
def self.extract_basic_auth(url, params)
|
64
|
+
return unless url && URI(url).userinfo
|
65
|
+
params[:basic_auth] = {
|
66
|
+
username: URI(url).user,
|
67
|
+
password: URI(url).password
|
68
|
+
}
|
69
|
+
end
|
70
70
|
|
71
|
-
|
71
|
+
private_class_method :extract_basic_auth
|
72
72
|
|
73
|
-
|
74
|
-
|
75
|
-
|
73
|
+
def db_type
|
74
|
+
:server_db
|
75
|
+
end
|
76
76
|
|
77
|
-
|
78
|
-
|
79
|
-
|
77
|
+
def to_s
|
78
|
+
"#{self.class} url: '#{@resource_url}'"
|
79
|
+
end
|
80
80
|
|
81
|
-
|
82
|
-
|
83
|
-
|
81
|
+
def inspect
|
82
|
+
"#{self} version: '#{version}'"
|
83
|
+
end
|
84
84
|
|
85
|
-
|
86
|
-
|
87
|
-
|
85
|
+
def version
|
86
|
+
resource_data ? resource_data['neo4j_version'] : ''
|
87
|
+
end
|
88
88
|
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
89
|
+
def initialize_resource(data_url)
|
90
|
+
response = @connection.get(data_url)
|
91
|
+
expect_response_code(response, 200)
|
92
|
+
data_resource = response.body
|
93
|
+
fail "No data_resource for #{response.body}" unless data_resource
|
94
|
+
# store the resource data
|
95
|
+
init_resource_data(data_resource, data_url)
|
96
|
+
end
|
97
97
|
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
98
|
+
def close
|
99
|
+
super
|
100
|
+
Neo4j::Transaction.unregister_current
|
101
|
+
end
|
102
102
|
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
103
|
+
def begin_tx
|
104
|
+
if Neo4j::Transaction.current
|
105
|
+
# Handle nested transaction "placebo transaction"
|
106
|
+
Neo4j::Transaction.current.push_nested!
|
107
|
+
else
|
108
|
+
wrap_resource(@connection)
|
109
|
+
end
|
110
|
+
Neo4j::Transaction.current
|
109
111
|
end
|
110
|
-
Neo4j::Transaction.current
|
111
|
-
end
|
112
112
|
|
113
|
-
|
114
|
-
|
115
|
-
|
113
|
+
def create_node(props = nil, labels = [])
|
114
|
+
id = _query_or_fail(cypher_string(labels, props), true, cypher_prop_list(props))
|
115
|
+
value = props.nil? ? id : {'id' => id, 'metadata' => {'labels' => labels}, 'data' => props}
|
116
|
+
CypherNode.new(self, value)
|
117
|
+
end
|
116
118
|
|
117
|
-
|
118
|
-
|
119
|
-
|
119
|
+
def load_node(neo_id)
|
120
|
+
load_entity(CypherNode, _query("MATCH (n) WHERE ID(n) = #{neo_id} RETURN n"))
|
121
|
+
end
|
120
122
|
|
121
|
-
|
122
|
-
|
123
|
-
|
123
|
+
def load_relationship(neo_id)
|
124
|
+
load_entity(CypherRelationship, _query("MATCH (n)-[r]-() WHERE ID(r) = #{neo_id} RETURN r"))
|
125
|
+
end
|
124
126
|
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
127
|
+
def load_entity(clazz, cypher_response)
|
128
|
+
return nil if cypher_response.data.nil? || cypher_response.data[0].nil?
|
129
|
+
data = if cypher_response.transaction_response?
|
130
|
+
cypher_response.rest_data_with_id
|
131
|
+
else
|
132
|
+
cypher_response.first_data
|
133
|
+
end
|
134
|
+
|
135
|
+
if cypher_response.error?
|
136
|
+
cypher_response.raise_error
|
137
|
+
elsif cypher_response.error_msg =~ /not found/ # Ugly that the Neo4j API gives us this error message
|
138
|
+
return nil
|
139
|
+
else
|
140
|
+
clazz.new(self, data)
|
141
|
+
end
|
139
142
|
end
|
140
|
-
end
|
141
143
|
|
142
|
-
|
143
|
-
|
144
|
-
|
144
|
+
def create_label(name)
|
145
|
+
CypherLabel.new(self, name)
|
146
|
+
end
|
145
147
|
|
146
|
-
|
147
|
-
|
148
|
-
|
148
|
+
def uniqueness_constraints(label)
|
149
|
+
schema_properties("#{@resource_url}schema/constraint/#{label}/uniqueness")
|
150
|
+
end
|
149
151
|
|
150
|
-
|
151
|
-
|
152
|
-
|
152
|
+
def indexes(label)
|
153
|
+
schema_properties("#{@resource_url}schema/index/#{label}")
|
154
|
+
end
|
153
155
|
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
156
|
+
def schema_properties(query_string)
|
157
|
+
response = @connection.get(query_string)
|
158
|
+
expect_response_code(response, 200)
|
159
|
+
{property_keys: response.body.map { |row| row['property_keys'].map(&:to_sym) }}
|
160
|
+
end
|
159
161
|
|
160
|
-
|
161
|
-
|
162
|
-
|
162
|
+
def find_all_nodes(label_name)
|
163
|
+
search_result_to_enumerable_first_column(_query_or_fail("MATCH (n:`#{label_name}`) RETURN ID(n)"))
|
164
|
+
end
|
163
165
|
|
164
|
-
|
165
|
-
|
166
|
+
def find_nodes(label_name, key, value)
|
167
|
+
value = "'#{value}'" if value.is_a? String
|
166
168
|
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
169
|
+
response = _query_or_fail <<-CYPHER
|
170
|
+
MATCH (n:`#{label_name}`)
|
171
|
+
WHERE n.#{key} = #{value}
|
172
|
+
RETURN ID(n)
|
173
|
+
CYPHER
|
174
|
+
search_result_to_enumerable_first_column(response)
|
175
|
+
end
|
174
176
|
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
177
|
+
def query(*args)
|
178
|
+
if [[String], [String, Hash]].include?(args.map(&:class))
|
179
|
+
query, params = args[0, 2]
|
180
|
+
response = _query(query, params)
|
181
|
+
response.raise_error if response.error?
|
182
|
+
response.to_node_enumeration(query)
|
183
|
+
else
|
184
|
+
options = args[0] || {}
|
185
|
+
Neo4j::Core::Query.new(options.merge(session: self))
|
186
|
+
end
|
184
187
|
end
|
185
|
-
end
|
186
188
|
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
189
|
+
def _query_data(q)
|
190
|
+
r = _query_or_fail(q, true)
|
191
|
+
# the response is different if we have a transaction or not
|
192
|
+
Neo4j::Transaction.current ? r : r['data']
|
193
|
+
end
|
192
194
|
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
195
|
+
def _query_or_fail(q, single_row = false, params = nil)
|
196
|
+
response = _query(q, params)
|
197
|
+
response.raise_error if response.error?
|
198
|
+
single_row ? response.first_data : response
|
199
|
+
end
|
198
200
|
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
201
|
+
def _query_entity_data(q, id = nil, params = nil)
|
202
|
+
response = _query(q, params)
|
203
|
+
response.raise_error if response.error?
|
204
|
+
response.entity_data(id)
|
205
|
+
end
|
204
206
|
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
curr_tx
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
207
|
+
def _query(q, params = nil)
|
208
|
+
# puts "q #{q}"
|
209
|
+
curr_tx = Neo4j::Transaction.current
|
210
|
+
if curr_tx
|
211
|
+
curr_tx._query(q, params)
|
212
|
+
else
|
213
|
+
url = resource_url('cypher')
|
214
|
+
q = params.nil? ? {'query' => q} : {'query' => q, 'params' => params}
|
215
|
+
response = @connection.post(url, q)
|
216
|
+
CypherResponse.create_with_no_tx(response)
|
217
|
+
end
|
214
218
|
end
|
215
|
-
end
|
216
219
|
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
220
|
+
def search_result_to_enumerable_first_column(response)
|
221
|
+
return [] unless response.data
|
222
|
+
if Neo4j::Transaction.current
|
223
|
+
search_result_to_enumerable_first_column_with_tx(response)
|
224
|
+
else
|
225
|
+
search_result_to_enumerable_first_column_without_tx(response)
|
226
|
+
end
|
223
227
|
end
|
224
|
-
end
|
225
228
|
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
229
|
+
def search_result_to_enumerable_first_column_with_tx(response)
|
230
|
+
Enumerator.new do |yielder|
|
231
|
+
response.data.each do |data|
|
232
|
+
data['row'].each do |id|
|
233
|
+
yielder << CypherNode.new(self, id).wrapper
|
234
|
+
end
|
231
235
|
end
|
232
236
|
end
|
233
237
|
end
|
234
|
-
end
|
235
238
|
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
239
|
+
def search_result_to_enumerable_first_column_without_tx(response)
|
240
|
+
Enumerator.new do |yielder|
|
241
|
+
response.data.each do |data|
|
242
|
+
yielder << CypherNode.new(self, data[0]).wrapper
|
243
|
+
end
|
240
244
|
end
|
241
245
|
end
|
242
|
-
end
|
243
246
|
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
247
|
+
def map_column(key, map, data)
|
248
|
+
if map[key] == :node
|
249
|
+
CypherNode.new(self, data).wrapper
|
250
|
+
elsif map[key] == :rel || map[:key] || :relationship
|
251
|
+
CypherRelationship.new(self, data)
|
252
|
+
else
|
253
|
+
data
|
254
|
+
end
|
251
255
|
end
|
252
256
|
end
|
253
257
|
end
|