dm-core 0.10.1 → 0.10.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (116) hide show
  1. data/.autotest +29 -0
  2. data/.document +5 -0
  3. data/.gitignore +27 -0
  4. data/LICENSE +20 -0
  5. data/{README.txt → README.rdoc} +14 -3
  6. data/Rakefile +23 -22
  7. data/VERSION +1 -0
  8. data/dm-core.gemspec +201 -10
  9. data/lib/dm-core.rb +32 -23
  10. data/lib/dm-core/adapters.rb +0 -1
  11. data/lib/dm-core/adapters/data_objects_adapter.rb +230 -151
  12. data/lib/dm-core/adapters/mysql_adapter.rb +7 -8
  13. data/lib/dm-core/adapters/oracle_adapter.rb +39 -59
  14. data/lib/dm-core/adapters/postgres_adapter.rb +0 -1
  15. data/lib/dm-core/adapters/sqlite3_adapter.rb +5 -0
  16. data/lib/dm-core/adapters/sqlserver_adapter.rb +114 -0
  17. data/lib/dm-core/adapters/yaml_adapter.rb +0 -5
  18. data/lib/dm-core/associations/many_to_many.rb +118 -56
  19. data/lib/dm-core/associations/many_to_one.rb +48 -21
  20. data/lib/dm-core/associations/one_to_many.rb +8 -30
  21. data/lib/dm-core/associations/one_to_one.rb +1 -5
  22. data/lib/dm-core/associations/relationship.rb +89 -97
  23. data/lib/dm-core/collection.rb +299 -184
  24. data/lib/dm-core/core_ext/enumerable.rb +28 -0
  25. data/lib/dm-core/core_ext/kernel.rb +0 -2
  26. data/lib/dm-core/migrations.rb +314 -170
  27. data/lib/dm-core/model.rb +97 -66
  28. data/lib/dm-core/model/descendant_set.rb +1 -1
  29. data/lib/dm-core/model/hook.rb +0 -3
  30. data/lib/dm-core/model/property.rb +7 -10
  31. data/lib/dm-core/model/relationship.rb +79 -26
  32. data/lib/dm-core/model/scope.rb +3 -4
  33. data/lib/dm-core/property.rb +152 -90
  34. data/lib/dm-core/property_set.rb +18 -37
  35. data/lib/dm-core/query.rb +452 -153
  36. data/lib/dm-core/query/conditions/comparison.rb +266 -173
  37. data/lib/dm-core/query/conditions/operation.rb +499 -57
  38. data/lib/dm-core/query/direction.rb +0 -3
  39. data/lib/dm-core/query/operator.rb +0 -4
  40. data/lib/dm-core/query/path.rb +10 -12
  41. data/lib/dm-core/query/sort.rb +4 -10
  42. data/lib/dm-core/repository.rb +10 -6
  43. data/lib/dm-core/resource.rb +343 -148
  44. data/lib/dm-core/spec/adapter_shared_spec.rb +17 -1
  45. data/lib/dm-core/spec/data_objects_adapter_shared_spec.rb +277 -17
  46. data/lib/dm-core/support/chainable.rb +0 -2
  47. data/lib/dm-core/support/equalizer.rb +27 -3
  48. data/lib/dm-core/transaction.rb +75 -75
  49. data/lib/dm-core/type.rb +19 -5
  50. data/lib/dm-core/types/discriminator.rb +4 -4
  51. data/lib/dm-core/types/object.rb +2 -7
  52. data/lib/dm-core/types/paranoid_boolean.rb +8 -2
  53. data/lib/dm-core/types/paranoid_datetime.rb +8 -2
  54. data/lib/dm-core/version.rb +1 -1
  55. data/script/performance.rb +7 -7
  56. data/script/profile.rb +6 -6
  57. data/spec/lib/collection_helpers.rb +2 -2
  58. data/spec/lib/pending_helpers.rb +22 -3
  59. data/spec/lib/rspec_immediate_feedback_formatter.rb +1 -0
  60. data/spec/public/associations/many_to_many_spec.rb +6 -4
  61. data/spec/public/associations/many_to_one_spec.rb +10 -1
  62. data/spec/public/associations/many_to_one_with_boolean_cpk_spec.rb +39 -0
  63. data/spec/public/associations/one_to_many_spec.rb +4 -3
  64. data/spec/public/associations/one_to_one_spec.rb +19 -1
  65. data/spec/public/associations/one_to_one_with_boolean_cpk_spec.rb +45 -0
  66. data/spec/public/collection_spec.rb +4 -3
  67. data/spec/public/migrations_spec.rb +144 -0
  68. data/spec/public/model/relationship_spec.rb +115 -55
  69. data/spec/public/model_spec.rb +13 -13
  70. data/spec/public/property/object_spec.rb +106 -0
  71. data/spec/public/property_spec.rb +18 -14
  72. data/spec/public/resource_spec.rb +10 -1
  73. data/spec/public/sel_spec.rb +16 -49
  74. data/spec/public/setup_spec.rb +1 -1
  75. data/spec/public/shared/association_collection_shared_spec.rb +6 -14
  76. data/spec/public/shared/collection_finder_shared_spec.rb +267 -0
  77. data/spec/public/shared/collection_shared_spec.rb +214 -217
  78. data/spec/public/shared/finder_shared_spec.rb +259 -365
  79. data/spec/public/shared/resource_shared_spec.rb +524 -248
  80. data/spec/public/transaction_spec.rb +27 -3
  81. data/spec/public/types/discriminator_spec.rb +1 -1
  82. data/spec/rcov.opts +6 -0
  83. data/spec/semipublic/adapters/sqlserver_adapter_spec.rb +17 -0
  84. data/spec/semipublic/associations/many_to_one_spec.rb +3 -20
  85. data/spec/semipublic/associations_spec.rb +2 -2
  86. data/spec/semipublic/collection_spec.rb +0 -32
  87. data/spec/semipublic/model_spec.rb +96 -0
  88. data/spec/semipublic/property_spec.rb +3 -3
  89. data/spec/semipublic/query/conditions/comparison_spec.rb +1719 -0
  90. data/spec/semipublic/query/conditions/operation_spec.rb +1292 -0
  91. data/spec/semipublic/query_spec.rb +1285 -144
  92. data/spec/semipublic/resource_spec.rb +0 -24
  93. data/spec/semipublic/shared/resource_shared_spec.rb +103 -38
  94. data/spec/spec.opts +1 -1
  95. data/spec/spec_helper.rb +15 -6
  96. data/tasks/ci.rake +1 -0
  97. data/tasks/metrics.rake +37 -0
  98. data/tasks/spec.rake +41 -0
  99. data/tasks/yard.rake +9 -0
  100. data/tasks/yardstick.rake +19 -0
  101. metadata +99 -29
  102. data/CONTRIBUTING +0 -51
  103. data/FAQ +0 -93
  104. data/History.txt +0 -27
  105. data/MIT-LICENSE +0 -22
  106. data/Manifest.txt +0 -121
  107. data/QUICKLINKS +0 -11
  108. data/SPECS +0 -35
  109. data/TODO +0 -1
  110. data/spec/semipublic/query/conditions_spec.rb +0 -528
  111. data/tasks/ci.rb +0 -24
  112. data/tasks/dm.rb +0 -58
  113. data/tasks/doc.rb +0 -17
  114. data/tasks/gemspec.rb +0 -23
  115. data/tasks/hoe.rb +0 -45
  116. data/tasks/install.rb +0 -18
