neo4j-core 5.0.10 → 5.1.0.rc.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,5 +1,5 @@
1
1
  module Neo4j
2
2
  module Core
3
- VERSION = '5.0.10'
3
+ VERSION = '5.1.0.rc.1'
4
4
  end
5
5
  end
@@ -13,4 +13,4 @@ require 'neo4j-embedded/embedded_transaction'
13
13
  require 'neo4j-embedded/cypher_response'
14
14
 
15
15
  # TODO: replace this with https://github.com/intridea/hashie gem
16
- require 'neo4j-core/hash_with_indifferent_access'
16
+ require 'active_support/hash_with_indifferent_access'
@@ -4,7 +4,6 @@ Neo4j::Session.register_db(:embedded_db) do |*args|
4
4
  Neo4j::Embedded::EmbeddedSession.new(*args)
5
5
  end
6
6
 
7
-
8
7
  module Neo4j
9
8
  module Embedded
10
9
  class EmbeddedSession < Neo4j::Session
@@ -17,16 +16,22 @@ module Neo4j
17
16
  def_delegator :@graph_db, :begin_tx
18
17
 
19
18
  def initialize(db_location, config = {})
19
+ @config = config
20
20
  @db_location = db_location
21
- @auto_commit = !!config[:auto_commit]
22
- @properties_file = config[:properties_file]
23
- if config[:properties_map]
24
- props = config[:properties_map].each_with_object({}) { |(k, v), m| m[k.to_s.to_java] = v.to_s.to_java }
25
- @properties_map = java.util.HashMap.new(props)
26
- end
21
+ @auto_commit = !!@config[:auto_commit]
22
+ @properties_file = @config[:properties_file]
27
23
  Neo4j::Session.register(self)
28
24
  end
29
25
 
26
+ def properties_map
27
+ return @properties_map if @properties_map
28
+
29
+ props = @config[:properties_map].each_with_object({}) do |(k, v), m|
30
+ m[k.to_s.to_java] = v.to_s.to_java
31
+ end
32
+ @properties_map = java.util.HashMap.new(props)
33
+ end
34
+
30
35
  def db_type
31
36
  :embedded_db
32
37
  end
@@ -138,9 +143,10 @@ module Neo4j
138
143
  # @param [String] q the cypher query as a String
139
144
  # @return (see #query)
140
145
  def _query(query, params = {}, options = {})
141
- ActiveSupport::Notifications.instrument('neo4j.cypher_query', context: options[:context] || 'CYPHER', cypher: query, params: params) do
146
+ ActiveSupport::Notifications.instrument('neo4j.cypher_query', params: params, context: options[:context],
147
+ cypher: query, pretty_cypher: options[:pretty_cypher], params: params) do
142
148
  @engine ||= Java::OrgNeo4jCypherJavacompat::ExecutionEngine.new(@graph_db)
143
- @engine.execute(query, Neo4j::Core::HashWithIndifferentAccess.new(params))
149
+ @engine.execute(query, HashWithIndifferentAccess.new(params))
144
150
  end
145
151
  rescue StandardError => e
146
152
  raise Neo4j::Session::CypherError.new(e.message, e.class, 'cypher error')
data/lib/neo4j-server.rb CHANGED
@@ -7,7 +7,6 @@ require 'neo4j-server/cypher_node'
7
7
  require 'neo4j-server/cypher_label'
8
8
  require 'neo4j-server/label'
9
9
  require 'neo4j-server/cypher_session'
10
- require 'neo4j-server/cypher_node_uncommited'
11
10
  require 'neo4j-server/cypher_relationship'
12
11
  require 'neo4j-server/cypher_response'
13
12
  require 'neo4j-server/cypher_transaction'
@@ -2,7 +2,6 @@ module Neo4j
2
2
  module Server
3
3
  class CypherNode < Neo4j::Node
4
4
  include Neo4j::Server::Resource
5
- include Neo4j::Core::CypherTranslator
6
5
  include Neo4j::Core::ActiveEntity
7
6
 
8
7
  def initialize(session, value)
@@ -113,8 +112,6 @@ module Neo4j
113
112
  end
114
113
 
115
114
  def set_label(*label_names)
116
- q = match_start_query
117
-
118
115
  labels_to_add = label_names.map(&:to_sym).uniq
