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,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
|