@@ -126,7 +126,6 @@ module DataMapper
126
126
  end
127
127
 
128
128
  extendable do
129
- # TODO: document
130
129
  # @api private
131
130
  def const_added(const_name)
132
131
  end
@@ -11,6 +11,56 @@ module DataMapper
11
11
  # You can extend and overwrite these copies without affecting the originals.
12
12
  class DataObjectsAdapter < AbstractAdapter
13
13
  extend Chainable
14
+ extend Deprecate
15
+
16
+ deprecate :query, :select
17
+
18
+ # Retrieve results using an SQL SELECT statement
19
+ #
20
+ # @param [String] statement
21
+ # the SQL SELECT statement
22
+ # @param [Array] *bind_values
23
+ # optional bind values to merge into the statement
24
+ #
25
+ # @return [Array]
26
+ # if fields > 1, return an Array of Struct objects
27
+ # if fields == 1, return an Array of objects
28
+ #
29
+ # @api public
30
+ def select(statement, *bind_values)
31
+ with_connection do |connection|
32
+ reader = connection.create_command(statement).execute_reader(*bind_values)
33
+ fields = reader.fields
34
+
35
+ begin
36
+ if fields.size > 1
37
+ select_fields(reader, fields)
38
+ else
39
+ select_field(reader)
40
+ end
41
+ ensure
42
+ reader.close
43
+ end
44
+ end
45
+ end
46
+
47
+ # Execute non-SELECT SQL query
48
+ #
49
+ # @param [String] statement
50
+ # the SQL statement
51
+ # @param [Array] *bind_values
52
+ # optional bind values to merge into the statement
53
+ #
54
+ # @return [DataObjects::Result]
55
+ # result with number of affected rows, and insert id if any
56
+ #
57
+ # @api public
58
+ def execute(statement, *bind_values)
59
+ with_connection do |connection|
60
+ command = connection.create_command(statement)
61
+ command.execute_non_query(*bind_values)
62
+ end
63
+ end
14
64
 
