neo4j-core 5.0.10 → 5.1.0.rc.1

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.
@@ -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