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.
- data/.autotest +29 -0
- data/.document +5 -0
- data/.gitignore +27 -0
- data/LICENSE +20 -0
- data/{README.txt → README.rdoc} +14 -3
- data/Rakefile +23 -22
- data/VERSION +1 -0
- data/dm-core.gemspec +201 -10
- data/lib/dm-core.rb +32 -23
- data/lib/dm-core/adapters.rb +0 -1
- data/lib/dm-core/adapters/data_objects_adapter.rb +230 -151
- data/lib/dm-core/adapters/mysql_adapter.rb +7 -8
- data/lib/dm-core/adapters/oracle_adapter.rb +39 -59
- data/lib/dm-core/adapters/postgres_adapter.rb +0 -1
- data/lib/dm-core/adapters/sqlite3_adapter.rb +5 -0
- data/lib/dm-core/adapters/sqlserver_adapter.rb +114 -0
- data/lib/dm-core/adapters/yaml_adapter.rb +0 -5
- data/lib/dm-core/associations/many_to_many.rb +118 -56
- data/lib/dm-core/associations/many_to_one.rb +48 -21
- data/lib/dm-core/associations/one_to_many.rb +8 -30
- data/lib/dm-core/associations/one_to_one.rb +1 -5
- data/lib/dm-core/associations/relationship.rb +89 -97
- data/lib/dm-core/collection.rb +299 -184
- data/lib/dm-core/core_ext/enumerable.rb +28 -0
- data/lib/dm-core/core_ext/kernel.rb +0 -2
- data/lib/dm-core/migrations.rb +314 -170
- data/lib/dm-core/model.rb +97 -66
- data/lib/dm-core/model/descendant_set.rb +1 -1
- data/lib/dm-core/model/hook.rb +0 -3
- data/lib/dm-core/model/property.rb +7 -10
- data/lib/dm-core/model/relationship.rb +79 -26
- data/lib/dm-core/model/scope.rb +3 -4
- data/lib/dm-core/property.rb +152 -90
- data/lib/dm-core/property_set.rb +18 -37
- data/lib/dm-core/query.rb +452 -153
- data/lib/dm-core/query/conditions/comparison.rb +266 -173
- data/lib/dm-core/query/conditions/operation.rb +499 -57
- data/lib/dm-core/query/direction.rb +0 -3
- data/lib/dm-core/query/operator.rb +0 -4
- data/lib/dm-core/query/path.rb +10 -12
- data/lib/dm-core/query/sort.rb +4 -10
- data/lib/dm-core/repository.rb +10 -6
- data/lib/dm-core/resource.rb +343 -148
- data/lib/dm-core/spec/adapter_shared_spec.rb +17 -1
- data/lib/dm-core/spec/data_objects_adapter_shared_spec.rb +277 -17
- data/lib/dm-core/support/chainable.rb +0 -2
- data/lib/dm-core/support/equalizer.rb +27 -3
- data/lib/dm-core/transaction.rb +75 -75
- data/lib/dm-core/type.rb +19 -5
- data/lib/dm-core/types/discriminator.rb +4 -4
- data/lib/dm-core/types/object.rb +2 -7
- data/lib/dm-core/types/paranoid_boolean.rb +8 -2
- data/lib/dm-core/types/paranoid_datetime.rb +8 -2
- data/lib/dm-core/version.rb +1 -1
- data/script/performance.rb +7 -7
- data/script/profile.rb +6 -6
- data/spec/lib/collection_helpers.rb +2 -2
- data/spec/lib/pending_helpers.rb +22 -3
- data/spec/lib/rspec_immediate_feedback_formatter.rb +1 -0
- data/spec/public/associations/many_to_many_spec.rb +6 -4
- data/spec/public/associations/many_to_one_spec.rb +10 -1
- data/spec/public/associations/many_to_one_with_boolean_cpk_spec.rb +39 -0
- data/spec/public/associations/one_to_many_spec.rb +4 -3
- data/spec/public/associations/one_to_one_spec.rb +19 -1
- data/spec/public/associations/one_to_one_with_boolean_cpk_spec.rb +45 -0
- data/spec/public/collection_spec.rb +4 -3
- data/spec/public/migrations_spec.rb +144 -0
- data/spec/public/model/relationship_spec.rb +115 -55
- data/spec/public/model_spec.rb +13 -13
- data/spec/public/property/object_spec.rb +106 -0
- data/spec/public/property_spec.rb +18 -14
- data/spec/public/resource_spec.rb +10 -1
- data/spec/public/sel_spec.rb +16 -49
- data/spec/public/setup_spec.rb +1 -1
- data/spec/public/shared/association_collection_shared_spec.rb +6 -14
- data/spec/public/shared/collection_finder_shared_spec.rb +267 -0
- data/spec/public/shared/collection_shared_spec.rb +214 -217
- data/spec/public/shared/finder_shared_spec.rb +259 -365
- data/spec/public/shared/resource_shared_spec.rb +524 -248
- data/spec/public/transaction_spec.rb +27 -3
- data/spec/public/types/discriminator_spec.rb +1 -1
- data/spec/rcov.opts +6 -0
- data/spec/semipublic/adapters/sqlserver_adapter_spec.rb +17 -0
- data/spec/semipublic/associations/many_to_one_spec.rb +3 -20
- data/spec/semipublic/associations_spec.rb +2 -2
- data/spec/semipublic/collection_spec.rb +0 -32
- data/spec/semipublic/model_spec.rb +96 -0
- data/spec/semipublic/property_spec.rb +3 -3
- data/spec/semipublic/query/conditions/comparison_spec.rb +1719 -0
- data/spec/semipublic/query/conditions/operation_spec.rb +1292 -0
- data/spec/semipublic/query_spec.rb +1285 -144
- data/spec/semipublic/resource_spec.rb +0 -24
- data/spec/semipublic/shared/resource_shared_spec.rb +103 -38
- data/spec/spec.opts +1 -1
- data/spec/spec_helper.rb +15 -6
- data/tasks/ci.rake +1 -0
- data/tasks/metrics.rake +37 -0
- data/tasks/spec.rake +41 -0
- data/tasks/yard.rake +9 -0
- data/tasks/yardstick.rake +19 -0
- metadata +99 -29
- data/CONTRIBUTING +0 -51
- data/FAQ +0 -93
- data/History.txt +0 -27
- data/MIT-LICENSE +0 -22
- data/Manifest.txt +0 -121
- data/QUICKLINKS +0 -11
- data/SPECS +0 -35
- data/TODO +0 -1
- data/spec/semipublic/query/conditions_spec.rb +0 -528
- data/tasks/ci.rb +0 -24
- data/tasks/dm.rb +0 -58
- data/tasks/doc.rb +0 -17
- data/tasks/gemspec.rb +0 -23
- data/tasks/hoe.rb +0 -45
- data/tasks/install.rb +0 -18
data/lib/dm-core/property_set.rb
CHANGED
@@ -10,75 +10,67 @@ module DataMapper
|
|
10
10
|
deprecate :slice, :values_at
|
11
11
|
deprecate :add, :<<
|
12
12
|
|
13
|
-
# TODO: document
|
14
13
|
# @api semipublic
|
15
14
|
def [](name)
|
16
15
|
@properties[name]
|
17
16
|
end
|
18
17
|
|
19
|
-
alias
|
18
|
+
alias superclass_slice []=
|
19
|
+
private :superclass_slice
|
20
20
|
|
21
|
-
# TODO: document
|
22
21
|
# @api semipublic
|
23
22
|
def []=(name, property)
|
24
|
-
|
25
|
-
add_property(property)
|
26
|
-
super_slice(index(property), property)
|
27
|
-
else
|
28
|
-
self << property
|
29
|
-
end
|
23
|
+
self << property
|
30
24
|
end
|
31
25
|
|
32
|
-
# TODO: document
|
33
26
|
# @api semipublic
|
34
27
|
def named?(name)
|
35
28
|
@properties.key?(name)
|
36
29
|
end
|
37
30
|
|
38
|
-
# TODO: document
|
39
31
|
# @api semipublic
|
40
32
|
def values_at(*names)
|
41
33
|
@properties.values_at(*names)
|
42
34
|
end
|
43
35
|
|
44
|
-
# TODO: document
|
45
36
|
# @api semipublic
|
46
37
|
def <<(property)
|
47
|
-
|
48
|
-
|
49
|
-
|
38
|
+
found = named?(property.name)
|
39
|
+
add_property(property)
|
40
|
+
|
41
|
+
if found
|
42
|
+
superclass_slice(index(property), property)
|
50
43
|
else
|
51
|
-
add_property(property)
|
52
44
|
super
|
53
45
|
end
|
54
46
|
end
|
55
47
|
|
56
|
-
# TODO: document
|
57
48
|
# @api semipublic
|
58
49
|
def include?(property)
|
59
50
|
named?(property.name)
|
60
51
|
end
|
61
52
|
|
53
|
+
# @api semipublic
|
54
|
+
def index(property)
|
55
|
+
each_index { |index| break index if at(index).name == property.name }
|
56
|
+
end
|
57
|
+
|
62
58
|
# TODO: make PropertySet#reject return a PropertySet instance
|
63
|
-
# TODO: document
|
64
59
|
# @api semipublic
|
65
60
|
def defaults
|
66
61
|
@defaults ||= self.class.new(key | [ discriminator ].compact | reject { |property| property.lazy? }).freeze
|
67
62
|
end
|
68
63
|
|
69
|
-
# TODO: document
|
70
64
|
# @api semipublic
|
71
65
|
def key
|
72
66
|
@key ||= self.class.new(select { |property| property.key? }).freeze
|
73
67
|
end
|
74
68
|
|
75
|
-
# TODO: document
|
76
69
|
# @api semipublic
|
77
70
|
def discriminator
|
78
71
|
@discriminator ||= detect { |property| property.type == Types::Discriminator }
|
79
72
|
end
|
80
73
|
|
81
|
-
# TODO: document
|
82
74
|
# @api semipublic
|
83
75
|
def indexes
|
84
76
|
index_hash = {}
|
@@ -86,7 +78,6 @@ module DataMapper
|
|
86
78
|
index_hash
|
87
79
|
end
|
88
80
|
|
89
|
-
# TODO: document
|
90
81
|
# @api semipublic
|
91
82
|
def unique_indexes
|
92
83
|
index_hash = {}
|
@@ -94,43 +85,41 @@ module DataMapper
|
|
94
85
|
index_hash
|
95
86
|
end
|
96
87
|
|
97
|
-
# TODO: document
|
98
88
|
# @api semipublic
|
99
89
|
def get(resource)
|
100
90
|
map { |property| property.get(resource) }
|
101
91
|
end
|
102
92
|
|
103
|
-
# TODO: document
|
104
93
|
# @api semipublic
|
105
94
|
def get!(resource)
|
106
95
|
map { |property| property.get!(resource) }
|
107
96
|
end
|
108
97
|
|
109
|
-
# TODO: document
|
110
98
|
# @api semipublic
|
111
99
|
def set(resource, values)
|
112
100
|
zip(values) { |property, value| property.set(resource, value) }
|
113
101
|
end
|
114
102
|
|
115
|
-
# TODO: document
|
116
103
|
# @api semipublic
|
117
104
|
def set!(resource, values)
|
118
105
|
zip(values) { |property, value| property.set!(resource, value) }
|
119
106
|
end
|
120
107
|
|
121
|
-
# TODO: document
|
122
108
|
# @api semipublic
|
123
109
|
def loaded?(resource)
|
124
110
|
all? { |property| property.loaded?(resource) }
|
125
111
|
end
|
126
112
|
|
127
|
-
#
|
113
|
+
# @api semipublic
|
114
|
+
def valid?(values)
|
115
|
+
zip(values.nil? ? [] : values).all? { |property, value| property.valid?(value) }
|
116
|
+
end
|
117
|
+
|
128
118
|
# @api semipublic
|
129
119
|
def typecast(values)
|
130
120
|
zip(values.nil? ? [] : values).map { |property, value| property.typecast(value) }
|
131
121
|
end
|
132
122
|
|
133
|
-
# TODO: document
|
134
123
|
# @api private
|
135
124
|
def property_contexts(property)
|
136
125
|
contexts = []
|
@@ -140,13 +129,11 @@ module DataMapper
|
|
140
129
|
contexts
|
141
130
|
end
|
142
131
|
|
143
|
-
# TODO: document
|
144
132
|
# @api private
|
145
133
|
def lazy_context(context)
|
146
134
|
lazy_contexts[context] ||= []
|
147
135
|
end
|
148
136
|
|
149
|
-
# TODO: document
|
150
137
|
# @api private
|
151
138
|
def in_context(properties)
|
152
139
|
properties_in_context = properties.map do |property|
|
@@ -162,40 +149,34 @@ module DataMapper
|
|
162
149
|
|
163
150
|
private
|
164
151
|
|
165
|
-
# TODO: document
|
166
152
|
# @api semipublic
|
167
153
|
def initialize(*)
|
168
154
|
super
|
169
155
|
@properties = map { |property| [ property.name, property ] }.to_mash
|
170
156
|
end
|
171
157
|
|
172
|
-
# TODO: document
|
173
158
|
# @api private
|
174
159
|
def initialize_copy(*)
|
175
160
|
super
|
176
161
|
@properties = @properties.dup
|
177
162
|
end
|
178
163
|
|
179
|
-
# TODO: document
|
180
164
|
# @api private
|
181
165
|
def add_property(property)
|
182
166
|
clear_cache
|
183
167
|
@properties[property.name] = property
|
184
168
|
end
|
185
169
|
|
186
|
-
# TODO: document
|
187
170
|
# @api private
|
188
171
|
def clear_cache
|
189
172
|
@defaults, @key, @discriminator = nil
|
190
173
|
end
|
191
174
|
|
192
|
-
# TODO: document
|
193
175
|
# @api private
|
194
176
|
def lazy_contexts
|
195
177
|
@lazy_contexts ||= {}
|
196
178
|
end
|
197
179
|
|
198
|
-
# TODO: document
|
199
180
|
# @api private
|
200
181
|
def parse_index(index, property, index_hash)
|
201
182
|
case index
|
data/lib/dm-core/query.rb
CHANGED
@@ -47,20 +47,23 @@ module DataMapper
|
|
47
47
|
#
|
48
48
|
# @api private
|
49
49
|
def self.target_conditions(source, source_key, target_key)
|
50
|
-
|
50
|
+
target_key_size = target_key.size
|
51
|
+
source_values = []
|
51
52
|
|
52
53
|
if source.nil?
|
53
|
-
source_values << [ nil ] *
|
54
|
+
source_values << [ nil ] * target_key_size
|
54
55
|
else
|
55
56
|
Array(source).each do |resource|
|
56
57
|
next unless source_key.loaded?(resource)
|
57
|
-
|
58
|
+
source_value = source_key.get!(resource)
|
59
|
+
next unless target_key.valid?(source_value)
|
60
|
+
source_values << source_value
|
58
61
|
end
|
59
62
|
end
|
60
63
|
|
61
64
|
source_values.uniq!
|
62
65
|
|
63
|
-
if
|
66
|
+
if target_key_size == 1
|
64
67
|
target_key = target_key.first
|
65
68
|
source_values.flatten!
|
66
69
|
|
@@ -86,6 +89,29 @@ module DataMapper
|
|
86
89
|
end
|
87
90
|
end
|
88
91
|
|
92
|
+
# @param [Repository] repository
|
93
|
+
# the default repository to scope the query within
|
94
|
+
# @param [Model] model
|
95
|
+
# the default model for the query
|
96
|
+
# @param [#query, Enumerable] source
|
97
|
+
# the source to generate the query with
|
98
|
+
#
|
99
|
+
# @return [Query]
|
100
|
+
# the query to match the resources with
|
101
|
+
#
|
102
|
+
# @api private
|
103
|
+
def self.target_query(repository, model, source)
|
104
|
+
if source.respond_to?(:query)
|
105
|
+
source.query
|
106
|
+
elsif source.kind_of?(Enumerable)
|
107
|
+
key = model.key(repository.name)
|
108
|
+
conditions = Query.target_conditions(source, key, key)
|
109
|
+
Query.new(repository, model, :conditions => conditions)
|
110
|
+
else
|
111
|
+
raise ArgumentError, "+source+ must respond to #query or be an Enumerable, but was #{source.class}"
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
89
115
|
# Returns the repository query should be
|
90
116
|
# executed in
|
91
117
|
#
|
@@ -173,7 +199,7 @@ module DataMapper
|
|
173
199
|
#
|
174
200
|
# Document.all(:limit => 10)
|
175
201
|
#
|
176
|
-
# @return [Integer,
|
202
|
+
# @return [Integer, nil]
|
177
203
|
# the maximum number of results
|
178
204
|
#
|
179
205
|
# @api semipublic
|
@@ -325,24 +351,26 @@ module DataMapper
|
|
325
351
|
def update(other)
|
326
352
|
assert_kind_of 'other', other, self.class, Hash
|
327
353
|
|
328
|
-
other_options = if
|
329
|
-
if self.eql?(other)
|
330
|
-
return self
|
331
|
-
end
|
354
|
+
other_options = if kind_of?(other.class)
|
355
|
+
return self if self.eql?(other)
|
332
356
|
assert_valid_other(other)
|
333
357
|
other.options
|
334
358
|
else
|
359
|
+
return self if other.empty?
|
335
360
|
other
|
336
361
|
end
|
337
362
|
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
363
|
+
@options = @options.merge(other_options).freeze
|
364
|
+
assert_valid_options(@options)
|
365
|
+
|
366
|
+
normalize = other_options.only(*OPTIONS - [ :conditions ]).map do |attribute, value|
|
367
|
+
instance_variable_set("@#{attribute}", value.try_dup)
|
368
|
+
attribute
|
344
369
|
end
|
345
370
|
|
371
|
+
merge_conditions([ other_options.except(*OPTIONS), other_options[:conditions] ])
|
372
|
+
normalize_options(normalize | [ :links, :unique ])
|
373
|
+
|
346
374
|
self
|
347
375
|
end
|
348
376
|
|
@@ -375,22 +403,76 @@ module DataMapper
|
|
375
403
|
def relative(options)
|
376
404
|
assert_kind_of 'options', options, Hash
|
377
405
|
|
378
|
-
|
379
|
-
|
380
|
-
repository = options.delete(:repository) || self.repository
|
406
|
+
offset = nil
|
407
|
+
limit = self.limit
|
381
408
|
|
382
|
-
if
|
383
|
-
|
409
|
+
if options.key?(:offset) && (options.key?(:limit) || limit)
|
410
|
+
options = options.dup
|
411
|
+
offset = options.delete(:offset)
|
412
|
+
limit = options.delete(:limit) || limit - offset
|
384
413
|
end
|
385
414
|
|
386
|
-
|
387
|
-
|
388
|
-
|
415
|
+
query = merge(options)
|
416
|
+
query = query.slice!(offset, limit) if offset
|
417
|
+
query
|
418
|
+
end
|
389
419
|
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
420
|
+
# Return the union with another query
|
421
|
+
#
|
422
|
+
# @param [Query] other
|
423
|
+
# the other query
|
424
|
+
#
|
425
|
+
# @return [Query]
|
426
|
+
# the union of the query and other
|
427
|
+
#
|
428
|
+
# @api semipublic
|
429
|
+
def union(other)
|
430
|
+
return dup if self == other
|
431
|
+
set_operation(:union, other)
|
432
|
+
end
|
433
|
+
|
434
|
+
alias | union
|
435
|
+
alias + union
|
436
|
+
|
437
|
+
# Return the intersection with another query
|
438
|
+
#
|
439
|
+
# @param [Query] other
|
440
|
+
# the other query
|
441
|
+
#
|
442
|
+
# @return [Query]
|
443
|
+
# the intersection of the query and other
|
444
|
+
#
|
445
|
+
# @api semipublic
|
446
|
+
def intersection(other)
|
447
|
+
return dup if self == other
|
448
|
+
set_operation(:intersection, other)
|
449
|
+
end
|
450
|
+
|
451
|
+
alias & intersection
|
452
|
+
|
453
|
+
# Return the difference with another query
|
454
|
+
#
|
455
|
+
# @param [Query] other
|
456
|
+
# the other query
|
457
|
+
#
|
458
|
+
# @return [Query]
|
459
|
+
# the difference of the query and other
|
460
|
+
#
|
461
|
+
# @api semipublic
|
462
|
+
def difference(other)
|
463
|
+
set_operation(:difference, other)
|
464
|
+
end
|
465
|
+
|
466
|
+
alias - difference
|
467
|
+
|
468
|
+
# Clear conditions
|
469
|
+
#
|
470
|
+
# @return [self]
|
471
|
+
#
|
472
|
+
# @api semipublic
|
473
|
+
def clear
|
474
|
+
@conditions = Conditions::Operation.new(:null)
|
475
|
+
self
|
394
476
|
end
|
395
477
|
|
396
478
|
# Takes an Enumerable of records, and destructively filters it.
|
@@ -422,10 +504,9 @@ module DataMapper
|
|
422
504
|
#
|
423
505
|
# @api semipublic
|
424
506
|
def match_records(records)
|
507
|
+
conditions = self.conditions
|
425
508
|
return records if conditions.nil?
|
426
|
-
records.select
|
427
|
-
conditions.matches?(record)
|
428
|
-
end
|
509
|
+
records.select { |record| conditions.matches?(record) }
|
429
510
|
end
|
430
511
|
|
431
512
|
# Sorts a list of Records by the order
|
@@ -457,7 +538,9 @@ module DataMapper
|
|
457
538
|
#
|
458
539
|
# @api semipublic
|
459
540
|
def limit_records(records)
|
460
|
-
|
541
|
+
offset = self.offset
|
542
|
+
limit = self.limit
|
543
|
+
size = records.size
|
461
544
|
|
462
545
|
if offset > size - 1
|
463
546
|
[]
|
@@ -547,7 +630,9 @@ module DataMapper
|
|
547
630
|
properties = Set.new
|
548
631
|
|
549
632
|
each_comparison do |comparison|
|
550
|
-
|
633
|
+
next unless comparison.respond_to?(:subject)
|
634
|
+
subject = comparison.subject
|
635
|
+
properties << subject if subject.kind_of?(Property)
|
551
636
|
end
|
552
637
|
|
553
638
|
properties
|
@@ -563,6 +648,52 @@ module DataMapper
|
|
563
648
|
fields.sort_by { |property| property.hash }
|
564
649
|
end
|
565
650
|
|
651
|
+
# Transform Query into subquery conditions
|
652
|
+
#
|
653
|
+
# @return [AndOperation]
|
654
|
+
# a subquery for the Query
|
655
|
+
#
|
656
|
+
# @api private
|
657
|
+
def to_subquery
|
658
|
+
collection = model.all(merge(:fields => model_key))
|
659
|
+
Conditions::Operation.new(:and, Conditions::Comparison.new(:in, self_relationship, collection))
|
660
|
+
end
|
661
|
+
|
662
|
+
# Hash representation of a Query
|
663
|
+
#
|
664
|
+
# @return [Hash]
|
665
|
+
# Hash representation of a Query
|
666
|
+
#
|
667
|
+
# @api private
|
668
|
+
def to_hash
|
669
|
+
{
|
670
|
+
:repository => repository.name,
|
671
|
+
:model => model.name,
|
672
|
+
:fields => fields,
|
673
|
+
:links => links,
|
674
|
+
:conditions => conditions,
|
675
|
+
:offset => offset,
|
676
|
+
:limit => limit,
|
677
|
+
:order => order,
|
678
|
+
:unique => unique?,
|
679
|
+
:add_reversed => add_reversed?,
|
680
|
+
:reload => reload?,
|
681
|
+
}
|
682
|
+
end
|
683
|
+
|
684
|
+
# Extract options from a Query
|
685
|
+
#
|
686
|
+
# @param [Query] query
|
687
|
+
# the query to extract options from
|
688
|
+
#
|
689
|
+
# @return [Hash]
|
690
|
+
# the options to use to initialize the new query
|
691
|
+
#
|
692
|
+
# @api private
|
693
|
+
def to_relative_hash
|
694
|
+
to_hash.only(:fields, :order, :unique, :add_reversed, :reload)
|
695
|
+
end
|
696
|
+
|
566
697
|
private
|
567
698
|
|
568
699
|
# Initializes a Query instance
|
@@ -598,7 +729,7 @@ module DataMapper
|
|
598
729
|
assert_valid_options(@options)
|
599
730
|
|
600
731
|
@fields = @options.fetch :fields, @properties.defaults
|
601
|
-
@links = @options.
|
732
|
+
@links = @options.key?(:links) ? @options[:links].dup : []
|
602
733
|
@conditions = Conditions::Operation.new(:null)
|
603
734
|
@offset = @options.fetch :offset, 0
|
604
735
|
@limit = @options.fetch :limit, nil
|
@@ -608,35 +739,18 @@ module DataMapper
|
|
608
739
|
@reload = @options.fetch :reload, false
|
609
740
|
@raw = false
|
610
741
|
|
611
|
-
@
|
612
|
-
|
613
|
-
# treat all non-options as conditions
|
614
|
-
@options.except(*OPTIONS).each { |kv| append_condition(*kv) }
|
615
|
-
|
616
|
-
# parse @options[:conditions] differently
|
617
|
-
case conditions = @options[:conditions]
|
618
|
-
when Conditions::AbstractOperation, Conditions::AbstractComparison
|
619
|
-
add_condition(conditions)
|
620
|
-
|
621
|
-
when Hash
|
622
|
-
conditions.each { |kv| append_condition(*kv) }
|
623
|
-
|
624
|
-
when Array
|
625
|
-
statement, *bind_values = *conditions
|
626
|
-
add_condition([ statement, bind_values ])
|
627
|
-
@raw = true
|
628
|
-
end
|
629
|
-
|
630
|
-
normalize_order
|
631
|
-
normalize_fields
|
632
|
-
normalize_links
|
742
|
+
merge_conditions([ @options.except(*OPTIONS), @options[:conditions] ])
|
743
|
+
normalize_options
|
633
744
|
end
|
634
745
|
|
635
746
|
# Copying contructor, called for Query#dup
|
636
747
|
#
|
637
748
|
# @api semipublic
|
638
|
-
def initialize_copy(
|
639
|
-
|
749
|
+
def initialize_copy(*)
|
750
|
+
@fields = @fields.dup
|
751
|
+
@links = @links.dup
|
752
|
+
@conditions = @conditions.dup
|
753
|
+
@order = @order.try_dup
|
640
754
|
end
|
641
755
|
|
642
756
|
# Validate the options
|
@@ -673,15 +787,15 @@ module DataMapper
|
|
673
787
|
def assert_valid_fields(fields, unique)
|
674
788
|
assert_kind_of 'options[:fields]', fields, Array
|
675
789
|
|
676
|
-
|
677
|
-
raise ArgumentError, '+options[:fields]+ should not be empty if +options[:unique]+ is false'
|
678
|
-
end
|
790
|
+
model = self.model
|
679
791
|
|
680
792
|
fields.each do |field|
|
793
|
+
inspect = field.inspect
|
794
|
+
|
681
795
|
case field
|
682
796
|
when Symbol, String
|
683
797
|
unless @properties.named?(field)
|
684
|
-
raise ArgumentError, "+options[:fields]+ entry #{
|
798
|
+
raise ArgumentError, "+options[:fields]+ entry #{inspect} does not map to a property in #{model}"
|
685
799
|
end
|
686
800
|
|
687
801
|
when Property
|
@@ -690,7 +804,7 @@ module DataMapper
|
|
690
804
|
end
|
691
805
|
|
692
806
|
else
|
693
|
-
raise ArgumentError, "+options[:fields]+ entry #{
|
807
|
+
raise ArgumentError, "+options[:fields]+ entry #{inspect} of an unsupported object #{field.class}"
|
694
808
|
end
|
695
809
|
end
|
696
810
|
end
|
@@ -707,10 +821,12 @@ module DataMapper
|
|
707
821
|
end
|
708
822
|
|
709
823
|
links.each do |link|
|
824
|
+
inspect = link.inspect
|
825
|
+
|
710
826
|
case link
|
711
827
|
when Symbol, String
|
712
828
|
unless @relationships.key?(link.to_sym)
|
713
|
-
raise ArgumentError, "+options[:links]+ entry #{
|
829
|
+
raise ArgumentError, "+options[:links]+ entry #{inspect} does not map to a relationship in #{model}"
|
714
830
|
end
|
715
831
|
|
716
832
|
when Associations::Relationship
|
@@ -720,7 +836,7 @@ module DataMapper
|
|
720
836
|
#end
|
721
837
|
|
722
838
|
else
|
723
|
-
raise ArgumentError, "+options[:links]+ entry #{
|
839
|
+
raise ArgumentError, "+options[:links]+ entry #{inspect} of an unsupported object #{link.class}"
|
724
840
|
end
|
725
841
|
end
|
726
842
|
end
|
@@ -735,23 +851,23 @@ module DataMapper
|
|
735
851
|
case conditions
|
736
852
|
when Hash
|
737
853
|
conditions.each do |subject, bind_value|
|
854
|
+
inspect = subject.inspect
|
855
|
+
|
738
856
|
case subject
|
739
857
|
when Symbol, String
|
740
858
|
unless subject.to_s.include?('.') || @properties.named?(subject) || @relationships.key?(subject)
|
741
|
-
raise ArgumentError, "condition #{
|
859
|
+
raise ArgumentError, "condition #{inspect} does not map to a property or relationship in #{model}"
|
742
860
|
end
|
743
861
|
|
744
862
|
when Operator
|
745
|
-
|
746
|
-
|
863
|
+
operator = subject.operator
|
864
|
+
|
865
|
+
unless (Conditions::Comparison.slugs | [ :not ]).include?(operator)
|
866
|
+
raise ArgumentError, "condition #{inspect} used an invalid operator #{operator}"
|
747
867
|
end
|
748
868
|
|
749
869
|
assert_valid_conditions(subject.target => bind_value)
|
750
870
|
|
751
|
-
if subject.operator == :not && bind_value.kind_of?(Array) && bind_value.empty?
|
752
|
-
raise ArgumentError, "Cannot use 'not' operator with a bind value that is an empty Array for #{subject.inspect}"
|
753
|
-
end
|
754
|
-
|
755
871
|
when Path
|
756
872
|
assert_valid_links(subject.relationships)
|
757
873
|
|
@@ -763,7 +879,7 @@ module DataMapper
|
|
763
879
|
#end
|
764
880
|
|
765
881
|
else
|
766
|
-
raise ArgumentError, "condition #{
|
882
|
+
raise ArgumentError, "condition #{inspect} of an unsupported object #{subject.class}"
|
767
883
|
end
|
768
884
|
end
|
769
885
|
|
@@ -772,7 +888,9 @@ module DataMapper
|
|
772
888
|
raise ArgumentError, '+options[:conditions]+ should not be empty'
|
773
889
|
end
|
774
890
|
|
775
|
-
|
891
|
+
first_condition = conditions.first
|
892
|
+
|
893
|
+
unless first_condition.kind_of?(String) && !first_condition.blank?
|
776
894
|
raise ArgumentError, '+options[:conditions]+ should have a statement for the first entry'
|
777
895
|
end
|
778
896
|
end
|
@@ -813,17 +931,20 @@ module DataMapper
|
|
813
931
|
def assert_valid_order(order, fields)
|
814
932
|
return if order.nil?
|
815
933
|
|
816
|
-
|
817
|
-
|
934
|
+
order = Array(order)
|
818
935
|
if order.empty? && fields && fields.any? { |property| !property.kind_of?(Operator) }
|
819
936
|
raise ArgumentError, '+options[:order]+ should not be empty if +options[:fields] contains a non-operator'
|
820
937
|
end
|
821
938
|
|
939
|
+
model = self.model
|
940
|
+
|
822
941
|
order.each do |order_entry|
|
942
|
+
inspect = order_entry.inspect
|
943
|
+
|
823
944
|
case order_entry
|
824
945
|
when Symbol, String
|
825
946
|
unless @properties.named?(order_entry)
|
826
|
-
raise ArgumentError, "+options[:order]+ entry #{
|
947
|
+
raise ArgumentError, "+options[:order]+ entry #{inspect} does not map to a property in #{model}"
|
827
948
|
end
|
828
949
|
|
829
950
|
when Property
|
@@ -832,14 +953,16 @@ module DataMapper
|
|
832
953
|
end
|
833
954
|
|
834
955
|
when Operator, Direction
|
835
|
-
|
836
|
-
|
956
|
+
operator = order_entry.operator
|
957
|
+
|
958
|
+
unless operator == :asc || operator == :desc
|
959
|
+
raise ArgumentError, "+options[:order]+ entry #{inspect} used an invalid operator #{operator}"
|
837
960
|
end
|
838
961
|
|
839
962
|
assert_valid_order([ order_entry.target ], fields)
|
840
963
|
|
841
964
|
else
|
842
|
-
raise ArgumentError, "+options[:order]+ entry #{
|
965
|
+
raise ArgumentError, "+options[:order]+ entry #{inspect} of an unsupported object #{order_entry.class}"
|
843
966
|
end
|
844
967
|
end
|
845
968
|
end
|
@@ -857,18 +980,67 @@ module DataMapper
|
|
857
980
|
#
|
858
981
|
# @api private
|
859
982
|
def assert_valid_other(other)
|
860
|
-
|
861
|
-
|
983
|
+
other_repository = other.repository
|
984
|
+
repository = self.repository
|
985
|
+
other_class = other.class
|
986
|
+
|
987
|
+
unless other_repository == repository
|
988
|
+
raise ArgumentError, "+other+ #{other_class} must be for the #{repository.name} repository, not #{other_repository.name}"
|
862
989
|
end
|
863
990
|
|
864
|
-
|
865
|
-
|
991
|
+
other_model = other.model
|
992
|
+
model = self.model
|
993
|
+
|
994
|
+
unless other_model >= model
|
995
|
+
raise ArgumentError, "+other+ #{other_class} must be for the #{model.name} model, not #{other_model.name}"
|
866
996
|
end
|
867
997
|
end
|
868
998
|
|
869
|
-
#
|
999
|
+
# Handle all the conditions options provided
|
870
1000
|
#
|
871
|
-
#
|
1001
|
+
# @param [Array<Conditions::AbstractOperation, Conditions::AbstractComparison, Hash, Array>]
|
1002
|
+
# a list of conditions
|
1003
|
+
#
|
1004
|
+
# @return [undefined]
|
1005
|
+
#
|
1006
|
+
# @api private
|
1007
|
+
def merge_conditions(conditions)
|
1008
|
+
@conditions = Conditions::Operation.new(:and) << @conditions unless @conditions.nil?
|
1009
|
+
|
1010
|
+
conditions.compact!
|
1011
|
+
conditions.each do |condition|
|
1012
|
+
case condition
|
1013
|
+
when Conditions::AbstractOperation, Conditions::AbstractComparison
|
1014
|
+
add_condition(condition)
|
1015
|
+
|
1016
|
+
when Hash
|
1017
|
+
condition.each { |kv| append_condition(*kv) }
|
1018
|
+
|
1019
|
+
when Array
|
1020
|
+
statement, *bind_values = *condition
|
1021
|
+
raw_condition = [ statement ]
|
1022
|
+
raw_condition << bind_values if bind_values.size > 0
|
1023
|
+
add_condition(raw_condition)
|
1024
|
+
@raw = true
|
1025
|
+
end
|
1026
|
+
end
|
1027
|
+
end
|
1028
|
+
|
1029
|
+
# Normalize options
|
1030
|
+
#
|
1031
|
+
# @param [Array<Symbol>] options
|
1032
|
+
# the options to normalize
|
1033
|
+
#
|
1034
|
+
# @return [undefined]
|
1035
|
+
#
|
1036
|
+
# @api private
|
1037
|
+
def normalize_options(options = OPTIONS)
|
1038
|
+
(options & [ :order, :fields, :links, :unique ]).each do |option|
|
1039
|
+
send("normalize_#{option}")
|
1040
|
+
end
|
1041
|
+
end
|
1042
|
+
|
1043
|
+
# Normalize order elements to Query::Direction instances
|
872
1044
|
#
|
873
1045
|
# @api private
|
874
1046
|
def normalize_order
|
@@ -876,6 +1048,7 @@ module DataMapper
|
|
876
1048
|
|
877
1049
|
# TODO: should Query::Path objects be permitted? If so, then it
|
878
1050
|
# should probably be normalized to a Direction object
|
1051
|
+
@order = Array(@order)
|
879
1052
|
@order = @order.map do |order|
|
880
1053
|
case order
|
881
1054
|
when Operator
|
@@ -898,8 +1071,6 @@ module DataMapper
|
|
898
1071
|
|
899
1072
|
# Normalize fields to Property instances
|
900
1073
|
#
|
901
|
-
# TODO: needs example
|
902
|
-
#
|
903
1074
|
# @api private
|
904
1075
|
def normalize_fields
|
905
1076
|
@fields = @fields.map do |field|
|
@@ -921,21 +1092,19 @@ module DataMapper
|
|
921
1092
|
#
|
922
1093
|
# @api private
|
923
1094
|
def normalize_links
|
924
|
-
|
1095
|
+
stack = @links.dup
|
925
1096
|
|
926
1097
|
@links.clear
|
927
1098
|
|
928
|
-
while link =
|
1099
|
+
while link = stack.pop
|
929
1100
|
relationship = case link
|
930
1101
|
when Symbol, String then @relationships[link]
|
931
1102
|
when Associations::Relationship then link
|
932
1103
|
end
|
933
1104
|
|
934
|
-
next if @links.include?(relationship)
|
935
|
-
|
936
1105
|
if relationship.respond_to?(:links)
|
937
|
-
|
938
|
-
|
1106
|
+
stack.concat(relationship.links)
|
1107
|
+
elsif !@links.include?(relationship)
|
939
1108
|
repository_name = relationship.relative_target_repository_name
|
940
1109
|
model = relationship.target_model
|
941
1110
|
|
@@ -961,9 +1130,20 @@ module DataMapper
|
|
961
1130
|
@links << relationship
|
962
1131
|
end
|
963
1132
|
end
|
1133
|
+
|
964
1134
|
@links.reverse!
|
965
1135
|
end
|
966
1136
|
|
1137
|
+
# Normalize the unique attribute
|
1138
|
+
#
|
1139
|
+
# If any links are present, and the unique attribute was not
|
1140
|
+
# explicitly specified, then make sure the query is marked as unique
|
1141
|
+
#
|
1142
|
+
# @api private
|
1143
|
+
def normalize_unique
|
1144
|
+
@unique = @links.any? unless @options.key?(:unique)
|
1145
|
+
end
|
1146
|
+
|
967
1147
|
# Append conditions to this Query
|
968
1148
|
#
|
969
1149
|
# TODO: needs example
|
@@ -991,21 +1171,21 @@ module DataMapper
|
|
991
1171
|
end
|
992
1172
|
end
|
993
1173
|
|
994
|
-
# TODO: document
|
995
1174
|
# @api private
|
996
|
-
def append_property_condition(
|
997
|
-
|
998
|
-
negated = operator == :not
|
1175
|
+
def append_property_condition(subject, bind_value, operator)
|
1176
|
+
negated = operator == :not
|
999
1177
|
|
1000
1178
|
if operator == :eql || negated
|
1001
|
-
|
1002
|
-
|
1003
|
-
|
1004
|
-
|
1179
|
+
# transform :relationship => nil into :relationship.not => association
|
1180
|
+
if subject.respond_to?(:collection_for) && bind_value.nil?
|
1181
|
+
negated = !negated
|
1182
|
+
bind_value = collection_for_nil(subject)
|
1005
1183
|
end
|
1184
|
+
|
1185
|
+
operator = equality_operator_for_type(bind_value)
|
1006
1186
|
end
|
1007
1187
|
|
1008
|
-
condition = Conditions::Comparison.new(operator,
|
1188
|
+
condition = Conditions::Comparison.new(operator, subject, bind_value)
|
1009
1189
|
|
1010
1190
|
if negated
|
1011
1191
|
condition = Conditions::Operation.new(:not, condition)
|
@@ -1014,20 +1194,39 @@ module DataMapper
|
|
1014
1194
|
add_condition(condition)
|
1015
1195
|
end
|
1016
1196
|
|
1017
|
-
|
1197
|
+
if RUBY_VERSION >= '1.9'
|
1198
|
+
def equality_operator_for_type(bind_value)
|
1199
|
+
case bind_value
|
1200
|
+
when Enumerable then :in
|
1201
|
+
when Regexp then :regexp
|
1202
|
+
else :eql
|
1203
|
+
end
|
1204
|
+
end
|
1205
|
+
else
|
1206
|
+
def equality_operator_for_type(bind_value)
|
1207
|
+
case bind_value
|
1208
|
+
when String then :eql
|
1209
|
+
when Enumerable then :in
|
1210
|
+
when Regexp then :regexp
|
1211
|
+
else :eql
|
1212
|
+
end
|
1213
|
+
end
|
1214
|
+
end
|
1215
|
+
|
1018
1216
|
# @api private
|
1019
1217
|
def append_symbol_condition(symbol, bind_value, model, operator)
|
1020
1218
|
append_condition(symbol.to_s, bind_value, model, operator)
|
1021
1219
|
end
|
1022
1220
|
|
1023
|
-
# TODO: document
|
1024
1221
|
# @api private
|
1025
1222
|
def append_string_condition(string, bind_value, model, operator)
|
1026
1223
|
if string.include?('.')
|
1027
1224
|
query_path = model
|
1028
1225
|
|
1029
1226
|
target_components = string.split('.')
|
1030
|
-
|
1227
|
+
last_component = target_components.last
|
1228
|
+
operator = target_components.pop.to_sym if DataMapper::Query::Conditions::Comparison.slugs.any? { |slug| slug.to_s == last_component }
|
1229
|
+
|
1031
1230
|
target_components.each { |method| query_path = query_path.send(method) }
|
1032
1231
|
|
1033
1232
|
append_condition(query_path, bind_value, model, operator)
|
@@ -1040,16 +1239,18 @@ module DataMapper
|
|
1040
1239
|
end
|
1041
1240
|
end
|
1042
1241
|
|
1043
|
-
# TODO: document
|
1044
1242
|
# @api private
|
1045
1243
|
def append_operator_conditions(operator, bind_value, model)
|
1046
1244
|
append_condition(operator.target, bind_value, model, operator.operator)
|
1047
1245
|
end
|
1048
1246
|
|
1049
|
-
# TODO: document
|
1050
1247
|
# @api private
|
1051
1248
|
def append_path(path, bind_value, model, operator)
|
1052
|
-
|
1249
|
+
path.relationships.each do |relationship|
|
1250
|
+
inverse = relationship.inverse
|
1251
|
+
@links.unshift(inverse) unless @links.include?(inverse)
|
1252
|
+
end
|
1253
|
+
|
1053
1254
|
append_condition(path.property, bind_value, path.model, operator)
|
1054
1255
|
end
|
1055
1256
|
|
@@ -1066,65 +1267,59 @@ module DataMapper
|
|
1066
1267
|
@conditions << condition
|
1067
1268
|
end
|
1068
1269
|
|
1069
|
-
# TODO: make this typecast all bind values that do not match the
|
1070
|
-
# property primitive
|
1071
|
-
|
1072
|
-
# TODO: document
|
1073
|
-
# @api private
|
1074
|
-
def normalize_bind_value(property_or_path, bind_value)
|
1075
|
-
# TODO: defer this inside the comparison
|
1076
|
-
if bind_value.respond_to?(:call)
|
1077
|
-
bind_value = bind_value.call
|
1078
|
-
end
|
1079
|
-
|
1080
|
-
# TODO: bypass this for Collection, once subqueries can be handled by adapters
|
1081
|
-
if bind_value.respond_to?(:to_ary)
|
1082
|
-
bind_value = bind_value.to_ary
|
1083
|
-
bind_value.uniq!
|
1084
|
-
end
|
1085
|
-
|
1086
|
-
# FIXME: causes m:m specs to fail with in-memory adapter
|
1087
|
-
# if bind_value.instance_of?(Array) && bind_value.size == 1
|
1088
|
-
# bind_value = bind_value.first
|
1089
|
-
# end
|
1090
|
-
|
1091
|
-
bind_value
|
1092
|
-
end
|
1093
|
-
|
1094
1270
|
# Extract arguments for #slice and #slice! then return offset and limit
|
1095
1271
|
#
|
1096
1272
|
# @param [Integer, Array(Integer), Range] *args the offset,
|
1097
1273
|
# offset and limit, or range indicating first and last position
|
1098
1274
|
#
|
1099
1275
|
# @return [Integer] the offset
|
1100
|
-
# @return [Integer,
|
1276
|
+
# @return [Integer, nil] the limit, if any
|
1101
1277
|
#
|
1102
1278
|
# @api private
|
1103
1279
|
def extract_slice_arguments(*args)
|
1104
|
-
|
1105
|
-
|
1106
|
-
|
1107
|
-
return first_arg, second_arg
|
1108
|
-
elsif args.size == 1
|
1109
|
-
if first_arg.kind_of?(Integer)
|
1110
|
-
return first_arg, 1
|
1111
|
-
elsif first_arg.kind_of?(Range)
|
1112
|
-
offset = first_arg.first
|
1113
|
-
limit = first_arg.last - offset
|
1114
|
-
limit += 1 unless first_arg.exclude_end?
|
1115
|
-
return offset, limit
|
1116
|
-
end
|
1280
|
+
offset, limit = case args.size
|
1281
|
+
when 2 then extract_offset_limit_from_two_arguments(*args)
|
1282
|
+
when 1 then extract_offset_limit_from_one_argument(*args)
|
1117
1283
|
end
|
1118
1284
|
|
1285
|
+
return offset, limit if offset && limit
|
1286
|
+
|
1119
1287
|
raise ArgumentError, "arguments may be 1 or 2 Integers, or 1 Range object, was: #{args.inspect}"
|
1120
1288
|
end
|
1121
1289
|
|
1122
|
-
#
|
1290
|
+
# @api private
|
1291
|
+
def extract_offset_limit_from_two_arguments(*args)
|
1292
|
+
args if args.all? { |arg| arg.kind_of?(Integer) }
|
1293
|
+
end
|
1294
|
+
|
1295
|
+
# @api private
|
1296
|
+
def extract_offset_limit_from_one_argument(arg)
|
1297
|
+
case arg
|
1298
|
+
when Integer then extract_offset_limit_from_integer(arg)
|
1299
|
+
when Range then extract_offset_limit_from_range(arg)
|
1300
|
+
end
|
1301
|
+
end
|
1302
|
+
|
1303
|
+
# @api private
|
1304
|
+
def extract_offset_limit_from_integer(integer)
|
1305
|
+
[ integer, 1 ]
|
1306
|
+
end
|
1307
|
+
|
1308
|
+
# @api private
|
1309
|
+
def extract_offset_limit_from_range(range)
|
1310
|
+
offset = range.first
|
1311
|
+
limit = range.last - offset
|
1312
|
+
limit = limit.succ unless range.exclude_end?
|
1313
|
+
return offset, limit
|
1314
|
+
end
|
1315
|
+
|
1123
1316
|
# @api private
|
1124
1317
|
def get_relative_position(offset, limit)
|
1125
|
-
|
1318
|
+
self_offset = self.offset
|
1319
|
+
self_limit = self.limit
|
1320
|
+
new_offset = self_offset + offset
|
1126
1321
|
|
1127
|
-
if limit <= 0 || (
|
1322
|
+
if limit <= 0 || (self_limit && new_offset + limit > self_offset + self_limit)
|
1128
1323
|
raise RangeError, "offset #{offset} and limit #{limit} are outside allowed range"
|
1129
1324
|
end
|
1130
1325
|
|
@@ -1142,18 +1337,122 @@ module DataMapper
|
|
1142
1337
|
end
|
1143
1338
|
end
|
1144
1339
|
|
1145
|
-
#
|
1340
|
+
# @api private
|
1341
|
+
def collection_for_nil(relationship)
|
1342
|
+
query = relationship.query.dup
|
1343
|
+
|
1344
|
+
relationship.target_key.each do |target_key|
|
1345
|
+
query[target_key.name.not] = nil if target_key.allow_nil?
|
1346
|
+
end
|
1347
|
+
|
1348
|
+
relationship.target_model.all(query)
|
1349
|
+
end
|
1350
|
+
|
1146
1351
|
# @api private
|
1147
1352
|
def each_comparison
|
1148
|
-
operands = conditions.operands.
|
1353
|
+
operands = conditions.operands.to_a
|
1149
1354
|
|
1150
1355
|
while operand = operands.shift
|
1151
1356
|
if operand.respond_to?(:operands)
|
1152
|
-
operands.
|
1357
|
+
operands.unshift(*operand.operands)
|
1153
1358
|
else
|
1154
1359
|
yield operand
|
1155
1360
|
end
|
1156
1361
|
end
|
1157
1362
|
end
|
1363
|
+
|
1364
|
+
# Apply a set operation on self and another query
|
1365
|
+
#
|
1366
|
+
# @param [Symbol] operation
|
1367
|
+
# the set operation to apply
|
1368
|
+
# @param [Query] other
|
1369
|
+
# the other query to apply the set operation on
|
1370
|
+
#
|
1371
|
+
# @return [Query]
|
1372
|
+
# the query that was created for the set operation
|
1373
|
+
#
|
1374
|
+
# @api private
|
1375
|
+
def set_operation(operation, other)
|
1376
|
+
assert_valid_other(other)
|
1377
|
+
query = self.class.new(@repository, @model, other.to_relative_hash)
|
1378
|
+
query.instance_variable_set(:@conditions, other_conditions(other, operation))
|
1379
|
+
query
|
1380
|
+
end
|
1381
|
+
|
1382
|
+
# Return the union with another query's conditions
|
1383
|
+
#
|
1384
|
+
# @param [Query] other
|
1385
|
+
# the query conditions to union with
|
1386
|
+
#
|
1387
|
+
# @return [OrOperation]
|
1388
|
+
# the union of the query conditions and other conditions
|
1389
|
+
#
|
1390
|
+
# @api private
|
1391
|
+
def other_conditions(other, operation)
|
1392
|
+
query_conditions(self).send(operation, query_conditions(other))
|
1393
|
+
end
|
1394
|
+
|
1395
|
+
# Extract conditions from a Query
|
1396
|
+
#
|
1397
|
+
# @param [Query] query
|
1398
|
+
# the query with conditions
|
1399
|
+
#
|
1400
|
+
# @return [AbstractOperation]
|
1401
|
+
# the operation
|
1402
|
+
#
|
1403
|
+
# @api private
|
1404
|
+
def query_conditions(query)
|
1405
|
+
if query.limit || query.links.any?
|
1406
|
+
query.to_subquery
|
1407
|
+
else
|
1408
|
+
query.conditions
|
1409
|
+
end
|
1410
|
+
end
|
1411
|
+
|
1412
|
+
# Return a self referrential relationship
|
1413
|
+
#
|
1414
|
+
# @return [Associations::OneToMany::Relationship]
|
1415
|
+
# the 1:m association to the same model
|
1416
|
+
#
|
1417
|
+
# @api private
|
1418
|
+
def self_relationship
|
1419
|
+
@self_relationship ||=
|
1420
|
+
begin
|
1421
|
+
model = self.model
|
1422
|
+
Associations::OneToMany::Relationship.new(
|
1423
|
+
:self,
|
1424
|
+
model,
|
1425
|
+
model,
|
1426
|
+
self_relationship_options
|
1427
|
+
)
|
1428
|
+
end
|
1429
|
+
end
|
1430
|
+
|
1431
|
+
# Return options for the self referrential relationship
|
1432
|
+
#
|
1433
|
+
# @return [Hash]
|
1434
|
+
# the options to use with the self referrential relationship
|
1435
|
+
#
|
1436
|
+
# @api private
|
1437
|
+
def self_relationship_options
|
1438
|
+
keys = model_key.map { |property| property.name }
|
1439
|
+
repository = self.repository
|
1440
|
+
{
|
1441
|
+
:child_key => keys,
|
1442
|
+
:parent_key => keys,
|
1443
|
+
:child_repository_name => repository,
|
1444
|
+
:parent_repository_name => repository,
|
1445
|
+
}
|
1446
|
+
end
|
1447
|
+
|
1448
|
+
# Return the model key
|
1449
|
+
#
|
1450
|
+
# @return [PropertySet]
|
1451
|
+
# the model key
|
1452
|
+
#
|
1453
|
+
# @api private
|
1454
|
+
def model_key
|
1455
|
+
@properties.key
|
1456
|
+
end
|
1158
1457
|
end # class Query
|
1159
1458
|
end # module DataMapper
|