15
65
  # For each model instance in resources, issues an SQL INSERT
16
66
  # (or equivalent) statement to create a new record in the data store for
@@ -28,6 +78,8 @@ module DataMapper
28
78
  #
29
79
  # @api semipublic
30
80
  def create(resources)
81
+ name = self.name
82
+
31
83
  resources.each do |resource|
32
84
  model = resource.model
33
85
  serial = model.serial(name)
@@ -57,7 +109,7 @@ module DataMapper
57
109
  statement = insert_statement(model, properties, serial)
58
110
  result = execute(statement, *bind_values)
59
111
 
60
- if result.to_i == 1 && serial
112
+ if result.affected_rows == 1 && serial
61
113
  serial.set!(resource, result.insert_id)
62
114
  end
63
115
  end
@@ -114,9 +166,6 @@ module DataMapper
114
166
  def update(attributes, collection)
115
167
  query = collection.query
116
168
 
117
- # TODO: if the query contains any links, a limit or an offset
118
- # use a subselect to get the rows to be updated
119
-
120
169
  properties = []
121
170
  bind_values = []
122
171
 
@@ -131,7 +180,7 @@ module DataMapper
131
180
 
132
181
  bind_values.concat(conditions_bind_values)
133
182
 
134
- execute(statement, *bind_values).to_i
183
+ execute(statement, *bind_values).affected_rows
135
184
  end
136
185
 
137
186
  # Constructs and executes DELETE statement for given query
@@ -145,57 +194,12 @@ module DataMapper
145
194
  # @api semipublic
146
195
  def delete(collection)
147
196
  query = collection.query
148
-
149
- # TODO: if the query contains any links, a limit or an offset
150
- # use a subselect to get the rows to be deleted
151
-
152
197
  statement, bind_values = delete_statement(query)
153
- execute(statement, *bind_values).to_i
154
- end
155
-
156
- # Database-specific method
157
- # TODO: document
158
- # @api public
159
- def execute(statement, *bind_values)
160
- with_connection do |connection|
161
- command = connection.create_command(statement)
162
- command.execute_non_query(*bind_values)
163
- end
164
- end
165
-
166
- # TODO: document
167
- # @api public
168
- def query(statement, *bind_values)
169
- with_connection do |connection|
170
- reader = connection.create_command(statement).execute_reader(*bind_values)
171
- fields = reader.fields
172
-
173
- results = []
174
-
175
- begin
176
- if fields.size > 1
177
- fields = fields.map { |field| Extlib::Inflection.underscore(field).to_sym }
178
- struct = Struct.new(*fields)
179
-
180
- while reader.next!
181
- results << struct.new(*reader.values)
182
- end
183
- else
184
- while reader.next!
185
- results << reader.values.at(0)
186
- end
187
- end
188
- ensure
189
- reader.close
190
- end
191
-
192
- results
193
- end
198
+ execute(statement, *bind_values).affected_rows
194
199
  end
195
200
 
196
201
  protected
197
202
 
198
- # TODO: document
199
203
  # @api private
200
204
  def normalized_uri
201
205
  @normalized_uri ||=
@@ -225,6 +229,7 @@ module DataMapper
225
229
  def open_connection
226
230
  # DataObjects::Connection.new(uri) will give you back the right
