activegraph 10.0.0.pre.alpha.6

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.
Files changed (142) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +1989 -0
  3. data/CONTRIBUTORS +12 -0
  4. data/Gemfile +24 -0
  5. data/README.md +107 -0
  6. data/bin/rake +17 -0
  7. data/config/locales/en.yml +5 -0
  8. data/config/neo4j/add_classnames.yml +1 -0
  9. data/config/neo4j/config.yml +38 -0
  10. data/lib/neo4j.rb +116 -0
  11. data/lib/neo4j/active_base.rb +89 -0
  12. data/lib/neo4j/active_node.rb +108 -0
  13. data/lib/neo4j/active_node/callbacks.rb +8 -0
  14. data/lib/neo4j/active_node/dependent.rb +11 -0
  15. data/lib/neo4j/active_node/dependent/association_methods.rb +49 -0
  16. data/lib/neo4j/active_node/dependent/query_proxy_methods.rb +51 -0
  17. data/lib/neo4j/active_node/enum.rb +26 -0
  18. data/lib/neo4j/active_node/has_n.rb +612 -0
  19. data/lib/neo4j/active_node/has_n/association.rb +278 -0
  20. data/lib/neo4j/active_node/has_n/association/rel_factory.rb +61 -0
  21. data/lib/neo4j/active_node/has_n/association/rel_wrapper.rb +23 -0
  22. data/lib/neo4j/active_node/has_n/association_cypher_methods.rb +108 -0
  23. data/lib/neo4j/active_node/id_property.rb +224 -0
  24. data/lib/neo4j/active_node/id_property/accessor.rb +62 -0
  25. data/lib/neo4j/active_node/initialize.rb +21 -0
  26. data/lib/neo4j/active_node/labels.rb +207 -0
  27. data/lib/neo4j/active_node/labels/index.rb +37 -0
  28. data/lib/neo4j/active_node/labels/reloading.rb +21 -0
  29. data/lib/neo4j/active_node/node_list_formatter.rb +13 -0
  30. data/lib/neo4j/active_node/node_wrapper.rb +54 -0
  31. data/lib/neo4j/active_node/orm_adapter.rb +82 -0
  32. data/lib/neo4j/active_node/persistence.rb +187 -0
  33. data/lib/neo4j/active_node/property.rb +60 -0
  34. data/lib/neo4j/active_node/query.rb +76 -0
  35. data/lib/neo4j/active_node/query/query_proxy.rb +374 -0
  36. data/lib/neo4j/active_node/query/query_proxy_eager_loading.rb +177 -0
  37. data/lib/neo4j/active_node/query/query_proxy_eager_loading/association_tree.rb +75 -0
  38. data/lib/neo4j/active_node/query/query_proxy_enumerable.rb +110 -0
  39. data/lib/neo4j/active_node/query/query_proxy_find_in_batches.rb +19 -0
  40. data/lib/neo4j/active_node/query/query_proxy_link.rb +139 -0
  41. data/lib/neo4j/active_node/query/query_proxy_methods.rb +302 -0
  42. data/lib/neo4j/active_node/query/query_proxy_methods_of_mass_updating.rb +86 -0
  43. data/lib/neo4j/active_node/query_methods.rb +68 -0
  44. data/lib/neo4j/active_node/reflection.rb +86 -0
  45. data/lib/neo4j/active_node/rels.rb +11 -0
  46. data/lib/neo4j/active_node/scope.rb +166 -0
  47. data/lib/neo4j/active_node/unpersisted.rb +48 -0
  48. data/lib/neo4j/active_node/validations.rb +59 -0
  49. data/lib/neo4j/active_rel.rb +67 -0
  50. data/lib/neo4j/active_rel/callbacks.rb +15 -0
  51. data/lib/neo4j/active_rel/initialize.rb +28 -0
  52. data/lib/neo4j/active_rel/persistence.rb +134 -0
  53. data/lib/neo4j/active_rel/persistence/query_factory.rb +95 -0
  54. data/lib/neo4j/active_rel/property.rb +95 -0
  55. data/lib/neo4j/active_rel/query.rb +101 -0
  56. data/lib/neo4j/active_rel/rel_wrapper.rb +31 -0
  57. data/lib/neo4j/active_rel/related_node.rb +87 -0
  58. data/lib/neo4j/active_rel/types.rb +82 -0
  59. data/lib/neo4j/active_rel/validations.rb +8 -0
  60. data/lib/neo4j/ansi.rb +14 -0
  61. data/lib/neo4j/class_arguments.rb +39 -0
  62. data/lib/neo4j/config.rb +135 -0
  63. data/lib/neo4j/core.rb +14 -0
  64. data/lib/neo4j/core/connection_failed_error.rb +6 -0
  65. data/lib/neo4j/core/cypher_error.rb +37 -0
  66. data/lib/neo4j/core/driver.rb +66 -0
  67. data/lib/neo4j/core/has_uri.rb +63 -0
  68. data/lib/neo4j/core/instrumentable.rb +36 -0
  69. data/lib/neo4j/core/label.rb +158 -0
  70. data/lib/neo4j/core/logging.rb +44 -0
  71. data/lib/neo4j/core/node.rb +23 -0
  72. data/lib/neo4j/core/querable.rb +88 -0
  73. data/lib/neo4j/core/query.rb +487 -0
  74. data/lib/neo4j/core/query_builder.rb +32 -0
  75. data/lib/neo4j/core/query_clauses.rb +727 -0
  76. data/lib/neo4j/core/query_ext.rb +24 -0
  77. data/lib/neo4j/core/query_find_in_batches.rb +49 -0
  78. data/lib/neo4j/core/relationship.rb +13 -0
  79. data/lib/neo4j/core/responses.rb +50 -0
  80. data/lib/neo4j/core/result.rb +33 -0
  81. data/lib/neo4j/core/schema.rb +30 -0
  82. data/lib/neo4j/core/schema_errors.rb +12 -0
  83. data/lib/neo4j/core/wrappable.rb +30 -0
  84. data/lib/neo4j/errors.rb +57 -0
  85. data/lib/neo4j/migration.rb +148 -0
  86. data/lib/neo4j/migrations.rb +27 -0
  87. data/lib/neo4j/migrations/base.rb +77 -0
  88. data/lib/neo4j/migrations/check_pending.rb +20 -0
  89. data/lib/neo4j/migrations/helpers.rb +105 -0
  90. data/lib/neo4j/migrations/helpers/id_property.rb +75 -0
  91. data/lib/neo4j/migrations/helpers/relationships.rb +66 -0
  92. data/lib/neo4j/migrations/helpers/schema.rb +51 -0
  93. data/lib/neo4j/migrations/migration_file.rb +24 -0
  94. data/lib/neo4j/migrations/runner.rb +195 -0
  95. data/lib/neo4j/migrations/schema.rb +44 -0
  96. data/lib/neo4j/migrations/schema_migration.rb +14 -0
  97. data/lib/neo4j/model_schema.rb +139 -0
  98. data/lib/neo4j/paginated.rb +27 -0
  99. data/lib/neo4j/railtie.rb +105 -0
  100. data/lib/neo4j/schema/operation.rb +102 -0
  101. data/lib/neo4j/shared.rb +60 -0
  102. data/lib/neo4j/shared/attributes.rb +216 -0
  103. data/lib/neo4j/shared/callbacks.rb +68 -0
  104. data/lib/neo4j/shared/cypher.rb +37 -0
  105. data/lib/neo4j/shared/declared_properties.rb +204 -0
  106. data/lib/neo4j/shared/declared_property.rb +109 -0
  107. data/lib/neo4j/shared/declared_property/index.rb +37 -0
  108. data/lib/neo4j/shared/enum.rb +167 -0
  109. data/lib/neo4j/shared/filtered_hash.rb +79 -0
  110. data/lib/neo4j/shared/identity.rb +34 -0
  111. data/lib/neo4j/shared/initialize.rb +64 -0
  112. data/lib/neo4j/shared/marshal.rb +23 -0
  113. data/lib/neo4j/shared/mass_assignment.rb +64 -0
  114. data/lib/neo4j/shared/permitted_attributes.rb +28 -0
  115. data/lib/neo4j/shared/persistence.rb +282 -0
  116. data/lib/neo4j/shared/property.rb +240 -0
  117. data/lib/neo4j/shared/query_factory.rb +102 -0
  118. data/lib/neo4j/shared/rel_type_converters.rb +43 -0
  119. data/lib/neo4j/shared/serialized_properties.rb +30 -0
  120. data/lib/neo4j/shared/type_converters.rb +433 -0
  121. data/lib/neo4j/shared/typecasted_attributes.rb +98 -0
  122. data/lib/neo4j/shared/typecaster.rb +53 -0
  123. data/lib/neo4j/shared/validations.rb +44 -0
  124. data/lib/neo4j/tasks/migration.rake +202 -0
  125. data/lib/neo4j/timestamps.rb +11 -0
  126. data/lib/neo4j/timestamps/created.rb +9 -0
  127. data/lib/neo4j/timestamps/updated.rb +9 -0
  128. data/lib/neo4j/transaction.rb +139 -0
  129. data/lib/neo4j/type_converters.rb +7 -0
  130. data/lib/neo4j/undeclared_properties.rb +53 -0
  131. data/lib/neo4j/version.rb +3 -0
  132. data/lib/neo4j/wrapper.rb +4 -0
  133. data/lib/rails/generators/neo4j/migration/migration_generator.rb +14 -0
  134. data/lib/rails/generators/neo4j/migration/templates/migration.erb +9 -0
  135. data/lib/rails/generators/neo4j/model/model_generator.rb +88 -0
  136. data/lib/rails/generators/neo4j/model/templates/migration.erb +9 -0
  137. data/lib/rails/generators/neo4j/model/templates/model.erb +15 -0
  138. data/lib/rails/generators/neo4j/upgrade_v8/templates/migration.erb +17 -0
  139. data/lib/rails/generators/neo4j/upgrade_v8/upgrade_v8_generator.rb +32 -0
  140. data/lib/rails/generators/neo4j_generator.rb +119 -0
  141. data/neo4j.gemspec +51 -0
  142. metadata +421 -0
