neo4j-core 4.0.7 → 5.0.0.rc.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -14,6 +14,9 @@ module Neo4j
14
14
  class Query
15
15
  include Neo4j::Core::QueryClauses
16
16
  include Neo4j::Core::QueryFindInBatches
17
+ DEFINED_CLAUSES = {}
18
+
19
+ attr_accessor :clauses
17
20
 
18
21
  def initialize(options = {})
19
22
  @session = options[:session] || Neo4j::Session.current
@@ -99,17 +102,24 @@ module Neo4j
99
102
  # DELETE clause
100
103
  # @return [Query]
101
104
 
102
- METHODS = %w(with start match optional_match using where set create create_unique merge on_create_set on_match_set remove unwind delete return order skip limit)
105
+ METHODS = %w(start match optional_match using where create create_unique merge set on_create_set on_match_set remove unwind delete with return order skip limit)
106
+ BREAK_METHODS = %(with)
107
+
108
+ CLAUSIFY_CLAUSE = proc do |method|
109
+ const_get(method.to_s.split('_').map(&:capitalize).join + 'Clause')
110
+ end
103
111
 
104
- CLAUSES = METHODS.map { |method| const_get(method.split('_').map(&:capitalize).join + 'Clause') }
112
+ CLAUSES = METHODS.map(&CLAUSIFY_CLAUSE)
105
113
 
106
114
  METHODS.each_with_index do |clause, i|
107
115
  clause_class = CLAUSES[i]
108
116
 