227
231
  # driver based on the DataObjects::URI#scheme
232
+ connection_stack = self.connection_stack
228
233
  connection = connection_stack.last || DataObjects::Connection.new(normalized_uri)
229
234
  connection_stack << connection
230
235
  connection
@@ -234,14 +239,20 @@ module DataMapper
234
239
  #
235
240
  # @api semipublic
236
241
  def close_connection(connection)
242
+ connection_stack = self.connection_stack
237
243
  connection_stack.pop
238
244
  connection.close if connection_stack.empty?
239
245
  end
240
246
  end
241
247
 
248
+ # @api private
249
+ def connection_stack
250
+ connection_stack_for = Thread.current[:dm_do_connection_stack] ||= {}
251
+ connection_stack_for[self] ||= []
252
+ end
253
+
242
254
  private
243
255
 
244
- # TODO: document
245
256
  # @api public
246
257
  def initialize(name, uri_or_options)
247
258
  super
@@ -252,14 +263,6 @@ module DataMapper
252
263
  end
253
264
  end
254
265
 
255
- # TODO: document
256
- # @api private
257
- def connection_stack
258
- connection_stack_for = Thread.current[:dm_do_connection_stack] ||= {}
259
- connection_stack_for[self] ||= []
260
- end
261
-
262
- # TODO: document
263
266
  # @api private
264
267
  def with_connection
265
268
  begin
@@ -272,19 +275,45 @@ module DataMapper
272
275
  end
273
276
  end
274
277
 
278
+ # @api private
279
+ def select_fields(reader, fields)
280
+ fields = fields.map { |field| Extlib::Inflection.underscore(field).to_sym }
281
+ struct = Struct.new(*fields)
282
+
283
+ results = []
284
+
285
+ while reader.next!
286
+ results << struct.new(*reader.values)
287
+ end
288
+
289
+ results
290
+ end
291
+
292
+ # @api private
293
+ def select_field(reader)
294
+ results = []
295
+
296
+ while reader.next!
297
+ results << reader.values.at(0)
298
+ end
299
+
300
+ results
301
+ end
302
+
275
303
  # This module is just for organization. The methods are included into the
276
304
  # Adapter below.
277
305
  module SQL #:nodoc:
278
306
  IDENTIFIER_MAX_LENGTH = 128
279
307
 
280
- # TODO: document
281
308
  # @api semipublic
282
- def property_to_column_name(property, qualify)
309
+ def property_to_column_name(property, qualify, table_name = nil)
310
+ column_name = quote_name(property.field)
311
+
283
312
  if qualify
284
- table_name = property.model.storage_name(name)
285
- "#{quote_name(table_name)}.#{quote_name(property.field)}"
313
+ table_name ||= quote_name(property.model.storage_name(name))
314
+ "#{table_name}.#{column_name}"
286
315
  else
287
- quote_name(property.field)
316
+ column_name
288
317
  end
289
318
  end
290
319
 
@@ -315,7 +344,7 @@ module DataMapper
315
344
  qualify = query.links.any?
316
345
  fields = query.fields
317
346
  order_by = query.order
318
- group_by = if qualify || query.unique?
347
+ group_by = if query.unique?
319
348
  fields.select { |property| property.kind_of?(Property) }
320
349
  end
321
350
 
@@ -334,7 +363,7 @@ module DataMapper
334
363
  end
335
364
 
336
365
  # default construction of LIMIT and OFFSET
337
- # overriden in Oracle adapter
366
+ # overriden by some adapters (currently Oracle and SQL Server)
338
367
  def add_limit_offset!(statement, limit, offset, bind_values)
339
368
  if limit
340
369
  statement << ' LIMIT ?'
@@ -390,9 +419,17 @@ module DataMapper
390
419
  #
391
420
  # @api private
392
421
  def update_statement(properties, query)
393
- conditions_statement, bind_values = conditions_statement(query.conditions)
422
+ model = query.model
423
+ name = self.name
394
424
 
395
- statement = "UPDATE #{quote_name(query.model.storage_name(name))}"
425
+ # TODO: DRY this up with delete_statement
426
+ conditions_statement, bind_values = if query.limit || query.links.any?
427
+ subquery(query, model.key(name), false)
428
+ else
429
+ conditions_statement(query.conditions)
430
+ end
431
+
432
+ statement = "UPDATE #{quote_name(model.storage_name(name))}"
396
433
  statement << " SET #{properties.map { |property| "#{quote_name(property.field)} = ?" }.join(', ')}"
