neo4j-core 5.0.1 → 5.1.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.
@@ -11,12 +11,12 @@ module Neo4j
11
11
 
12
12
 
13
13
  class Clause
14
- include CypherTranslator
15
14
  UNDERSCORE = '_'
16
15
  COMMA_SPACE = ', '
17
16
  AND = ' AND '
18
17
 
19
18
  attr_accessor :params, :arg
19
+ attr_reader :options
20
20
 
21
21
  def initialize(arg, options = {})
22
22
  @arg = arg
@@ -25,7 +25,7 @@ module Neo4j
25
25
  end
26
26
 
27
27
  def value
28
- [String, Symbol, Integer, Hash].each do |arg_class|
28
+ [String, Symbol, Integer, Hash, NilClass].each do |arg_class|
29
29
  from_method = "from_#{arg_class.name.downcase}"
30
30
  return send(from_method, @arg) if @arg.is_a?(arg_class) && self.respond_to?(from_method)
31
31
  end
@@ -53,8 +53,10 @@ module Neo4j
53
53
  end
54
54
 
55
55
  def node_from_key_and_value(key, value, options = {})
56
- var = var_from_key_and_value(key, value, options[:prefer] || :var)
57
- label = label_from_key_and_value(key, value, options[:prefer] || :var)
56
+ prefer = options[:prefer] || :var
57
+ var = var_from_key_and_value(key, value, prefer)
58
+ label = label_from_key_and_value(key, value, prefer)
59
+
58
60
  attributes = attributes_from_key_and_value(key, value)
59
61
 
60
62
  prefix_value = value
@@ -131,30 +133,73 @@ module Neo4j
131
133
  new(arg, options) if !arg.respond_to?(:empty?) || !arg.empty?
132
134
  end
133
135
 
134
- def to_cypher(clauses)
136
+ def to_cypher(clauses, options = {})
135
137
  @question_mark_param_index = 1
136
138
 
137
- string = clause_string(clauses)
138
- string.strip!
139
+ string = clause_string(clauses, options)
140
+
141
+ final_keyword = if options[:pretty]
142
+ "#{clause_color}#{keyword}#{ANSI::CLEAR}"
143
+ else
144
+ keyword
145
+ end
146
+
147
+ "#{final_keyword} #{string}" if string.size > 0
148
+ end
149
+
150
+ def clause_string(clauses, options = {})
151
+ join_string = clause_join + (options[:pretty] ? "\n " : '')
152
+
153
+ strings = clause_strings(clauses)
154
+ string = ((options[:pretty] && strings.size > 1) ? "\n " : '')
155
+ string + strings.join(join_string).strip
156
+ end
157
+
158
+ def clause_join
159
+ ''
160
+ end
139
161
 
140
- "#{keyword} #{string}" if string.size > 0
162
+ def clause_color
163
+ ANSI::CYAN
141
164
  end
142
165
  end
143
166
 
167
+ def self.paramaterize_key!(key)
168
+ key.tr_s!('^a-zA-Z0-9', UNDERSCORE)
169
+ key.gsub!(/^_+|_+$/, '')
170
+ end
171
+
144
172
  private
145
173
 
146
174
  def key_value_string(key, value, previous_keys = [], is_set = false)
147
175
  param = (previous_keys << key).join(UNDERSCORE)
148
- param.tr_s!('^a-zA-Z0-9', UNDERSCORE)
149
- param.gsub!(/^_+|_+$/, '')
176
+ self.class.paramaterize_key!(param)
150
177
 
151
- value = value.first if !is_set && value.is_a?(Array) && value.size == 1
152
- @params[param.to_sym] = value
178
+ if value.is_a?(Range)
179
+ add_params("#{param}_range_min" => value.min, "#{param}_range_max" => value.max)
153
180
 
154
- if !value.is_a?(Array) || is_set
155
- "#{key} = {#{param}}"
181
+ "#{key} IN RANGE({#{param}_range_min}, {#{param}_range_max})"
156
182
  else
157
- "#{key} IN {#{param}}"
183
+ value = value.first if array_value?(value, is_set) && value.size == 1
184
+ operator = array_value?(value, is_set) ? 'IN' : '='
185
+
186
+ add_param(param, value)
187
+
188
+ "#{key} #{operator} {#{param}}"
189
+ end
190
+ end
191
+
192
+ def array_value?(value, is_set)
193
+ value.is_a?(Array) && !is_set
194
+ end
195
+
196
+ def add_param(key, value)
197
+ @params[key.freeze.to_sym] = value
198
+ end
199
+
200
+ def add_params(params)
201
+ params.each do |key, value|
202
+ add_param(key, value)
158
203
  end