@@ -0,0 +1,487 @@
1
+ require 'neo4j/core/query_clauses'
2
+ require 'neo4j/core/query_find_in_batches'
3
+ require 'active_support/notifications'
4
+
5
+ module Neo4j
6
+ module Core
7
+ # Allows for generation of cypher queries via ruby method calls (inspired by ActiveRecord / arel syntax)
8
+ #
9
+ # Can be used to express cypher queries in ruby nicely, or to more easily generate queries programatically.
10
+ #
11
+ # Also, queries can be passed around an application to progressively build a query across different concerns
12
+ #
13
+ # See also the following link for full cypher language documentation:
14
+ # http://docs.neo4j.org/chunked/milestone/cypher-query-lang.html
15
+ class Query
16
+ include Neo4j::Core::QueryClauses
17
+ include Neo4j::Core::QueryFindInBatches
18
+ DEFINED_CLAUSES = {}
19
+
20
+
21
+ attr_accessor :clauses
22
+
23
+ class Parameters
24
+ def initialize(hash = nil)
25
+ @parameters = (hash || {})
26
+ end
27
+
28
+ def to_hash
29
+ @parameters
30
+ end
31
+
32
+ def copy
33
+ self.class.new(@parameters.dup)
34
+ end
35
+
36
+ def add_param(key, value)
37
+ free_param_key(key).tap do |k|
38
+ @parameters[k.freeze] = value
39
+ end
40
+ end
41
+
42
+ def remove_param(key)
43
+ @parameters.delete(key.to_sym)
44
+ end
45
+
46
+ def add_params(params)
47
+ params.map do |key, value|
48
+ add_param(key, value)
49
+ end
50
+ end
51
+
52
+ private
53
+
54
+ def free_param_key(key)
55
+ k = key.to_sym
56
+
57
+ return k if !@parameters.key?(k)
58
+
59
+ i = 2
60
+ i += 1 while @parameters.key?("#{key}#{i}".to_sym)
61
+
62
+ "#{key}#{i}".to_sym
63
+ end
64
+ end
65
+
66
+ class << self
67
+ attr_accessor :pretty_cypher
68
+ end
69
+
70
+ def initialize(options = {})
71
+ @session = options[:session]
72
+
73
+ @options = options
74
+ @clauses = []
75
+ @_params = {}
76
+ @params = Parameters.new
77
+ end
78
+
79
+ def inspect
80
+ "#<Query CYPHER: #{ANSI::YELLOW}#{to_cypher.inspect}#{ANSI::CLEAR}>"
81
+ end
82
+
83
+ # @method start *args
84
+ # START clause
85
+ # @return [Query]
86
+
87
+ # @method match *args
88
+ # MATCH clause
89
+ # @return [Query]
90
+
91
+ # @method optional_match *args
92
+ # OPTIONAL MATCH clause
93
+ # @return [Query]
94
+
95
+ # @method using *args
96
+ # USING clause
97
+ # @return [Query]
98
+
99
+ # @method where *args
100
+ # WHERE clause
101
+ # @return [Query]
102
+
103
+ # @method with *args
104
+ # WITH clause
105
+ # @return [Query]
106
+
107
+ # @method with_distinct *args
108
+ # WITH clause with DISTINCT specified
109
+ # @return [Query]
110
+
111
+ # @method order *args
112
+ # ORDER BY clause
113
+ # @return [Query]
114
+
115
+ # @method limit *args
116
+ # LIMIT clause
117
+ # @return [Query]
118
+
119
+ # @method skip *args
120
+ # SKIP clause
121
+ # @return [Query]
122
+
123
+ # @method set *args
124
+ # SET clause
125
+ # @return [Query]
126
+
127
+ # @method remove *args
128
+ # REMOVE clause
129
+ # @return [Query]
130
+
131
+ # @method unwind *args
132
+ # UNWIND clause
133
+ # @return [Query]
134
+
135
+ # @method return *args
136
+ # RETURN clause
137
+ # @return [Query]
138
+
139
+ # @method create *args
140
+ # CREATE clause
141
+ # @return [Query]
142
+
143
+ # @method create_unique *args
144
+ # CREATE UNIQUE clause
145
+ # @return [Query]
146
+
147
+ # @method merge *args
148
+ # MERGE clause
149
+ # @return [Query]
150
+
151
+ # @method on_create_set *args
152
+ # ON CREATE SET clause
153
+ # @return [Query]
154
+
155
+ # @method on_match_set *args
156
+ # ON MATCH SET clause
157
+ # @return [Query]
158
+
159
+ # @method delete *args
160
+ # DELETE clause
161
+ # @return [Query]
162
+
163
+ # @method detach_delete *args
164
+ # DETACH DELETE clause
165
+ # @return [Query]
166
+
167
+ METHODS = %w[start match optional_match call using where create create_unique merge set on_create_set on_match_set remove unwind delete detach_delete with with_distinct return order skip limit] # rubocop:disable Metrics/LineLength
168
+ BREAK_METHODS = %(with with_distinct call)
169
+
170
+ CLAUSIFY_CLAUSE = proc { |method| const_get(method.to_s.split('_').map(&:capitalize).join + 'Clause') }
171
+ CLAUSES = METHODS.map(&CLAUSIFY_CLAUSE)
172
+
173
+ METHODS.each_with_index do |clause, i|
174
+ clause_class = CLAUSES[i]
175
+
176
+ DEFINED_CLAUSES[clause.to_sym] = clause_class
177
+ define_method(clause) do |*args|
178
+ result = build_deeper_query(clause_class, args)
179
+
180
+ BREAK_METHODS.include?(clause) ? result.break : result
181
+ end
182
+ end
183
+
184
+ alias offset skip
185
+ alias order_by order
186
+
187
+ # Clears out previous order clauses and allows only for those specified by args
188
+ def reorder(*args)
189
+ query = copy
190
+
191
+ query.remove_clause_class(OrderClause)
192
+ query.order(*args)
193
+ end
194
+
195
+ # Works the same as the #where method, but the clause is surrounded by a
196
+ # Cypher NOT() function
197
+ def where_not(*args)
198
+ build_deeper_query(WhereClause, args, not: true)
199
+ end
200
+
201
+ # Works the same as the #set method, but when given a nested array it will set properties rather than setting entire objects
202
+ # @example
203
+ # # Creates a query representing the cypher: MATCH (n:Person) SET n.age = 19
204
+ # Query.new.match(n: :Person).set_props(n: {age: 19})
205
+ def set_props(*args) # rubocop:disable Naming/AccessorMethodName
206
+ build_deeper_query(SetClause, args, set_props: true)
207
+ end
208
+
209
+ # Allows what's been built of the query so far to be frozen and the rest built anew. Can be called multiple times in a string of method calls
210
+ # @example
211
+ # # Creates a query representing the cypher: MATCH (q:Person), r:Car MATCH (p: Person)-->q
212
+ # Query.new.match(q: Person).match('r:Car').break.match('(p: Person)-->q')
213
+ def break
214
+ build_deeper_query(nil)
215
+ end
216
+
217
+ # Allows for the specification of values for params specified in query
218
+ # @example
219
+ # # Creates a query representing the cypher: MATCH (q: Person {id: {id}})
220
+ # # Calls to params don't affect the cypher query generated, but the params will be
221
+ # # Passed down when the query is made
222
+ # Query.new.match('(q: Person {id: {id}})').params(id: 12)
223
+ #
224
+ def params(args)
225
+ copy.tap { |new_query| new_query.instance_variable_get('@params'.freeze).add_params(args) }
226
+ end
227
+
228
+ def unwrapped
229
+ @_unwrapped_obj = true
230
+ self
231
+ end
232
+
233
+ def unwrapped?
234
+ !!@_unwrapped_obj
235
+ end
236
+
237
+ def response
238
+ return @response if @response
239
+
240
+ @response = Neo4j::Transaction.query(self, transaction: Transaction.root, wrap_level: (:core_entity if unwrapped?))
241
+ end
242
+
243
+ def raise_if_cypher_error!(response)
244
+ response.raise_cypher_error if response.respond_to?(:error?) && response.error?
245
+ end
246
+
247
+ def match_nodes(hash, optional_match = false)
248
+ hash.inject(self) do |query, (variable, node_object)|
249
+ neo_id = (node_object.respond_to?(:neo_id) ? node_object.neo_id : node_object)
250
+
251
+ match_method = optional_match ? :optional_match : :match
252
+ query.send(match_method, variable).where(variable => {neo_id: neo_id})
253
+ end
254
+ end
255
+
256
+ def optional_match_nodes(hash)
257
+ match_nodes(hash, true)
258
+ end
259
+
260
+ include Enumerable
261
+
262
+ def count(var = nil)
263
+ v = var.nil? ? '*' : var
264
+ pluck("count(#{v})").first
265
+ end
266
+
267
+ def each
268
+ response.each { |object| yield object }
269
+ end
270
+
271
+ # @method to_a
272
+ # Class is Enumerable. Each yield is a Hash with the key matching the variable returned and the value being the value for that key from the response
273
+ # @return [Array]
274
+ # @raise [Neo4j::Server::CypherResponse::ResponseError] Raises errors from neo4j server
275
+
276
+
277
+ # Executes a query without returning the result
278
+ # @return [Boolean] true if successful
279
+ # @raise [Neo4j::Server::CypherResponse::ResponseError] Raises errors from neo4j server
280
+ def exec
281
+ response
282
+
283
+ true
284
+ end
285
+
286
+ # Return the specified columns as an array.
287
+ # If one column is specified, a one-dimensional array is returned with the values of that column
288
+ # If two columns are specified, a n-dimensional array is returned with the values of those columns
289
+ #
290
+ # @example
291
+ # Query.new.match(n: :Person).return(p: :name}.pluck(p: :name) # => Array of names
292
+ # @example
293
+ # Query.new.match(n: :Person).return(p: :name}.pluck('p, DISTINCT p.name') # => Array of [node, name] pairs
294
+ #
295
+ def pluck(*columns)
296
+ fail ArgumentError, 'No columns specified for Query#pluck' if columns.size.zero?
297
+
298
+ query = return_query(columns)
299
+ columns = query.response.columns
300
+
301
+ if columns.size == 1
302
+ column = columns[0]
303
+ query.map { |row| row[column] }
304
+ else
305
+ query.map { |row| columns.map { |column| row[column] } }
306
+ end
307
+ end
308
+
309
+ def return_query(columns)
310
+ query = copy
311
+ query.remove_clause_class(ReturnClause)
312
+
313
+ query.return(*columns)
314
+ end
315
+
316
+ # Returns a CYPHER query string from the object query representation
317
+ # @example
318
+ # Query.new.match(p: :Person).where(p: {age: 30}) # => "MATCH (p:Person) WHERE p.age = 30
319
+ #
320
+ # @return [String] Resulting cypher query string
321
+ EMPTY = ' '
322
+ NEWLINE = "\n"
323
+ def to_cypher(options = {})
324
+ join_string = options[:pretty] ? NEWLINE : EMPTY
325
+
326
+ cypher_string = partitioned_clauses.map do |clauses|
327
+ clauses_by_class = clauses.group_by(&:class)
328
+
329
+ cypher_parts = CLAUSES.map do |clause_class|
330
+ clause_class.to_cypher(clauses, options[:pretty]) if clauses = clauses_by_class[clause_class]
331
+ end.compact
332
+
333
+ cypher_parts.join(join_string).tap(&:strip!)
334
+ end.join(join_string)
335
+
336
+ cypher_string = "CYPHER #{@options[:parser]} #{cypher_string}" if @options[:parser]
337
+ cypher_string.tap(&:strip!)
338
+ end
339
+ alias cypher to_cypher
340
+
341
+ def pretty_cypher
342
+ to_cypher(pretty: true)
343
+ end
344
+
345
+ def context
346
+ @options[:context]
347
+ end
348
+
349
+ def parameters
350
+ to_cypher
351
+ merge_params
352
+ end
353
+
354
+ def partitioned_clauses
355
+ @partitioned_clauses ||= PartitionedClauses.new(@clauses)
356
+ end
357
+
358
+ def print_cypher
359
+ puts to_cypher(pretty: true).gsub(/\e[^m]+m/, '')
360
+ end
361
+
362
+ # Returns a CYPHER query specifying the union of the callee object's query and the argument's query
363
+ #
364
+ # @example
365
+ # # Generates cypher: MATCH (n:Person) UNION MATCH (o:Person) WHERE o.age = 10
366
+ # q = Neo4j::Core::Query.new.match(o: :Person).where(o: {age: 10})
367
+ # result = Neo4j::Core::Query.new.match(n: :Person).union_cypher(q)
368
+ #
369
+ # @param other [Query] Second half of UNION
370
+ # @param options [Hash] Specify {all: true} to use UNION ALL
371
+ # @return [String] Resulting UNION cypher query string
372
+ def union_cypher(other, options = {})
373
+ "#{to_cypher} UNION#{options[:all] ? ' ALL' : ''} #{other.to_cypher}"
374
+ end
375
+
376
+ def &(other)
377
+ self.class.new(session: @session).tap do |new_query|
378
+ new_query.options = options.merge(other.options)
379
+ new_query.clauses = clauses + other.clauses
380
+ end.params(other._params)
381
+ end
382
+
383
+ def copy
384
+ dup.tap do |query|
385
+ to_cypher
386
+ query.instance_variable_set('@params'.freeze, @params.copy)
387
+ query.instance_variable_set('@partitioned_clauses'.freeze, nil)
388
+ query.instance_variable_set('@response'.freeze, nil)
389
+ end
390
+ end
391
+
392
+ def clause?(method)
393
+ clause_class = DEFINED_CLAUSES[method] || CLAUSIFY_CLAUSE.call(method)
394
+ clauses.any? { |clause| clause.is_a?(clause_class) }
395
+ end
396
+
397
+ protected
398
+
399
+ attr_accessor :session, :options, :_params
400
+
401
+ def add_clauses(clauses)
402
+ @clauses += clauses
403
+ end
404
+
405
+ def remove_clause_class(clause_class)
406
+ @clauses = @clauses.reject { |clause| clause.is_a?(clause_class) }
407
+ end
408
+
409
+ private
410
+
411
+ def build_deeper_query(clause_class, args = {}, options = {})
412
+ copy.tap do |new_query|
413
+ new_query.add_clauses [nil] if [nil, WithClause].include?(clause_class)
414
+ new_query.add_clauses clause_class.from_args(args, new_query.instance_variable_get('@params'.freeze), options) if clause_class
415
+ end
416
+ end
417
+
418
+ class PartitionedClauses
419
+ def initialize(clauses)
420
+ @clauses = clauses
421
+ @partitioning = [[]]
422
+ end
423
+
424
+ include Enumerable
425
+
426
+ def each
427
+ generate_partitioning!
428
+
429
+ @partitioning.each { |partition| yield partition }
430
+ end
431
+
432
+ def generate_partitioning!
433
+ @partitioning = [[]]
434
+
435
+ @clauses.each do |clause|
436
+ if clause.nil? && !fresh_partition?
437
+ @partitioning << []
438
+ elsif clause_is_order_or_limit_directly_following_with_or_order?(clause)
439
+ second_to_last << clause
440
+ elsif clause_is_with_following_order_or_limit?(clause)
441
+ second_to_last << clause
442
+ second_to_last.sort_by! { |c| c.is_a?(::Neo4j::Core::QueryClauses::OrderClause) ? 1 : 0 }
443
+ else
444
+ @partitioning.last << clause
445
+ end
446
+ end
447
+ end
448
+
449
+ private
450
+
451
+ def fresh_partition?
452
+ @partitioning.last == []
453
+ end
454
+
455
+ def second_to_last
456
+ @partitioning[-2]
457
+ end
458
+
459
+ def clause_is_order_or_limit_directly_following_with_or_order?(clause)
460
+ self.class.clause_is_order_or_limit?(clause) &&
461
+ @partitioning[-2] &&
462
+ @partitioning[-1].empty? &&
463
+ (@partitioning[-2].last.is_a?(::Neo4j::Core::QueryClauses::WithClause) ||
464
+ @partitioning[-2].last.is_a?(::Neo4j::Core::QueryClauses::OrderClause))
465
+ end
466
+
467
+ def clause_is_with_following_order_or_limit?(clause)
468
+ clause.is_a?(::Neo4j::Core::QueryClauses::WithClause) &&
469
+ @partitioning[-2] && @partitioning[-2].any? { |c| self.class.clause_is_order_or_limit?(c) }
470
+ end
471
+
472
+ class << self
473
+ def clause_is_order_or_limit?(clause)
474
+ clause.is_a?(::Neo4j::Core::QueryClauses::OrderClause) ||
475
+ clause.is_a?(::Neo4j::Core::QueryClauses::LimitClause)
476
+ end
477
+ end
478
+ end
479
+
480
+ # SHOULD BE DEPRECATED
481
+ def merge_params
482
+ @merge_params_base ||= @clauses.compact.inject({}) { |params, clause| params.merge!(clause.params) }
483
+ @params.to_hash.merge(@merge_params_base)
484
+ end
485
+ end
486
+ end
487
+ end