397
434
  statement << " WHERE #{conditions_statement}" unless conditions_statement.blank?
398
435
 
@@ -405,9 +442,17 @@ module DataMapper
405
442
  #
406
443
  # @api private
407
444
  def delete_statement(query)
408
- conditions_statement, bind_values = conditions_statement(query.conditions)
445
+ model = query.model
446
+ name = self.name
409
447
 
410
- statement = "DELETE FROM #{quote_name(query.model.storage_name(name))}"
448
+ # TODO: DRY this up with update_statement
449
+ conditions_statement, bind_values = if query.limit || query.links.any?
450
+ subquery(query, model.key(name), false)
451
+ else
452
+ conditions_statement(query.conditions)
453
+ end
454
+
455
+ statement = "DELETE FROM #{quote_name(model.storage_name(name))}"
411
456
  statement << " WHERE #{conditions_statement}" unless conditions_statement.blank?
412
457
 
413
458
  return statement, bind_values
@@ -419,8 +464,8 @@ module DataMapper
419
464
  # list of fields as a string
420
465
  #
421
466
  # @api private
422
- def columns_statement(properties, qualify)
423
- properties.map { |property| property_to_column_name(property, qualify) }.join(', ')
467
+ def columns_statement(properties, qualify, qualifier = nil)
468
+ properties.map { |property| property_to_column_name(property, qualify, qualifier) }.join(', ')
424
469
  end
425
470
 
426
471
  # Constructs joins clause
@@ -450,36 +495,90 @@ module DataMapper
450
495
  # @api private
451
496
  def conditions_statement(conditions, qualify = false)
452
497
  case conditions
453
- when Query::Conditions::NotOperation
454
- negate_operation(conditions, qualify)
498
+ when Query::Conditions::NotOperation then negate_operation(conditions.operand, qualify)
499
+ when Query::Conditions::AbstractOperation then operation_statement(conditions, qualify)
500
+ when Query::Conditions::AbstractComparison then comparison_statement(conditions, qualify)
501
+ when Array
502
+ statement, bind_values = conditions # handle raw conditions
503
+ [ "(#{statement})", bind_values ].compact
504
+ end
505
+ end
455
506
 
456
- when Query::Conditions::AbstractOperation
457
- # TODO: remove this once conditions can be compressed
458
- if conditions.operands.size == 1
459
- # factor out operations with a single operand
460
- conditions_statement(conditions.operands.first, qualify)
461
- else
462
- operation_statement(conditions, qualify)
463
- end
507
+ # @api private
508
+ def supports_subquery?(*)
509
+ true
510
+ end
464
511
 
465
- when Query::Conditions::AbstractComparison
466
- comparison_statement(conditions, qualify)
512
+ # @api private
513
+ def subquery(query, subject, qualify)
514
+ source_key, target_key = subquery_keys(subject)
467
515
 
468
- when Array
469
- statement, bind_values = conditions # handle raw conditions
470
- [ "(#{statement})", bind_values ]
516
+ if query.repository.name == name && supports_subquery?(query, source_key, target_key, qualify)
517
+ subquery_statement(query, source_key, target_key, qualify)
518
+ else
519
+ subquery_execute(query, source_key, target_key, qualify)
520
+ end
521
+ end
522
+
523
+ # @api private
524
+ def subquery_statement(query, source_key, target_key, qualify)
525
+ query = subquery_query(query, source_key)
526
+ select_statement, bind_values = select_statement(query)
527
+
528
+ statement = if target_key.size == 1
529
+ property_to_column_name(target_key.first, qualify)
530
+ else
531
+ "(#{target_key.map { |property| property_to_column_name(property, qualify) }.join(', ')})"
532
+ end
533
+
534
+ statement << " IN (#{select_statement})"
535
+
536
+ return statement, bind_values
537
+ end
538
+
539
+ # @api private
540
+ def subquery_execute(query, source_key, target_key, qualify)
541
+ query = subquery_query(query, source_key)
542
+ sources = query.model.all(query)
543
+ conditions = Query.target_conditions(sources, source_key, target_key)
544
+
545
+ if conditions.valid?
546
+ conditions_statement(conditions, qualify)
547
+ else
548
+ [ '1 = 0', [] ]
549
+ end
550
+ end
551
+
552
+ # @api private
553
+ def subquery_keys(subject)
554
+ case subject
555
+ when Associations::Relationship
556
+ relationship = subject.inverse
557
+ [ relationship.source_key, relationship.target_key ]
558
+ when PropertySet
559
+ [ subject, subject ]
471
560
  end