119
116
  labels_to_remove = labels - label_names
120
117
 
@@ -122,10 +119,15 @@ module Neo4j
122
119
  labels_to_add -= common_labels
123
120
  labels_to_remove -= common_labels
124
121
 
125
- q = q.remove(n: labels_to_remove) unless labels_to_remove.empty?
126
- q = q.set(n: labels_to_add) unless labels_to_add.empty?
122
+ query = _set_label_query(labels_to_add, labels_to_remove)
123
+ @session._query_or_fail(query, false) unless (labels_to_add + labels_to_remove).empty?
124
+ end
127
125
 
128
- @session._query_or_fail(q, false) unless (labels_to_add + labels_to_remove).empty?
126
+ def _set_label_query(labels_to_add, labels_to_remove)
127
+ query = match_start_query
128
+ query = query.remove(n: labels_to_remove) unless labels_to_remove.empty?
129
+ query = query.set(n: labels_to_add) unless labels_to_add.empty?
130
+ query
129
131
  end
130
132
 
131
133
  # (see Neo4j::Node#del)
@@ -2,7 +2,6 @@ module Neo4j
2
2
  module Server
3
3
  class CypherRelationship < Neo4j::Relationship
4
4
  include Neo4j::Server::Resource
5
- include Neo4j::Core::CypherTranslator
6
5
  include Neo4j::Core::ActiveEntity
7
6
 
8
7
  def initialize(session, value)
@@ -93,10 +92,17 @@ module Neo4j
93
92
  # (see Neo4j::Relationship#update_props)
94
93
  def update_props(properties)
95
94
  return if properties.empty?
96
- q = "#{match_start} SET " + properties.keys.map do |k|
97
- "n.`#{k}`= #{escape_value(properties[k])}"
95
+
96
+ params = {}
97
+ q = "#{match_start} SET " + properties.keys.each_with_index.map do |k, _i|
98
+ param = k.to_s.tr_s('^a-zA-Z0-9', '_').gsub(/^_+|_+$/, '')
99
+ params[param] = properties[k]
100
+
101
+ "n.`#{k}`= {#{param}}"
98
102
  end.join(',')
99
- @session._query_or_fail(q, false, neo_id: neo_id)
103
+
104
+ @session._query_or_fail(q, false, params.merge(neo_id: neo_id))
105
+
100
106
  properties
101
107
  end
102
108
 
@@ -127,7 +133,7 @@ module Neo4j
127
133
  end
128
134
 
129
135
  def neo_id_integer(id_or_url)
130
- id_or_url.is_a?(Integer) ? id_or_url : id_or_url.match(/\d+$/)[0].to_i
136
+ id_or_url.is_a?(Integer) ? id_or_url : id_or_url.split('/').last.to_i
131
137
  end
132
138
  end
133
139
  end
@@ -13,6 +13,7 @@ module Neo4j
13
13
  end
14
14
  end
15
15
 
16
+ class ConstraintViolationError < ResponseError; end
16
17
 
17
18
  class HashEnumeration
18
19
  include Enumerable
@@ -201,9 +202,15 @@ module Neo4j
201
202
  self
202
203
  end
203
204
 
205
+ CONSTRAINT_ERROR = 'Neo.ClientError.Schema.ConstraintViolation'
204
206
  def raise_error
205
207
  fail 'Tried to raise error without an error' unless @error
206
- fail ResponseError.new(@error_msg, @error_status, @error_code)
208
+ error_class = constraint_error? ? ConstraintViolationError : ResponseError
209
+ fail error_class.new(@error_msg, @error_status, @error_code)
210
+ end
211
+
212
+ def constraint_error?
213
+ @error_code == CONSTRAINT_ERROR || @error_msg.include?('already exists with')
207
214
  end
208
215
 
209
216
  def raise_cypher_error
@@ -228,11 +235,7 @@ module Neo4j
228
235
 
229
236
  new(response, true).tap do |cr|
230
237
  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
238
+ body[:errors].empty? ? cr.set_data(body[:results].first) : cr.set_error(body[:errors].first)
236
239
  end
237
240
  end
238
241
 
@@ -8,7 +8,6 @@ module Neo4j
8
8
 
9
9
  class CypherSession < Neo4j::Session
10
10
  include Resource
11
- include Neo4j::Core::CypherTranslator
12
11
 