109
- module_eval(%{
110
- def #{clause}(*args)
111
- build_deeper_query(#{clause_class}, args)
112
- end}, __FILE__, __LINE__)
117
+ DEFINED_CLAUSES[clause.to_sym] = clause_class
118
+ define_method(clause) do |*args|
119
+ build_deeper_query(clause_class, args).ergo do |result|
120
+ BREAK_METHODS.include?(clause) ? result.break : result
121
+ end
122
+ end
113
123
  end
114
124
 
115
125
  alias_method :offset, :skip
@@ -152,6 +162,15 @@ module Neo4j
152
162
  self
153
163
  end
154
164
 
165
+ def unwrapped
166
+ @_unwrapped_obj = true
167
+ self
168
+ end
169
+
170
+ def unwrapped?
171
+ !!@_unwrapped_obj
172
+ end
173
+
155
174
  def response
156
175
  return @response if @response
157
176
  cypher = to_cypher
@@ -167,12 +186,18 @@ module Neo4j
167
186
 
168
187
  include Enumerable
169
188
 
189
+ def count(var = nil)
190
+ v = var.nil? ? '*' : var
191
+ pluck("count(#{v})").first
192
+ end
193
+
170
194
  def each
171
195
  response = self.response
172
196
  if response.is_a?(Neo4j::Server::CypherResponse)
197
+ response.unwrapped! if unwrapped?
173
198
  response.to_node_enumeration
174
199
  else
175
- Neo4j::Embedded::ResultWrapper.new(response, to_cypher)
200
+ Neo4j::Embedded::ResultWrapper.new(response, to_cypher, unwrapped?)
176
201
  end.each { |object| yield object }
177
202
  end
178
203
 
@@ -201,12 +226,12 @@ module Neo4j
201
226
  # Query.new.match(n: :Person).return(p: :name}.pluck('p, DISTINCT p.name') # => Array of [node, name] pairs
202
227
  #
203
228
  def pluck(*columns)
229
+ fail ArgumentError, 'No columns specified for Query#pluck' if columns.size.zero?
230
+
204
231
  query = return_query(columns)
205
232
  columns = query.response.columns
206
233
 
207
234
  case columns.size
208
- when 0
209
- fail ArgumentError, 'No columns specified for Query#pluck'
210
235
  when 1
211
236
  column = columns[0]
212
237
  query.map { |row| row[column] }
@@ -223,15 +248,7 @@ module Neo4j
223
248
  query = copy
224
249
  query.remove_clause_class(ReturnClause)
225
250
 
226
- columns = columns.map do |column_definition|
227
- if column_definition.is_a?(Hash)
228
- column_definition.map { |k, v| "#{k}.#{v}" }
229
- else
230
- column_definition
231
- end
232
- end.flatten.map(&:to_sym)
233
-
234
- query.return(columns)
251
+ query.return(*columns)
235
252
  end
236
253
 
237
254
  # Returns a CYPHER query string from the object query representation
@@ -239,22 +256,21 @@ module Neo4j
239
256
  # Query.new.match(p: :Person).where(p: {age: 30}) # => "MATCH (p:Person) WHERE p.age = 30
240
257
  #
241
258
  # @return [String] Resulting cypher query string
259
+ EMPTY = ' '
242
260
  def to_cypher
243
- cypher_string = partitioned_clauses.map do |clauses|
261
+ cypher_string = PartitionedClauses.new(@clauses).map do |clauses|
244
262
  clauses_by_class = clauses.group_by(&:class)
245
263
 
246
264
  cypher_parts = CLAUSES.map do |clause_class|
247
- clauses = clauses_by_class[clause_class]
248
-
249
- clause_class.to_cypher(clauses) if clauses
265
+ clause_class.to_cypher(clauses) if clauses = clauses_by_class[clause_class]
250
266
  end
251
267
 
252
- cypher_string = cypher_parts.compact.join(' ')
253
- cypher_string.strip
254
- end.join ' '
268
+ cypher_parts.compact!
269
+ cypher_parts.join(EMPTY).tap(&:strip!)
270
+ end.join EMPTY
255
271
 
256
272
  cypher_string = "CYPHER #{@options[:parser]} #{cypher_string}" if @options[:parser]
257
- cypher_string.strip
273
+ cypher_string.tap(&:strip!)
258
274
  end
259
275
 
260
276
  # Returns a CYPHER query specifying the union of the callee object's query and the argument's query
@@ -289,9 +305,16 @@ module Neo4j
289
305
  end
290
306
  end
291
307
 
308
+ def clause?(method)
309
+ clause_class = DEFINED_CLAUSES[method] || CLAUSIFY_CLAUSE.call(method)
310
+ clauses.any? do |clause|
311
+ clause.is_a?(clause_class)
312
+ end
313
+ end
314
+
292
315
  protected
293
316
 
294
- attr_accessor :session, :options, :clauses, :_params
317
+ attr_accessor :session, :options, :_params
295
318
 
296
319
  def add_clauses(clauses)
297
320
  @clauses += clauses
@@ -312,35 +335,68 @@ module Neo4j
312
335
  end
313
336
  end
314
337
 
315
- def break_deeper_query
316
- copy.tap do |new_query|
317
- new_query.add_clauses [nil]
338
+ class PartitionedClauses
339
+ def initialize(clauses)
340
+ @clauses = clauses
341
+ @partitioning = [[]]
318
342
  end
319
- end
320
343
 
321
- def partitioned_clauses
322
- partitioning = [[]]
344
+ include Enumerable
345
+
346
+ def each
347
+ generate_partitioning!
348
+
349
+ @partitioning.each { |partition| yield partition }
350
+ end
323
351
 
324
- @clauses.each do |clause|
325
- if clause.nil? && partitioning.last != []
326
- partitioning << []
327
- else
328
- partitioning.last << clause
352
+ def generate_partitioning!
353
+ @partitioning = [[]]
354
+
355
+ @clauses.each do |clause|
356
+ if clause.nil? && !fresh_partition?
357
+ @partitioning << []
358
+ elsif clause_is_order_or_limit_directly_following_with_or_order?(clause)
359
+ second_to_last << clause
360
+ elsif clause_is_with_following_order_or_limit?(clause)
361
+ second_to_last << clause
362
+ second_to_last.sort_by! { |c| c.is_a?(::Neo4j::Core::QueryClauses::OrderClause) ? 1 : 0 }
363
+ else
364
+ @partitioning.last << clause
365
+ end
329
366
  end
330
367
  end
331
368
 
332
- partitioning
333
- end
369
+ private
334
370
 
335
- def merge_params
336
- @merge_params ||= @clauses.compact.inject(@_params) { |params, clause| params.merge(clause.params) }
337
- end
371
+ def fresh_partition?
372
+ @partitioning.last == []
373
+ end
338
374
 
339
- def sanitize_params(params)
340
- passthrough_classes = [String, Numeric, Array, Regexp]
341
- params.each do |key, value|
342
- params[key] = value.to_s if not passthrough_classes.any? { |klass| value.is_a?(klass) }
375
+ def second_to_last
376
+ @partitioning[-2]
343
377
  end
378
+
379
+ def clause_is_order_or_limit_directly_following_with_or_order?(clause)
380
+ self.class.clause_is_order_or_limit?(clause) &&
381
+ @partitioning[-2] &&
382
+ (@partitioning[-2].last.is_a?(::Neo4j::Core::QueryClauses::WithClause) ||
383
+ @partitioning[-2].last.is_a?(::Neo4j::Core::QueryClauses::OrderClause))
384
+ end
385
+
386
+ def clause_is_with_following_order_or_limit?(clause)
387
+ clause.is_a?(::Neo4j::Core::QueryClauses::WithClause) &&
388
+ @partitioning[-2] && @partitioning[-2].any? { |c| self.class.clause_is_order_or_limit?(c) }
389
+ end
390
+
391
+ def self.clause_is_order_or_limit?(clause)
392
+ clause.is_a?(::Neo4j::Core::QueryClauses::OrderClause) ||
393
+ clause.is_a?(::Neo4j::Core::QueryClauses::LimitClause)
394
+ end
395
+ end
396
+
397
+ def merge_params
398
+ @clauses.compact!
399
+ @merge_params ||= @clauses.inject(@_params) { |params, clause| params.merge!(clause.params) }
344
400
  end
345
401
  end
346
402
  end