159
204
  end
160
205
 
@@ -180,7 +225,7 @@ module Neo4j
180
225
  "#{key}: #{value}"
181
226
  else
182
227
  param_key = "#{prefix}#{key}".gsub('::', '_')
183
- @params[param_key.to_sym] = value
228
+ add_param(param_key, value)
184
229
  "#{key}: {#{param_key}}"
185
230
  end
186
231
  end.join(Clause::COMMA_SPACE)
@@ -206,8 +251,12 @@ module Neo4j
206
251
  end
207
252
 
208
253
  class << self
209
- def clause_string(clauses)
210
- clauses.map!(&:value).join(Clause::COMMA_SPACE)
254
+ def clause_strings(clauses)
255
+ clauses.map!(&:value)
256
+ end
257
+
258
+ def clause_join
259
+ Clause::COMMA_SPACE
211
260
  end
212
261
  end
213
262
  end
@@ -219,16 +268,24 @@ module Neo4j
219
268
  case value
220
269
  when Hash then hash_key_value_string(key, value, previous_keys)
221
270
  when NilClass then "#{key} IS NULL"
222
- when Regexp then regexp_key_value_string(key, value)
223
- when Array then key_value_string(key, value, previous_keys)
271
+ when Regexp then regexp_key_value_string(key, value, previous_keys)
272
+ when Array, Range then key_value_string(key, value, previous_keys)
224
273
  else
225
274
  key_value_string(key, value, previous_keys)
226
275
  end
227
276
  end
228
277
 
229
278
  class << self
230
- def clause_string(clauses)
231
- clauses.map!(&:value).tap(&:flatten!).map! { |value| "(#{value})" }.join(Clause::AND)
279
+ def clause_strings(clauses)
280
+ clauses.flat_map do |clause|
281
+ Array(clause.value).map do |v|
282
+ (clause.options[:not] ? 'NOT' : '') + "(#{v})"
283
+ end
284
+ end
285
+ end
286
+
287
+ def clause_join
288
+ Clause::AND
232
289
  end
233
290
  end
234
291
 
@@ -245,9 +302,15 @@ module Neo4j
245
302
  end.join(AND)
246
303
  end
247
304
 
248
- def regexp_key_value_string(key, value)
305
+ def regexp_key_value_string(key, value, previous_keys)
249
306
  pattern = (value.casefold? ? '(?i)' : '') + value.source
250
- "#{key} =~ #{escape_value(pattern.gsub(/\\/, '\\\\\\'))}"
307
+
308
+ param = [previous_keys + [key]].join(UNDERSCORE)
309
+ self.class.paramaterize_key!(param)
310
+
311
+ add_params(param => pattern)
312
+
313
+ "#{key} =~ {#{param}}"
251
314
  end
252
315
 
253
316
  class << self
@@ -274,14 +337,18 @@ module Neo4j
274
337
  end
275
338
 
276
339
  def question_mark_params_param
277
- result = "question_mark_param#{@question_mark_param_index}"
278
- @question_mark_param_index += 1
279
- result
340
+ "question_mark_param#{question_mark_param_index}"
341
+ end
342
+
343
+ def question_mark_param_index
344
+ @question_mark_param_index ||= 1
345
+ @question_mark_param_index.tap do
346
+ @question_mark_param_index += 1
347
+ end
280
348
  end
281
349
  end
282
350
  end
283
351
 
284
-
285
352
  class MatchClause < Clause
286
353
  KEYWORD = 'MATCH'
287
354
 
@@ -294,8 +361,12 @@ module Neo4j
294
361
  end
295
362
 
296
363
  class << self
297
- def clause_string(clauses)
298
- clauses.map!(&:value).join(Clause::COMMA_SPACE)
364
+ def clause_strings(clauses)
365
+ clauses.map!(&:value)
366
+ end
367
+
368
+ def clause_join
369
+ Clause::COMMA_SPACE
299
370
  end
300
371
  end
301
372
  end
@@ -316,8 +387,12 @@ module Neo4j
316
387
  end
317
388
 
318
389
  class << self