13
12
  alias_method :super_query, :query
14
13
  attr_reader :connection
@@ -102,9 +101,14 @@ module Neo4j
102
101
  end
103
102
 
104
103
  def create_node(props = nil, labels = [])
105
- id = _query_or_fail(cypher_string(labels, props), true, cypher_prop_list!(props))
106
- value = props.nil? ? id : {id: id, metadata: {labels: labels}, data: props}
107
- CypherNode.new(self, value)
104
+ label_string = labels.empty? ? '' : (':' + labels.map { |k| "`#{k}`" }.join(':'))
105
+ if !props.nil?
106
+ prop = '{props}'
107
+ props.each_key { |k| props.delete(k) if props[k].nil? }
108
+ end
109
+
110
+ id = _query_or_fail("CREATE (n#{label_string} #{prop}) RETURN ID(n)", true, props: props)
111
+ CypherNode.new(self, props.nil? ? id : {id: id, metadata: {labels: labels}, data: props})
108
112
  end
109
113
 
110
114
  def load_node(neo_id)
@@ -114,11 +118,9 @@ module Neo4j
114
118
  def load_relationship(neo_id)
115
119
  query.unwrapped.optional_match('(n)-[r]-()').where(r: {neo_id: neo_id}).pluck(:r).first
116
120
  rescue Neo4j::Session::CypherError => cypher_error
117
- if cypher_error.message.match(/not found$/)
118
- nil
119
- else
120
- raise cypher_error
121
- end
121
+ return nil if cypher_error.message.match(/not found$/)
122
+
123
+ raise cypher_error
122
124
  end
123
125
 
124
126
  def create_label(name)
@@ -206,13 +208,15 @@ module Neo4j
206
208
  query, params = query_and_params(query, params)
207
209
 
208
210
  curr_tx = Neo4j::Transaction.current
209
- ActiveSupport::Notifications.instrument('neo4j.cypher_query', context: options[:context] || 'CYPHER', cypher: query, params: params) do
211
+ puts options[:pretty_cypher]
212
+ puts
213
+ ActiveSupport::Notifications.instrument('neo4j.cypher_query', params: params, context: options[:context],
214
+ cypher: query, pretty_cypher: options[:pretty_cypher]) do
210
215
  if curr_tx
211
216
  curr_tx._query(query, params)
212
217
  else
213
- url = resource_url(:cypher)
214
218
  query = params.nil? ? {'query' => query} : {'query' => query, 'params' => params}
215
- response = @connection.post(url, query)
219
+ response = @connection.post(resource_url(:cypher), query)
216
220
  CypherResponse.create_with_no_tx(response)
217
221
  end
218
222
  end
@@ -235,12 +239,14 @@ module Neo4j
235
239
  end
236
240
 
237
241
  EMPTY = ''
242
+ NEWLINE_W_SPACES = "\n "
238
243
  def self.log_with
