neo4j 9.6.2 → 10.0.0.pre.alpha.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +0 -13
  3. data/CONTRIBUTORS +4 -0
  4. data/Gemfile +2 -33
  5. data/lib/neo4j.rb +6 -2
  6. data/lib/neo4j/active_base.rb +19 -22
  7. data/lib/neo4j/active_node/has_n.rb +1 -1
  8. data/lib/neo4j/active_node/labels.rb +1 -11
  9. data/lib/neo4j/active_node/node_wrapper.rb +1 -1
  10. data/lib/neo4j/active_node/query/query_proxy_methods_of_mass_updating.rb +1 -1
  11. data/lib/neo4j/active_rel/rel_wrapper.rb +2 -2
  12. data/lib/neo4j/ansi.rb +14 -0
  13. data/lib/neo4j/core.rb +14 -0
  14. data/lib/neo4j/core/connection_failed_error.rb +6 -0
  15. data/lib/neo4j/core/cypher_error.rb +37 -0
  16. data/lib/neo4j/core/driver.rb +83 -0
  17. data/lib/neo4j/core/has_uri.rb +63 -0
  18. data/lib/neo4j/core/instrumentable.rb +36 -0
  19. data/lib/neo4j/core/label.rb +158 -0
  20. data/lib/neo4j/core/logging.rb +44 -0
  21. data/lib/neo4j/core/node.rb +23 -0
  22. data/lib/neo4j/core/querable.rb +88 -0
  23. data/lib/neo4j/core/query.rb +487 -0
  24. data/lib/neo4j/core/query_builder.rb +32 -0
  25. data/lib/neo4j/core/query_clauses.rb +727 -0
  26. data/lib/neo4j/core/query_find_in_batches.rb +49 -0
  27. data/lib/neo4j/core/relationship.rb +13 -0
  28. data/lib/neo4j/core/responses.rb +50 -0
  29. data/lib/neo4j/core/result.rb +33 -0
  30. data/lib/neo4j/core/schema.rb +30 -0
  31. data/lib/neo4j/core/schema_errors.rb +12 -0
  32. data/lib/neo4j/core/wrappable.rb +30 -0
  33. data/lib/neo4j/migration.rb +2 -2
  34. data/lib/neo4j/migrations/base.rb +1 -1
  35. data/lib/neo4j/model_schema.rb +2 -2
  36. data/lib/neo4j/railtie.rb +8 -52
  37. data/lib/neo4j/schema/operation.rb +1 -1
  38. data/lib/neo4j/shared.rb +1 -1
  39. data/lib/neo4j/shared/property.rb +1 -1
  40. data/lib/neo4j/tasks/migration.rake +5 -4
  41. data/lib/neo4j/transaction.rb +137 -0
  42. data/lib/neo4j/version.rb +1 -1
  43. data/neo4j.gemspec +5 -5
  44. metadata +59 -26
  45. data/bin/neo4j-jars +0 -33
  46. data/lib/neo4j/active_base/session_registry.rb +0 -12
  47. data/lib/neo4j/session_manager.rb +0 -78