319
- def clause_string(clauses)
320
- clauses.map!(&:value).join(Clause::COMMA_SPACE)
390
+ def clause_strings(clauses)
391
+ clauses.map!(&:value)
392
+ end
393
+
394
+ def clause_join
395
+ Clause::COMMA_SPACE
321
396
  end
322
397
  end
323
398
  end
@@ -326,8 +401,12 @@ module Neo4j
326
401
  KEYWORD = 'USING'
327
402
 
328
403
  class << self
329
- def clause_string(clauses)
330
- clauses.map!(&:value).join(" #{keyword} ")
404
+ def clause_strings(clauses)
405
+ clauses.map!(&:value)
406
+ end
407
+
408
+ def clause_join
409
+ " #{keyword} "
331
410
  end
332
411
  end
333
412
  end
@@ -358,8 +437,16 @@ module Neo4j
358
437
  end
359
438
 
360
439
  class << self
361
- def clause_string(clauses)
362
- clauses.map!(&:value).join(', ')
440
+ def clause_strings(clauses)
441
+ clauses.map!(&:value)
442
+ end
443
+
444
+ def clause_join
445
+ ', '
446
+ end
447
+
448
+ def clause_color
449
+ ANSI::GREEN
363
450
  end
364
451
  end
365
452
  end
@@ -370,6 +457,12 @@ module Neo4j
370
457
 
371
458
  class MergeClause < CreateClause
372
459
  KEYWORD = 'MERGE'
460
+
461
+ class << self
462
+ def clause_color
463
+ ANSI::MAGENTA
464
+ end
465
+ end
373
466
  end
374
467
 
375
468
  class DeleteClause < Clause
@@ -380,8 +473,16 @@ module Neo4j
380
473
  end
381
474
 
382
475
  class << self
383
- def clause_string(clauses)
384
- clauses.map!(&:value).join(Clause::COMMA_SPACE)
476
+ def clause_strings(clauses)
477
+ clauses.map!(&:value)
478
+ end
479
+
480
+ def clause_join
481
+ Clause::COMMA_SPACE
482
+ end
483
+
484
+ def clause_color
485
+ ANSI::RED
385
486
  end
386
487
  end
387
488
  end
@@ -407,8 +508,12 @@ module Neo4j
407
508
  end
408
509
 
409
510
  class << self
410
- def clause_string(clauses)
411
- clauses.map!(&:value).join(Clause::COMMA_SPACE)
511
+ def clause_strings(clauses)
512
+ clauses.map!(&:value)
513
+ end
514
+
515
+ def clause_join
516
+ Clause::COMMA_SPACE
412
517
  end
413
518
  end
414
519
  end
@@ -418,19 +523,23 @@ module Neo4j
418
523
 
419
524
  def from_string(value)
420
525
  clause_id = "#{self.class.keyword_downcase}_#{value}"
421
- @params[clause_id.to_sym] = value.to_i
526
+ add_param(clause_id, value.to_i)
422
527
  "{#{clause_id}}"
423
528
  end
424
529
 
425
530
  def from_integer(value)
426
531
  clause_id = "#{self.class.keyword_downcase}_#{value}"
427
- @params[clause_id.to_sym] = value
532
+ add_param(clause_id, value)
428
533
  "{#{clause_id}}"
429
534
  end
430
535
 
536
+ def from_nilclass(value)
537
+ ''
538
+ end
539
+
431
540
  class << self
432
- def clause_string(clauses)
433
- clauses.last.value
541
+ def clause_strings(clauses)
542
+ [clauses.last.value]
434
543
  end
435
544
  end
436
545
  end
@@ -440,19 +549,19 @@ module Neo4j
440
549
 
441
550
  def from_string(value)
442
551
  clause_id = "#{self.class.keyword_downcase}_#{value}"
443
- @params[clause_id.to_sym] = value.to_i
552
+ add_param(clause_id, value.to_i)
444
553
  "{#{clause_id}}"
445
554
  end
446
555
 
447
556
  def from_integer(value)
448
557
  clause_id = "#{self.class.keyword_downcase}_#{value}"
449
- @params[clause_id.to_sym] = value
558
+ add_param(clause_id, value)
450
559
  "{#{clause_id}}"
451
560
  end
452
561
 
453
562
  class << self
454
- def clause_string(clauses)
455
- clauses.last.value
563
+ def clause_strings(clauses)
564
+ [clauses.last.value]
456
565
  end
457
566
  end
458
567
  end
@@ -465,7 +574,7 @@ module Neo4j
465
574
  when String, Symbol then "#{key}:`#{value}`"