472
561
  end
473
562
 
563
+ # @api private
564
+ def subquery_query(query, source_key)
565
+ # force unique to be false because PostgreSQL has a problem with
566
+ # subselects that contain a GROUP BY with different columns
567
+ # than the outer-most query
568
+ query = query.merge(:fields => source_key, :unique => false)
569
+ query.update(:order => nil) unless query.limit
570
+ query
571
+ end
572
+
474
573
  # Constructs order clause
475
574
  #
476
575
  # @return [String]
477
576
  # order clause
478
577
  #
479
578
  # @api private
480
- def order_statement(order, qualify)
579
+ def order_statement(order, qualify, qualifier = nil)
481
580
  statements = order.map do |direction|
482
- statement = property_to_column_name(direction.target, qualify)
581
+ statement = property_to_column_name(direction.target, qualify, qualifier)
483
582
  statement << ' DESC' if direction.operator == :desc
484
583
  statement
485
584
  end
@@ -487,18 +586,13 @@ module DataMapper
487
586
  statements.join(', ')
488
587
  end
489
588
 
490
- # TODO: document
491
589
  # @api private
492
- def negate_operation(operation, qualify)
493
- @negated = !@negated
494
- begin
495
- conditions_statement(operation.operands.first, qualify)
496
- ensure
497
- @negated = !@negated
498
- end
590
+ def negate_operation(operand, qualify)
591
+ statement, bind_values = conditions_statement(operand, qualify)
592
+ statement = "NOT(#{statement})" unless statement.nil?
593
+ [ statement, bind_values ]
499
594
  end
500
595
 
501
- # TODO: document
502
596
  # @api private
503
597
  def operation_statement(operation, qualify)
504
598
  statements = []
@@ -506,17 +600,16 @@ module DataMapper
506
600
 
507
601
  operation.each do |operand|
508
602
  statement, values = conditions_statement(operand, qualify)
509
-
510
- if operand.respond_to?(:operands) && operand.operands.size > 1
511
- statement = "(#{statement})"
512
- end
513
-
603
+ next unless statement && values
514
604
  statements << statement
515
605
  bind_values.concat(values)
516
606
  end
517
607
 
518
- join_with = operation.kind_of?(@negated ? Query::Conditions::OrOperation : Query::Conditions::AndOperation) ? 'AND' : 'OR'
519
- statement = statements.join(" #{join_with} ")
608
+ statement = statements.join(" #{operation.slug.to_s.upcase} ")
609
+
610
+ if statements.size > 1
611
+ statement = "(#{statement})"
612
+ end
520
613
 
521
614
  return statement, bind_values
522
615
  end
@@ -528,7 +621,8 @@ module DataMapper
528
621
  #
529
622
  # @api private
530
623
  def comparison_statement(comparison, qualify)
531
- value = comparison.value
624
+ subject = comparison.subject
625
+ value = comparison.value
532
626
 
533
627
  # TODO: move exclusive Range handling into another method, and
534
628
  # update conditions_statement to use it
@@ -536,50 +630,56 @@ module DataMapper
536
630
  # break exclusive Range queries up into two comparisons ANDed together
537
631
  if value.kind_of?(Range) && value.exclude_end?
538
632
  operation = Query::Conditions::Operation.new(:and,
539
- Query::Conditions::Comparison.new(:gte, comparison.subject, value.first),
540
- Query::Conditions::Comparison.new(:lt, comparison.subject, value.last)
633
+ Query::Conditions::Comparison.new(:gte, subject, value.first),
634
+ Query::Conditions::Comparison.new(:lt, subject, value.last)
541
635
  )
542
636
 
543
637
  statement, bind_values = conditions_statement(operation, qualify)
544
638
 
545
639
  return "(#{statement})", bind_values
546
640
  elsif comparison.relationship?