239
- clear, yellow, cyan = %W(\e[0m \e[33m \e[36m)
240
244
  ActiveSupport::Notifications.subscribe('neo4j.cypher_query') do |_, start, finish, _id, payload|
241
245
  ms = (finish - start) * 1000
242
246
  params_string = (payload[:params] && payload[:params].size > 0 ? "| #{payload[:params].inspect}" : EMPTY)
243
- yield(" #{cyan}#{payload[:context]}#{clear} #{yellow}#{ms.round}ms#{clear} #{payload[:cypher]} #{params_string}")
247
+ cypher = payload[:pretty_cypher] ? NEWLINE_W_SPACES + payload[:pretty_cypher].gsub(/\n/, NEWLINE_W_SPACES) : payload[:cypher]
248
+
249
+ yield(" #{ANSI::CYAN}#{payload[:context] || 'CYPHER'}#{ANSI::CLEAR} #{ANSI::YELLOW}#{ms.round}ms#{ANSI::CLEAR} #{cypher} #{params_string}")
244
250
  end
245
251
  end
246
252
  end
@@ -9,7 +9,6 @@ module Neo4j
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
11
11
  include Neo4j::Transaction::Instance
12
- include Neo4j::Core::CypherTranslator
13
12
  include Resource
14
13
 
15
14
  attr_reader :commit_url, :query_url, :base_url, :connection
data/lib/neo4j/label.rb CHANGED
@@ -60,7 +60,6 @@ module Neo4j
60
60
  end
61
61
 
62
62
  class << self
63
- include Neo4j::Core::CypherTranslator
64
63
  INDEX_PATH = '/db/data/schema/index/'
65
64
  CONSTRAINT_PATH = '/db/data/schema/constraint/'
66
65
 
data/lib/neo4j/node.rb CHANGED
@@ -168,7 +168,12 @@ module Neo4j
168
168
  class << self
169
169
  # Creates a node
170
170
  def create(props = nil, *labels_or_db)
171
- session = Neo4j::Core::ArgumentHelper.session(labels_or_db)
171
+ session = if labels_or_db.last.is_a?(Neo4j::Session)
172
+ labels_or_db.pop
173
+ else
174
+ Neo4j::Session.current!
175
+ end
176
+
172
177
  session.create_node(props, labels_or_db)
173
178
  end
174
179
 
data/neo4j-core.gemspec CHANGED
@@ -30,11 +30,10 @@ Neo4j-core provides classes and methods to work with the graph database Neo4j.
30
30
  s.add_dependency('httpclient')
31
31
  s.add_dependency('faraday_middleware', '~> 0.9.1')
32
32
  s.add_dependency('json')
33
- s.add_dependency('os') # for Rake task
34
- s.add_dependency('zip') # for Rake task
35
33
  s.add_dependency('activesupport') # For ActiveSupport::Notifications
36
34
  s.add_dependency('multi_json')
37
35
  s.add_dependency('faraday_middleware-multi_json')
36
+ s.add_dependency('neo4j-rake_tasks')
38
37
 
39
38
  s.add_development_dependency('pry')
40
39
  s.add_development_dependency('yard')
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: neo4j-core
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.0.10
4
+ version: 5.1.0.rc.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andreas Ronge, Chris Grigg, Brian Underwood
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-08-09 00:00:00.000000000 Z
11
+ date: 2015-08-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: httparty
@@ -95,21 +95,7 @@ dependencies:
95
95
  - !ruby/object:Gem::Version
96
96
  version: '0'
97
97
  - !ruby/object:Gem::Dependency
98
- name: os
99
- requirement: !ruby/object:Gem::Requirement
100
- requirements:
101
- - - ">="
102
- - !ruby/object:Gem::Version
103
- version: '0'
104
- type: :runtime
105
- prerelease: false
106
- version_requirements: !ruby/object:Gem::Requirement
107
- requirements:
108
- - - ">="
109
- - !ruby/object:Gem::Version
110
- version: '0'
111
- - !ruby/object:Gem::Dependency
112
- name: zip
98
+ name: activesupport
113
99
  requirement: !ruby/object:Gem::Requirement
114
100
  requirements:
115
101
  - - ">="
@@ -123,7 +109,7 @@ dependencies:
123
109
  - !ruby/object:Gem::Version
124
110
  version: '0'
125
111
  - !ruby/object:Gem::Dependency
126
- name: activesupport
112
+ name: multi_json
127
113
  requirement: !ruby/object:Gem::Requirement
128
114
  requirements:
129
115
  - - ">="
@@ -137,7 +123,7 @@ dependencies:
137
123
  - !ruby/object:Gem::Version
138
124
  version: '0'
139
125
  - !ruby/object:Gem::Dependency
140
- name: multi_json
126
+ name: faraday_middleware-multi_json
141
127
  requirement: !ruby/object:Gem::Requirement
142
128
  requirements:
143
129
  - - ">="
@@ -151,7 +137,7 @@ dependencies:
151
137
  - !ruby/object:Gem::Version
152
138
  version: '0'
153
139
  - !ruby/object:Gem::Dependency
154
- name: faraday_middleware-multi_json
140
+ name: neo4j-rake_tasks
155
141
  requirement: !ruby/object:Gem::Requirement
156
142
  requirements:
157
143
  - - ">="
@@ -261,8 +247,6 @@ files:
261
247
  - lib/ext/kernel.rb
262
248
  - lib/neo4j-core.rb
263
249
  - lib/neo4j-core/active_entity.rb
264
- - lib/neo4j-core/cypher_translator.rb
265
- - lib/neo4j-core/hash_with_indifferent_access.rb
266
250
  - lib/neo4j-core/helpers.rb
267
251
  - lib/neo4j-core/label.rb
268
252
  - lib/neo4j-core/query.rb
@@ -285,7 +269,6 @@ files:
285
269
  - lib/neo4j-server.rb
286
270
  - lib/neo4j-server/cypher_label.rb
287
271
  - lib/neo4j-server/cypher_node.rb
288
- - lib/neo4j-server/cypher_node_uncommited.rb
289
272
  - lib/neo4j-server/cypher_relationship.rb
290
273
  - lib/neo4j-server/cypher_response.rb
291
274
  - lib/neo4j-server/cypher_session.rb
@@ -300,8 +283,6 @@ files:
300
283
  - lib/neo4j/property_validator.rb
301
284
  - lib/neo4j/relationship.rb
302
285
  - lib/neo4j/session.rb
303
- - lib/neo4j/tasks/config_server.rb
304
- - lib/neo4j/tasks/neo4j_server.rake
305
286
  - lib/neo4j/transaction.rb
306
287
  - neo4j-core.gemspec
307
288
  homepage: https://github.com/neo4jrb/neo4j-core
@@ -326,9 +307,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
326
307
  version: 1.9.3
327
308
  required_rubygems_version: !ruby/object:Gem::Requirement
328
309
  requirements:
329
- - - ">="
310
+ - - ">"
330
311
  - !ruby/object:Gem::Version
331
- version: '0'
312
+ version: 1.3.1
332
313
  requirements: []
333
314
  rubyforge_project:
334
315
  rubygems_version: 2.4.5
@@ -1,76 +0,0 @@
1
- module Neo4j
2
- module Core
3
- module CypherTranslator
4
- # Cypher Helper
5
- def escape_value(value)
6
- if value.is_a?(String) || value.is_a?(Symbol)
7
- "'#{escape_quotes(sanitize_escape_sequences(value.to_s))}'"
8
- else
9
- value
10
- end
11
- end
12
-
13
- # Like escape_value but it does not wrap the value in quotes
14
- def create_escape_value(value)
15
- if value.is_a?(String) || value.is_a?(Symbol)
16
- "#{sanitize_escape_sequences(value.to_s)}"
17
- else
18
- value
19
- end
20
- end
21
-
22
- # Only following escape sequence characters are allowed in Cypher:
23
- #
24
- # \t Tab
25
- # \b Backspace
26
- # \n Newline
27
- # \r Carriage return
28
- # \f Form feed
29
- # \' Single quote
30
- # \" Double quote
31
- # \\ Backslash
32
- #
33
- # From:
34
- # http://docs.neo4j.org/chunked/stable/cypher-expressions.html#_note_on_string_literals
35
- SANITIZE_ESCAPED_REGEXP = /(?<!\\)\\(\\\\)*(?![futbnr'"\\])/
36
- EMPTY_PROPS = ''
37
-
38
- def sanitize_escape_sequences(s)
39
- s.gsub SANITIZE_ESCAPED_REGEXP, EMPTY_PROPS
40
- end
41
-
42
- def escape_quotes(s)
43
- s.gsub("'", %q(\\\'))
44
- end
45
-
46
- # Cypher Helper
47
- def cypher_prop_list!(props)
48
- return nil unless props
49
- props.reject! { |_, v| v.nil? }
50
- {props: props.each { |k, v| props[k] = create_escape_value(v) }}
51
- end
52
-
53
- # Stolen from keymaker
54
- # https://github.com/therubymug/keymaker/blob/master/lib/keymaker/parsers/cypher_response_parser.rb
55
- def self.translate_response(response_body, result)
56
- Hashie::Mash.new(Hash[sanitized_column_names(response_body).zip(result)])
57
- end
58
-
59
- def self.sanitized_column_names(response_body)
60
- response_body.columns.map { |column| column[/[^\.]+$/] }
61
- end
62
-
63
- def cypher_string(labels, props)
64
- "CREATE (n#{label_string(labels)} #{prop_identifier(props)}) RETURN ID(n)"
65
- end
66
-
67
- def label_string(labels)
68
- labels.empty? ? '' : ":#{labels.map { |k| "`#{k}`" }.join(':')}"
69
- end
70
-
71
- def prop_identifier(props)
72
- '{props}' unless props.nil?
73
- end
74
- end
75
- end
76
- end