466
575
  when Hash
467
576
  if @options[:set_props]
468
- @params["#{key}_set_props".freeze.to_sym] = value
577
+ add_param("#{key}_set_props", value)
469
578
  "#{key} = {#{key}_set_props}"
470
579
  else
471
580
  value.map { |k, v| key_value_string("#{key}.`#{k}`", v, ['setter'], true) }
@@ -478,8 +587,16 @@ module Neo4j
478
587
  end
479
588
 
480
589
  class << self
481
- def clause_string(clauses)
482
- clauses.map!(&:value).join(Clause::COMMA_SPACE)
590
+ def clause_strings(clauses)
591
+ clauses.map!(&:value)
592
+ end
593
+
594
+ def clause_join
595
+ Clause::COMMA_SPACE
596
+ end
597
+
598
+ def clause_color
599
+ ANSI::YELLOW
483
600
  end
484
601
  end
485
602
  end
@@ -518,8 +635,12 @@ module Neo4j
518
635
  end
519
636
 
520
637
  class << self
521
- def clause_string(clauses)
522
- clauses.map!(&:value).join(Clause::COMMA_SPACE)
638
+ def clause_strings(clauses)
639
+ clauses.map!(&:value)
640
+ end
641
+
642
+ def clause_join
643
+ Clause::COMMA_SPACE
523
644
  end
524
645
  end
525
646
  end
@@ -539,8 +660,12 @@ module Neo4j
539
660
  end
540
661
 
541
662
  class << self
542
- def clause_string(clauses)
543
- clauses.map!(&:value).join(' UNWIND ')
663
+ def clause_strings(clauses)
664
+ clauses.map!(&:value)
665
+ end
666
+
667
+ def clause_join
668
+ ' UNWIND '
544
669
  end
545
670
  end
546
671
  end
@@ -570,8 +695,12 @@ module Neo4j
570
695
  end
571
696
 
572
697
  class << self
573
- def clause_string(clauses)
574
- clauses.map!(&:value).join(Clause::COMMA_SPACE)
698
+ def clause_strings(clauses)
699
+ clauses.map!(&:value)
700
+ end
701
+
702
+ def clause_join
703
+ Clause::COMMA_SPACE
575
704
  end
576
705
  end
577
706
  end
@@ -1,5 +1,5 @@
1
1
  module Neo4j
2
2
  module Core
3
- VERSION = '5.0.1'
3
+ VERSION = '5.1.0'
4
4
  end
5
5
  end
data/lib/neo4j-core.rb CHANGED
@@ -9,7 +9,6 @@ require 'neo4j/property_validator'
9
9
  require 'neo4j/property_container'
10
10
  require 'neo4j-core/active_entity'
11
11
  require 'neo4j-core/helpers'
12
- require 'neo4j-core/cypher_translator'
13
12
  require 'neo4j-core/query_find_in_batches'
14
13
  require 'neo4j-core/query'
15
14
 
@@ -17,6 +16,7 @@ require 'neo4j/entity_equality'
17
16
  require 'neo4j/node'
18
17
  require 'neo4j/label'
19
18
  require 'neo4j/session'
19
+ require 'neo4j/ansi'
20
20
 
21
21
  require 'neo4j/relationship'
22
22
  require 'neo4j/transaction'
@@ -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
@@ -137,9 +142,12 @@ module Neo4j
137
142
  # Remember that you should close the resource iterator.
138
143
  # @param [String] q the cypher query as a String
139
144
  # @return (see #query)
140
- def _query(q, params = {})
141
- @engine ||= Java::OrgNeo4jCypherJavacompat::ExecutionEngine.new(@graph_db)
142
- @engine.execute(q, Neo4j::Core::HashWithIndifferentAccess.new(params))
145
+ def _query(query, params = {}, options = {})
146
+ ActiveSupport::Notifications.instrument('neo4j.cypher_query', params: params, context: options[:context],
147
+ cypher: query, pretty_cypher: options[:pretty_cypher], params: params) do
148
+ @engine ||= Java::OrgNeo4jCypherJavacompat::ExecutionEngine.new(@graph_db)
149
+ @engine.execute(query, HashWithIndifferentAccess.new(params))
150
+ end
143
151
  rescue StandardError => e
144
152
  raise Neo4j::Session::CypherError.new(e.message, e.class, 'cypher error')
145
153
  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'
@@ -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