@@ -0,0 +1,32 @@
1
+ module Neo4j
2
+ module Core
3
+ class QueryBuilder
4
+ attr_reader :queries
5
+
6
+ Query = Struct.new(:cypher, :parameters, :pretty_cypher, :context)
7
+
8
+ def initialize
9
+ @queries = []
10
+ end
11
+
12
+ def append(*args)
13
+ query = case args.map(&:class)
14
+ when [String], [String, Hash]
15
+ Query.new(args[0], args[1] || {})
16
+ when [::Neo4j::Core::Query]
17
+ args[0]
18
+ else
19
+ fail ArgumentError, "Could not determine query from arguments: #{args.inspect}"
20
+ end
21
+
22
+ @queries << query
23
+ end
24
+
25
+ def query
26
+ # `nil` sessions are just a workaround until
27
+ # we phase out `Query` objects containing sessions
28
+ Neo4j::Core::Query.new(session: nil)
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,727 @@
1
+ module Neo4j
2
+ module Core
3
+ module QueryClauses
4
+ class ArgError < StandardError
5
+ attr_reader :arg_part
6
+ def initialize(arg_part = nil)
7
+ super
8
+ @arg_part = arg_part
9
+ end
10
+ end
11
+
12
+ class Clause
13
+ UNDERSCORE = '_'
14
+ COMMA_SPACE = ', '
15
+ AND = ' AND '
16
+ PRETTY_NEW_LINE = "\n "
17
+
18
+ attr_accessor :params, :arg
19
+ attr_reader :options, :param_vars_added
20
+
21
+ def initialize(arg, params, options = {})
22
+ @arg = arg
23
+ @options = options
24
+ @params = params
25
+ @param_vars_added = []
26
+ end
27
+
28
+ def value
29
+ return @value if @value
30
+
31
+ [String, Symbol, Integer, Hash, NilClass].each do |arg_class|
32
+ from_method = "from_#{arg_class.name.downcase}"
33
+ return @value = send(from_method, @arg) if @arg.is_a?(arg_class) && respond_to?(from_method)
34
+ end
35
+
36
+ fail ArgError
37
+ rescue ArgError => arg_error
38
+ message = "Invalid argument for #{self.class.keyword}. Full arguments: #{@arg.inspect}"
39
+ message += " | Invalid part: #{arg_error.arg_part.inspect}" if arg_error.arg_part
40
+
41
+ raise ArgumentError, message
42
+ end
43
+
44
+ def from_hash(value)
45
+ fail ArgError if !respond_to?(:from_key_and_value)
46
+
47
+ value.map do |k, v|
48
+ from_key_and_value k, v
49
+ end
50
+ end
51
+
52
+ def from_string(value)
53
+ value
54
+ end
55
+
56
+ def node_from_key_and_value(key, value, options = {})
57
+ prefer = options[:prefer] || :var
58
+ var = var_from_key_and_value(key, value, prefer)
59
+ label = label_from_key_and_value(key, value, prefer)
60
+
61
+ attributes = attributes_from_key_and_value(key, value)
62
+
63
+ prefix_value = value
64
+ if value.is_a?(Hash)
65
+ prefix_value = (value.keys.join(UNDERSCORE) if value.values.any? { |v| v.is_a?(Hash) })
66
+ end
67
+
68
+ prefix_array = [key, prefix_value].tap(&:compact!).join(UNDERSCORE)
69
+ formatted_attributes = attributes_string(attributes, "#{prefix_array}#{UNDERSCORE}")
70
+ "(#{var}#{format_label(label)}#{formatted_attributes})"
71
+ end
72
+
73
+ def var_from_key_and_value(key, value, prefer = :var)
74
+ case value
75
+ when String, Symbol, Class, Module, NilClass, Array then key
76
+ when Hash
77
+ key if _use_key_for_var?(value, prefer)
78
+ else
79
+ fail ArgError, value
80
+ end
81
+ end
82
+
83
+ def label_from_key_and_value(key, value, prefer = :var)
84
+ case value
85
+ when String, Symbol, Array, NilClass then value
86
+ when Class, Module then value.name
87
+ when Hash
88
+ if value.values.map(&:class) == [Hash]
89
+ value.first.first
90
+ elsif !_use_key_for_var?(value, prefer)
91
+ key
92
+ end
93
+ else
94
+ fail ArgError, value
95
+ end
96
+ end
97
+
98
+ def _use_key_for_var?(value, prefer)
99
+ _nested_value_hash?(value) || prefer == :var
100
+ end
101
+
102
+ def _nested_value_hash?(value)
103
+ value.values.any? { |v| v.is_a?(Hash) }
104
+ end
105
+
106
+ def attributes_from_key_and_value(_key, value)
107
+ return nil unless value.is_a?(Hash)
108
+
109
+ value.values.map(&:class) == [Hash] ? value.first[1] : value
110
+ end
111
+
112
+ class << self
113
+ def keyword
114
+ self::KEYWORD
115
+ end
116
+
117
+ def keyword_downcase
118
+ keyword.downcase
119
+ end
120
+
121
+ def from_args(args, params, options = {})
122
+ args.flatten!
123
+ args.map { |arg| from_arg(arg, params, options) }.tap(&:compact!)
124
+ end
125
+
126
+ def from_arg(arg, params, options = {})
127
+ new(arg, params, options) if !arg.respond_to?(:empty?) || !arg.empty?
128
+ end
129
+
130
+ def to_cypher(clauses, pretty = false)
131
+ string = clause_string(clauses, pretty)
132
+
133
+ final_keyword = if pretty
134
+ "#{clause_color}#{keyword}#{ANSI::CLEAR}"
135
+ else
136
+ keyword
137
+ end
138
+
139
+ "#{final_keyword} #{string}" if !string.empty?
140
+ end
141
+
142
+ def clause_string(clauses, pretty)
143
+ join_string = pretty ? clause_join + PRETTY_NEW_LINE : clause_join
144
+
145
+ strings = clause_strings(clauses)
146
+ stripped_string = strings.join(join_string).strip
147
+ pretty && strings.size > 1 ? PRETTY_NEW_LINE + stripped_string : stripped_string
148
+ end
149
+
150
+ def clause_join
151
+ ''
152
+ end
153
+
154
+ def clause_color
155
+ ANSI::CYAN
156
+ end
157
+
158
+ def from_key_and_single_value(key, value)
159
+ value.to_sym == :neo_id ? "ID(#{key})" : "#{key}.#{value}"
160
+ end
161
+ end
162
+
163
+ def self.paramaterize_key!(key)
164
+ key.tr_s!('^a-zA-Z0-9', UNDERSCORE)
165
+ key.gsub!(/^_+|_+$/, '')
166
+ end
167
+
168
+ def add_param(key, value)
169
+ @param_vars_added << key
170
+ @params.add_param(key, value)
171
+ end
172
+
173
+ def add_params(keys_and_values)
174
+ @param_vars_added += keys_and_values.keys
175
+ @params.add_params(keys_and_values)
176
+ end
177
+
178
+ private
179
+
180
+ def key_value_string(key, value, previous_keys = [], is_set = false)
181
+ param = (previous_keys << key).join(UNDERSCORE)
182
+ self.class.paramaterize_key!(param)
183
+
184
+ if value.is_a?(Range)
185
+ range_key_value_string(key, value, previous_keys, param)
186
+ else
187
+ value = value.first if array_value?(value, is_set) && value.size == 1
188
+
189
+ param = add_param(param, value)
190
+
191
+ "#{key} #{array_value?(value, is_set) ? 'IN' : '='} {#{param}}"
192
+ end
193
+ end
194
+
195
+ def range_key_value_string(key, value, previous_keys, param)
196
+ begin_param, end_param = add_params("#{param}_range_min" => value.begin, "#{param}_range_max" => value.end)
197
+ "#{key} >= {#{begin_param}} AND #{previous_keys[-2]}.#{key} <#{'=' unless value.exclude_end?} {#{end_param}}"
198
+ end
199
+
200
+ def array_value?(value, is_set)
201
+ value.is_a?(Array) && !is_set
202
+ end
203
+
204
+ def format_label(label_arg)
205
+ return label_arg.map { |arg| format_label(arg) }.join if label_arg.is_a?(Array)
206
+
207
+ label_arg = label_arg.to_s.strip
208
+ if !label_arg.empty? && label_arg[0] != ':'
209
+ label_arg = "`#{label_arg}`" unless label_arg[' ']
210
+ label_arg = ":#{label_arg}"
211
+ end
212
+ label_arg
213
+ end
214
+
215
+ def attributes_string(attributes, prefix = '')
216
+ return '' if not attributes
217
+
218
+ attributes_string = attributes.map do |key, value|
219
+ if value.to_s =~ /^{.+}$/
220
+ "#{key}: #{value}"
221
+ else
222
+ param_key = "#{prefix}#{key}".gsub(/:+/, '_')
223
+ param_key = add_param(param_key, value)
224
+ "#{key}: {#{param_key}}"
225
+ end
226
+ end.join(Clause::COMMA_SPACE)
227
+
228
+ " {#{attributes_string}}"
229
+ end
230
+ end
231
+
232
+ class StartClause < Clause
233
+ KEYWORD = 'START'
234
+
235
+ def from_symbol(value)
236
+ from_string(value.to_s)
237
+ end
238
+
239
+ def from_key_and_value(key, value)
240
+ case value
241
+ when String, Symbol
242
+ "#{key} = #{value}"
243
+ else
244
+ fail ArgError, value
245
+ end
246
+ end
247
+
248
+ class << self
249
+ def clause_strings(clauses)
250
+ clauses.map!(&:value)
251
+ end
252
+
253
+ def clause_join
254
+ Clause::COMMA_SPACE
255
+ end
256
+ end
257
+ end
258
+
259
+ class WhereClause < Clause
260
+ KEYWORD = 'WHERE'
261
+
262
+ PAREN_SURROUND_REGEX = /^\s*\(.+\)\s*$/
263
+
264
+ def from_key_and_value(key, value, previous_keys = [])
265
+ case value
266
+ when Hash then hash_key_value_string(key, value, previous_keys)
267
+ when NilClass then "#{key} IS NULL"
268
+ when Regexp then regexp_key_value_string(key, value, previous_keys)
269
+ else
270
+ key_value_string(key, value, previous_keys)
271
+ end
272
+ end
273
+
274
+ class << self
275
+ def clause_strings(clauses)
276
+ clauses.flat_map do |clause|
277
+ Array(clause.value).map do |v|
278
+ (clause.options[:not] ? 'NOT' : '') + (v.to_s.match(PAREN_SURROUND_REGEX) ? v.to_s : "(#{v})")
279
+ end
280
+ end
281
+ end
282
+
283
+ def clause_join
284
+ Clause::AND
285
+ end
286
+ end
287
+
288
+ private
289
+
290
+ def hash_key_value_string(key, value, previous_keys)
291
+ value.map do |k, v|
292
+ if k.to_sym == :neo_id
293
+ v = Array(v).map { |item| (item.respond_to?(:neo_id) ? item.neo_id : item).to_i }
294
+ key_value_string("ID(#{key})", v)
295
+ else
296
+ "#{key}.#{from_key_and_value(k, v, previous_keys + [key])}"
297
+ end
298
+ end.join(AND)
299
+ end
300
+
301
+ def regexp_key_value_string(key, value, previous_keys)
302
+ pattern = (value.casefold? ? '(?i)' : '') + value.source
303
+
304
+ param = [previous_keys + [key]].join(UNDERSCORE)
305
+ self.class.paramaterize_key!(param)
306
+
307
+ param = add_param(param, pattern)
308
+
309
+ "#{key} =~ {#{param}}"
310
+ end
311
+
312
+ class << self
313
+ ARG_HAS_QUESTION_MARK_REGEX = /(^|\(|\s)\?(\s|\)|$)/
314
+
315
+ def from_args(args, params, options = {})
316
+ query_string, params_arg = args
317
+
318
+ if query_string.is_a?(String) && (query_string.match(ARG_HAS_QUESTION_MARK_REGEX) || params_arg.is_a?(Hash))
319
+ if params_arg.is_a?(Hash)
320
+ params.add_params(params_arg)
321
+ else
322
+ param_var = params.add_params(question_mark_param: params_arg)[0]
323
+ query_string = query_string.gsub(ARG_HAS_QUESTION_MARK_REGEX, "\\1{#{param_var}}\\2")
324
+ end
325
+
326
+ [from_arg(query_string, params, options)]
327
+ else
328
+ super
329
+ end
330
+ end
331
+ end
332
+ end
333
+
334
+ class CallClause < Clause
335
+ KEYWORD = 'CALL'
336
+
337
+ def from_string(value)
338
+ value
339
+ end
340
+
341
+ class << self
342
+ def clause_strings(clauses)
343
+ clauses.map!(&:value)
344
+ end
345
+
346
+ def clause_join
347
+ " #{KEYWORD} "
348
+ end
349
+ end
350
+ end
351
+
352
+ class MatchClause < Clause
353
+ KEYWORD = 'MATCH'
354
+
355
+ def from_symbol(value)
356
+ '(' + from_string(value.to_s) + ')'
357
+ end
358
+
359
+ def from_key_and_value(key, value)
360
+ node_from_key_and_value(key, value)
361
+ end
362
+
363
+ class << self
364
+ def clause_strings(clauses)
365
+ clauses.map!(&:value)
366
+ end
367
+
368
+ def clause_join
369
+ Clause::COMMA_SPACE
370
+ end
371
+ end
372
+ end
373
+
374
+ class OptionalMatchClause < MatchClause
375
+ KEYWORD = 'OPTIONAL MATCH'
376
+ end
377
+
378
+ class WithClause < Clause
379
+ KEYWORD = 'WITH'
380
+
381
+ def from_symbol(value)
382
+ from_string(value.to_s)
383
+ end
384
+
385
+ def from_key_and_value(key, value)
386
+ "#{value} AS #{key}"
387
+ end
388
+
389
+ class << self
390
+ def clause_strings(clauses)
391
+ clauses.map!(&:value)
392
+ end
393
+
394
+ def clause_join
395
+ Clause::COMMA_SPACE
396
+ end
397
+ end
398
+ end
399
+
400
+ class WithDistinctClause < WithClause
401
+ KEYWORD = 'WITH DISTINCT'
402
+ end
403
+
404
+ class UsingClause < Clause
405
+ KEYWORD = 'USING'
406
+
407
+ class << self
408
+ def clause_strings(clauses)
409
+ clauses.map!(&:value)
410
+ end
411
+
412
+ def clause_join
413
+ " #{keyword} "
414
+ end
415
+ end
416
+ end
417
+
418
+ class CreateClause < Clause
419
+ KEYWORD = 'CREATE'
420
+
421
+ def from_string(value)
422
+ value
423
+ end
424
+
425
+ def from_symbol(value)
426
+ "(:#{value})"
427
+ end
428
+
429
+ def from_hash(hash)
430
+ if hash.values.any? { |value| value.is_a?(Hash) }
431
+ hash.map do |key, value|
432
+ from_key_and_value(key, value)
433
+ end
434
+ else
435
+ "(#{attributes_string(hash)})"
436
+ end
437
+ end
438
+
439
+ def from_key_and_value(key, value)
440
+ node_from_key_and_value(key, value, prefer: :label)
441
+ end
442
+
443
+ class << self
444
+ def clause_strings(clauses)
445
+ clauses.map!(&:value)
446
+ end
447
+
448
+ def clause_join
449
+ ', '
450
+ end
451
+
452
+ def clause_color
453
+ ANSI::GREEN
454
+ end
455
+ end
456
+ end
457
+
458
+ class CreateUniqueClause < CreateClause
459
+ KEYWORD = 'CREATE UNIQUE'
460
+ end
461
+
462
+ class MergeClause < CreateClause
463
+ KEYWORD = 'MERGE'
464
+
465
+ class << self
466
+ def clause_color
467
+ ANSI::MAGENTA
468
+ end
469
+
470
+ def clause_join
471
+ ' MERGE '
472
+ end
473
+ end
474
+ end
475
+
476
+ class DeleteClause < Clause
477
+ KEYWORD = 'DELETE'
478
+
479
+ def from_symbol(value)
480
+ from_string(value.to_s)
481
+ end
482
+
483
+ class << self
484
+ def clause_strings(clauses)
485
+ clauses.map!(&:value)
486
+ end
487
+
488
+ def clause_join
489
+ Clause::COMMA_SPACE
490
+ end
491
+
492
+ def clause_color
493
+ ANSI::RED
494
+ end
495
+ end
496
+ end
497
+
498
+ class DetachDeleteClause < DeleteClause
499
+ KEYWORD = 'DETACH DELETE'
500
+ end
501
+
502
+ class OrderClause < Clause
503
+ KEYWORD = 'ORDER BY'
504
+
505
+ def from_symbol(value)
506
+ from_string(value.to_s)
507
+ end
508
+
509
+ def from_key_and_value(key, value)
510
+ case value
511
+ when String, Symbol
512
+ self.class.from_key_and_single_value(key, value)
513
+ when Array
514
+ value.map do |v|
515
+ v.is_a?(Hash) ? from_key_and_value(key, v) : self.class.from_key_and_single_value(key, v)
516
+ end
517
+ when Hash
518
+ value.map { |k, v| "#{self.class.from_key_and_single_value(key, k)} #{v.upcase}" }
519
+ end
520
+ end
521
+
522
+ class << self
523
+ def clause_strings(clauses)
524
+ clauses.map!(&:value)
525
+ end
526
+
527
+ def clause_join
528
+ Clause::COMMA_SPACE
529
+ end
530
+ end
531
+ end
532
+
533
+ class LimitClause < Clause
534
+ KEYWORD = 'LIMIT'
535
+
536
+ def from_string(value)
537
+ param_var = "#{self.class.keyword_downcase}_#{value}"
538
+ param_var = add_param(param_var, value.to_i)
539
+ "{#{param_var}}"
540
+ end
541
+
542
+ def from_integer(value)
543
+ from_string(value)
544
+ end
545
+
546
+ def from_nilclass(_value)
547
+ ''
548
+ end
549
+
550
+ class << self
551
+ def clause_strings(clauses)
552
+ result_clause = clauses.last
553
+
554
+ clauses[0..-2].map(&:param_vars_added).flatten.grep(/^limit_\d+$/).each do |var|
555
+ result_clause.params.remove_param(var)
556
+ end
557
+
558
+ [result_clause.value]
559
+ end
560
+ end
561
+ end
562
+
563
+ class SkipClause < Clause
564
+ KEYWORD = 'SKIP'
565
+
566
+ def from_string(value)
567
+ clause_id = "#{self.class.keyword_downcase}_#{value}"
568
+ clause_id = add_param(clause_id, value.to_i)
569
+ "{#{clause_id}}"
570
+ end
571
+
572
+ def from_integer(value)
573
+ clause_id = "#{self.class.keyword_downcase}_#{value}"
574
+ clause_id = add_param(clause_id, value)
575
+ "{#{clause_id}}"
576
+ end
577
+
578
+ class << self
579
+ def clause_strings(clauses)
580
+ result_clause = clauses.last
581
+
582
+ clauses[0..-2].map(&:param_vars_added).flatten.grep(/^skip_\d+$/).each do |var|
583
+ result_clause.params.remove_param(var)
584
+ end
585
+
586
+ [result_clause.value]
587
+ end
588
+ end
589
+ end
590
+
591
+ class SetClause < Clause
592
+ KEYWORD = 'SET'
593
+
594
+ def from_key_and_value(key, value)
595
+ case value
596
+ when String, Symbol then "#{key}:`#{value}`"
597
+ when Hash
598
+ if @options[:set_props]
599
+ param = add_param("#{key}_set_props", value)
600
+ "#{key} = {#{param}}"
601
+ else
602
+ value.map { |k, v| key_value_string("#{key}.`#{k}`", v, ['setter'], true) }
603
+ end
604
+ when Array then value.map { |v| from_key_and_value(key, v) }
605
+ when NilClass then []
606
+ else
607
+ fail ArgError, value
608
+ end
609
+ end
610
+
611
+ class << self
612
+ def clause_strings(clauses)
613
+ clauses.map!(&:value)
614
+ end
615
+
616
+ def clause_join
617
+ Clause::COMMA_SPACE
618
+ end
619
+
620
+ def clause_color
621
+ ANSI::YELLOW
622
+ end
623
+ end
624
+ end
625
+
626
+ class OnCreateSetClause < SetClause
627
+ KEYWORD = 'ON CREATE SET'
628
+
629
+ def initialize(*args)
630
+ super
631
+ @options[:set_props] = false
632
+ end
633
+ end
634
+
635
+ class OnMatchSetClause < OnCreateSetClause
636
+ KEYWORD = 'ON MATCH SET'
637
+ end
638
+
639
+ class RemoveClause < Clause
640
+ KEYWORD = 'REMOVE'
641
+
642
+ def from_key_and_value(key, value)
643
+ case value
644
+ when /^:/
645
+ "#{key}:`#{value[1..-1]}`"
646
+ when String
647
+ "#{key}.#{value}"
648
+ when Symbol
649
+ "#{key}:`#{value}`"
650
+ when Array
651
+ value.map do |v|
652
+ from_key_and_value(key, v)
653
+ end
654
+ else
655
+ fail ArgError, value
656
+ end
657
+ end
658
+
659
+ class << self
660
+ def clause_strings(clauses)
661
+ clauses.map!(&:value)
662
+ end
663
+
664
+ def clause_join
665
+ Clause::COMMA_SPACE
666
+ end
667
+ end
668
+ end
669
+
670
+ class UnwindClause < Clause
671
+ KEYWORD = 'UNWIND'
672
+
673
+ def from_key_and_value(key, value)
674
+ case value
675
+ when String, Symbol
676
+ "#{value} AS #{key}"
677
+ when Array
678
+ "#{value.inspect} AS #{key}"
679
+ else
680
+ fail ArgError, value
681
+ end
682
+ end
683
+
684
+ class << self
685
+ def clause_strings(clauses)
686
+ clauses.map!(&:value)
687
+ end
688
+
689
+ def clause_join
690
+ ' UNWIND '
691
+ end
692
+ end
693
+ end
694
+
695
+ class ReturnClause < Clause
696
+ KEYWORD = 'RETURN'
697
+
698
+ def from_symbol(value)
699
+ from_string(value.to_s)
700
+ end
701
+
702
+ def from_key_and_value(key, value)
703
+ case value
704
+ when Array
705
+ value.map do |v|
706
+ from_key_and_value(key, v)
707
+ end.join(Clause::COMMA_SPACE)
708
+ when String, Symbol
709
+ self.class.from_key_and_single_value(key, value)
710
+ else
711
+ fail ArgError, value
712
+ end
713
+ end
714
+
715
+ class << self
716
+ def clause_strings(clauses)
717
+ clauses.map!(&:value)
718
+ end
719
+
720
+ def clause_join
721
+ Clause::COMMA_SPACE
722
+ end
723
+ end
724
+ end
725
+ end
726
+ end
727
+ end