547
- return conditions_statement(comparison.foreign_key_mapping, qualify)
641
+ if value.respond_to?(:query) && value.respond_to?(:loaded?) && !value.loaded?
642
+ return subquery(value.query, subject, qualify)
643
+ else
644
+ return conditions_statement(comparison.foreign_key_mapping, qualify)
645
+ end
646
+ elsif comparison.slug == :in && value.empty?
647
+ return [] # match everything
548
648
  end
549
649
 
550
- operator = case comparison
551
- when Query::Conditions::EqualToComparison then @negated ? inequality_operator(comparison.subject, value) : equality_operator(comparison.subject, value)
552
- when Query::Conditions::InclusionComparison then @negated ? exclude_operator(comparison.subject, value) : include_operator(comparison.subject, value)
553
- when Query::Conditions::RegexpComparison then @negated ? not_regexp_operator(value) : regexp_operator(value)
554
- when Query::Conditions::LikeComparison then @negated ? unlike_operator(value) : like_operator(value)
555
- when Query::Conditions::GreaterThanComparison then @negated ? '<=' : '>'
556
- when Query::Conditions::LessThanComparison then @negated ? '>=' : '<'
557
- when Query::Conditions::GreaterThanOrEqualToComparison then @negated ? '<' : '>='
558
- when Query::Conditions::LessThanOrEqualToComparison then @negated ? '>' : '<='
559
- end
650
+ operator = comparison_operator(comparison)
651
+ column_name = property_to_column_name(subject, qualify)
560
652
 
561
653
  # if operator return value contains ? then it means that it is function call
562
654
  # and it contains placeholder (%s) for property name as well (used in Oracle adapter for regexp operator)
563
655
  if operator.include?('?')
564
- return operator % property_to_column_name(comparison.subject, qualify), [ value ]
656
+ return operator % column_name, [ value ]
565
657
  else
566
- return "#{property_to_column_name(comparison.subject, qualify)} #{operator} #{value.nil? ? 'NULL' : '?'}", [ value ].compact
658
+ return "#{column_name} #{operator} #{value.nil? ? 'NULL' : '?'}", [ value ].compact
567
659
  end
568
660
  end
569
661
 
570
- # TODO: document
571
- # @api private
572
- def equality_operator(property, operand)
573
- operand.nil? ? 'IS' : '='
662
+ def comparison_operator(comparison)
663
+ subject = comparison.subject
664
+ value = comparison.value
665
+
666
+ case comparison.slug
667
+ when :eql then equality_operator(subject, value)
668
+ when :in then include_operator(subject, value)
669
+ when :regexp then regexp_operator(value)
670
+ when :like then like_operator(value)
671
+ when :gt then '>'
672
+ when :lt then '<'
673
+ when :gte then '>='
674
+ when :lte then '<='
675
+ end
574
676
  end
575
677
 
576
- # TODO: document
577
678
  # @api private
578
- def inequality_operator(property, operand)
579
- operand.nil? ? 'IS NOT' : '<>'
679
+ def equality_operator(property, operand)
680
+ operand.nil? ? 'IS' : '='
580
681
  end
581
682
 
582
- # TODO: document
583
683
  # @api private
584
684
  def include_operator(property, operand)
585
685
  case operand
@@ -588,37 +688,16 @@ module DataMapper
588
688
  end
589
689
  end
590
690
 
591
- # TODO: document
592
- # @api private
593
- def exclude_operator(property, operand)
594
- "NOT #{include_operator(property, operand)}"
595
- end
596
-
597
- # TODO: document
598
691
  # @api private
599
692
  def regexp_operator(operand)
600
693
  '~'
601
694
  end
602
695
 
603
- # TODO: document
604
- # @api private
605
- def not_regexp_operator(operand)
606
- '!~'
607
- end
608
-
609
- # TODO: document
610
696
  # @api private
611
697
  def like_operator(operand)
612
698
  'LIKE'
613
699
  end
614
700
 
615
- # TODO: document
616
- # @api private
617
- def unlike_operator(operand)
618
- 'NOT LIKE'
619
- end
620
-
621
- # TODO: document
622
701
  # @api private
623
702
  def quote_name(name)
624
703
  "\"#{name[0, self.class::IDENTIFIER_MAX_LENGTH].gsub('"', '""')}\""