added iOS source code
[wl-app.git] / iOS / Pods / Realm / Realm / RLMResults.mm
1 ////////////////////////////////////////////////////////////////////////////
2 //
3 // Copyright 2014 Realm Inc.
4 //
5 // Licensed under the Apache License, Version 2.0 (the "License");
6 // you may not use this file except in compliance with the License.
7 // You may obtain a copy of the License at
8 //
9 // http://www.apache.org/licenses/LICENSE-2.0
10 //
11 // Unless required by applicable law or agreed to in writing, software
12 // distributed under the License is distributed on an "AS IS" BASIS,
13 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 // See the License for the specific language governing permissions and
15 // limitations under the License.
16 //
17 ////////////////////////////////////////////////////////////////////////////
18
19 #import "RLMResults_Private.hpp"
20
21 #import "RLMAccessor.hpp"
22 #import "RLMArray_Private.hpp"
23 #import "RLMCollection_Private.hpp"
24 #import "RLMObjectSchema_Private.hpp"
25 #import "RLMObjectStore.h"
26 #import "RLMObject_Private.hpp"
27 #import "RLMObservation.hpp"
28 #import "RLMProperty_Private.h"
29 #import "RLMQueryUtil.hpp"
30 #import "RLMRealm_Private.hpp"
31 #import "RLMSchema_Private.h"
32 #import "RLMThreadSafeReference_Private.hpp"
33 #import "RLMUtil.hpp"
34
35 #import "results.hpp"
36 #import "shared_realm.hpp"
37
38 #import <objc/message.h>
39 #import <realm/table_view.hpp>
40
41 using namespace realm;
42
43 #pragma clang diagnostic push
44 #pragma clang diagnostic ignored "-Wincomplete-implementation"
45 @implementation RLMNotificationToken
46 @end
47 #pragma clang diagnostic pop
48
49 @interface RLMResults () <RLMThreadConfined_Private>
50 @end
51
52 //
53 // RLMResults implementation
54 //
55 @implementation RLMResults {
56     RLMRealm *_realm;
57     RLMClassInfo *_info;
58 }
59
60 - (instancetype)initPrivate {
61     self = [super init];
62     return self;
63 }
64
65 - (instancetype)initWithResults:(Results)results {
66     if (self = [super init]) {
67         _results = std::move(results);
68     }
69     return self;
70 }
71
72 static void assertKeyPathIsNotNested(NSString *keyPath) {
73     if ([keyPath rangeOfString:@"."].location != NSNotFound) {
74         @throw RLMException(@"Nested key paths are not supported yet for KVC collection operators.");
75     }
76 }
77
78 void RLMThrowResultsError(NSString *aggregateMethod) {
79     try {
80         throw;
81     }
82     catch (realm::InvalidTransactionException const&) {
83         @throw RLMException(@"Cannot modify Results outside of a write transaction.");
84     }
85     catch (realm::IncorrectThreadException const&) {
86         @throw RLMException(@"Realm accessed from incorrect thread.");
87     }
88     catch (realm::Results::InvalidatedException const&) {
89         @throw RLMException(@"RLMResults has been invalidated.");
90     }
91     catch (realm::Results::DetatchedAccessorException const&) {
92         @throw RLMException(@"Object has been invalidated.");
93     }
94     catch (realm::Results::IncorrectTableException const& e) {
95         @throw RLMException(@"Object of type '%s' does not match RLMResults type '%s'.",
96                             e.actual.data(), e.expected.data());
97     }
98     catch (realm::Results::OutOfBoundsIndexException const& e) {
99         @throw RLMException(@"Index %zu is out of bounds (must be less than %zu).",
100                             e.requested, e.valid_count);
101     }
102     catch (realm::Results::UnsupportedColumnTypeException const& e) {
103         @throw RLMException(@"%@ is not supported for %s%s property '%s'.",
104                             aggregateMethod,
105                             string_for_property_type(e.property_type),
106                             is_nullable(e.property_type) ? "?" : "",
107                             e.column_name.data());
108     }
109     catch (std::exception const& e) {
110         @throw RLMException(e);
111     }
112 }
113
114 + (instancetype)resultsWithObjectInfo:(RLMClassInfo&)info
115                               results:(realm::Results)results {
116     RLMResults *ar = [[self alloc] initPrivate];
117     ar->_results = std::move(results);
118     ar->_realm = info.realm;
119     ar->_info = &info;
120     return ar;
121 }
122
123 + (instancetype)emptyDetachedResults {
124     return [[self alloc] initPrivate];
125 }
126
127 static inline void RLMResultsValidateInWriteTransaction(__unsafe_unretained RLMResults *const ar) {
128     ar->_realm->_realm->verify_thread();
129     ar->_realm->_realm->verify_in_write();
130 }
131
132 - (BOOL)isInvalidated {
133     return translateRLMResultsErrors([&] { return !_results.is_valid(); });
134 }
135
136 - (NSUInteger)count {
137     return translateRLMResultsErrors([&] { return _results.size(); });
138 }
139
140 - (RLMPropertyType)type {
141     return translateRLMResultsErrors([&] {
142         return static_cast<RLMPropertyType>(_results.get_type() & ~realm::PropertyType::Nullable);
143     });
144 }
145
146 - (BOOL)isOptional {
147     return translateRLMResultsErrors([&] {
148         return is_nullable(_results.get_type());
149     });
150 }
151
152 - (NSString *)objectClassName {
153     return translateRLMResultsErrors([&] {
154         return RLMStringDataToNSString(_results.get_object_type());
155     });
156 }
157
158 - (RLMClassInfo *)objectInfo {
159     return _info;
160 }
161
162 - (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state
163                                   objects:(__unused __unsafe_unretained id [])buffer
164                                     count:(NSUInteger)len {
165     if (!_info) {
166         return 0;
167     }
168     return RLMFastEnumerate(state, len, self);
169 }
170
171 - (NSUInteger)indexOfObjectWhere:(NSString *)predicateFormat, ... {
172     va_list args;
173     va_start(args, predicateFormat);
174     NSUInteger index = [self indexOfObjectWhere:predicateFormat args:args];
175     va_end(args);
176     return index;
177 }
178
179 - (NSUInteger)indexOfObjectWhere:(NSString *)predicateFormat args:(va_list)args {
180     return [self indexOfObjectWithPredicate:[NSPredicate predicateWithFormat:predicateFormat
181                                                                    arguments:args]];
182 }
183
184 - (NSUInteger)indexOfObjectWithPredicate:(NSPredicate *)predicate {
185     if (_results.get_mode() == Results::Mode::Empty) {
186         return NSNotFound;
187     }
188
189     return translateRLMResultsErrors([&] {
190         if (_results.get_type() != realm::PropertyType::Object) {
191             @throw RLMException(@"Querying is currently only implemented for arrays of Realm Objects");
192         }
193         return RLMConvertNotFound(_results.index_of(RLMPredicateToQuery(predicate, _info->rlmObjectSchema, _realm.schema, _realm.group)));
194     });
195 }
196
197 - (id)objectAtIndex:(NSUInteger)index {
198     RLMAccessorContext ctx(_realm, *_info);
199     return translateRLMResultsErrors([&] {
200         return _results.get(ctx, index);
201     });
202 }
203
204 - (id)firstObject {
205     if (!_info) {
206         return nil;
207     }
208     RLMAccessorContext ctx(_realm, *_info);
209     return translateRLMResultsErrors([&] {
210         return _results.first(ctx);
211     });
212 }
213
214 - (id)lastObject {
215     if (!_info) {
216         return nil;
217     }
218     RLMAccessorContext ctx(_realm, *_info);
219     return translateRLMResultsErrors([&] {
220         return _results.last(ctx);
221     });
222 }
223
224 - (NSUInteger)indexOfObject:(RLMObject *)object {
225     if (!_info || !object || (!object->_realm && !object.invalidated)) {
226         return NSNotFound;
227     }
228     RLMAccessorContext ctx(_realm, *_info);
229     return translateRLMResultsErrors([&] {
230         return RLMConvertNotFound(_results.index_of(ctx, object));
231     });
232 }
233
234 - (id)valueForKeyPath:(NSString *)keyPath {
235     if ([keyPath characterAtIndex:0] != '@') {
236         return [super valueForKeyPath:keyPath];
237     }
238     if ([keyPath isEqualToString:@"@count"]) {
239         return @(self.count);
240     }
241
242     NSRange operatorRange = [keyPath rangeOfString:@"." options:NSLiteralSearch];
243     NSUInteger keyPathLength = keyPath.length;
244     NSUInteger separatorIndex = operatorRange.location != NSNotFound ? operatorRange.location : keyPathLength;
245     NSString *operatorName = [keyPath substringWithRange:NSMakeRange(1, separatorIndex - 1)];
246     SEL opSelector = NSSelectorFromString([NSString stringWithFormat:@"_%@ForKeyPath:", operatorName]);
247     if (![self respondsToSelector:opSelector]) {
248         @throw RLMException(@"Unsupported KVC collection operator found in key path '%@'", keyPath);
249     }
250     if (separatorIndex >= keyPathLength - 1) {
251         @throw RLMException(@"Missing key path for KVC collection operator %@ in key path '%@'",
252                             operatorName, keyPath);
253     }
254     NSString *operatorKeyPath = [keyPath substringFromIndex:separatorIndex + 1];
255     return ((id(*)(id, SEL, id))objc_msgSend)(self, opSelector, operatorKeyPath);
256 }
257
258 - (id)valueForKey:(NSString *)key {
259     return translateRLMResultsErrors([&] {
260         return RLMCollectionValueForKey(_results, key, _realm, *_info);
261     });
262 }
263
264 - (void)setValue:(id)value forKey:(NSString *)key {
265     translateRLMResultsErrors([&] { RLMResultsValidateInWriteTransaction(self); });
266     RLMCollectionSetValueForKey(self, key, value);
267 }
268
269 - (NSNumber *)_aggregateForKeyPath:(NSString *)keyPath
270                             method:(util::Optional<Mixed> (Results::*)(size_t))method
271                         methodName:(NSString *)methodName returnNilForEmpty:(BOOL)returnNilForEmpty {
272     assertKeyPathIsNotNested(keyPath);
273     return [self aggregate:keyPath method:method methodName:methodName returnNilForEmpty:returnNilForEmpty];
274 }
275
276 - (NSNumber *)_minForKeyPath:(NSString *)keyPath {
277     return [self _aggregateForKeyPath:keyPath method:&Results::min methodName:@"@min" returnNilForEmpty:YES];
278 }
279
280 - (NSNumber *)_maxForKeyPath:(NSString *)keyPath {
281     return [self _aggregateForKeyPath:keyPath method:&Results::max methodName:@"@max" returnNilForEmpty:YES];
282 }
283
284 - (NSNumber *)_sumForKeyPath:(NSString *)keyPath {
285     return [self _aggregateForKeyPath:keyPath method:&Results::sum methodName:@"@sum" returnNilForEmpty:NO];
286 }
287
288 - (NSNumber *)_avgForKeyPath:(NSString *)keyPath {
289     assertKeyPathIsNotNested(keyPath);
290     return [self averageOfProperty:keyPath];
291 }
292
293 - (NSArray *)_unionOfObjectsForKeyPath:(NSString *)keyPath {
294     assertKeyPathIsNotNested(keyPath);
295     return translateRLMResultsErrors([&] {
296         return RLMCollectionValueForKey(_results, keyPath, _realm, *_info);
297     });
298 }
299
300 - (NSArray *)_distinctUnionOfObjectsForKeyPath:(NSString *)keyPath {
301     return [NSSet setWithArray:[self _unionOfObjectsForKeyPath:keyPath]].allObjects;
302 }
303
304 - (NSArray *)_unionOfArraysForKeyPath:(NSString *)keyPath {
305     assertKeyPathIsNotNested(keyPath);
306     if ([keyPath isEqualToString:@"self"]) {
307         @throw RLMException(@"self is not a valid key-path for a KVC array collection operator as 'unionOfArrays'.");
308     }
309
310     return translateRLMResultsErrors([&] {
311         NSMutableArray *flatArray = [NSMutableArray new];
312         for (id<NSFastEnumeration> array in RLMCollectionValueForKey(_results, keyPath, _realm, *_info)) {
313             for (id value in array) {
314                 [flatArray addObject:value];
315             }
316         }
317         return flatArray;
318     });
319 }
320
321 - (NSArray *)_distinctUnionOfArraysForKeyPath:(__unused NSString *)keyPath {
322     return [NSSet setWithArray:[self _unionOfArraysForKeyPath:keyPath]].allObjects;
323 }
324
325 - (RLMResults *)objectsWhere:(NSString *)predicateFormat, ... {
326     va_list args;
327     va_start(args, predicateFormat);
328     RLMResults *results = [self objectsWhere:predicateFormat args:args];
329     va_end(args);
330     return results;
331 }
332
333 - (RLMResults *)objectsWhere:(NSString *)predicateFormat args:(va_list)args {
334     return [self objectsWithPredicate:[NSPredicate predicateWithFormat:predicateFormat arguments:args]];
335 }
336
337 - (RLMResults *)objectsWithPredicate:(NSPredicate *)predicate {
338     return translateRLMResultsErrors([&] {
339         if (_results.get_mode() == Results::Mode::Empty) {
340             return self;
341         }
342         if (_results.get_type() != realm::PropertyType::Object) {
343             @throw RLMException(@"Querying is currently only implemented for arrays of Realm Objects");
344         }
345         auto query = RLMPredicateToQuery(predicate, _info->rlmObjectSchema, _realm.schema, _realm.group);
346         return [RLMResults resultsWithObjectInfo:*_info results:_results.filter(std::move(query))];
347     });
348 }
349
350 - (RLMResults *)sortedResultsUsingKeyPath:(NSString *)keyPath ascending:(BOOL)ascending {
351     return [self sortedResultsUsingDescriptors:@[[RLMSortDescriptor sortDescriptorWithKeyPath:keyPath ascending:ascending]]];
352 }
353
354 - (RLMResults *)sortedResultsUsingDescriptors:(NSArray<RLMSortDescriptor *> *)properties {
355     if (properties.count == 0) {
356         return self;
357     }
358     return translateRLMResultsErrors([&] {
359         if (_results.get_mode() == Results::Mode::Empty) {
360             return self;
361         }
362         return [RLMResults resultsWithObjectInfo:*_info
363                                          results:_results.sort(RLMSortDescriptorsToKeypathArray(properties))];
364     });
365 }
366
367 - (RLMResults *)distinctResultsUsingKeyPaths:(NSArray<NSString *> *)keyPaths {
368     for (NSString *keyPath in keyPaths) {
369         if ([keyPath rangeOfString:@"@"].location != NSNotFound) {
370             @throw RLMException(@"Cannot distinct on keypath '%@': KVC collection operators are not supported.", keyPath);
371         }
372     }
373     
374     return translateRLMResultsErrors([&] {
375         if (_results.get_mode() == Results::Mode::Empty) {
376             return self;
377         }
378         
379         std::vector<std::string> keyPathsVector;
380         for (NSString *keyPath in keyPaths) {
381             keyPathsVector.push_back(keyPath.UTF8String);
382         }
383         
384         return [RLMResults resultsWithObjectInfo:*_info results:_results.distinct(keyPathsVector)];
385     });
386 }
387
388 - (id)objectAtIndexedSubscript:(NSUInteger)index {
389     return [self objectAtIndex:index];
390 }
391
392 - (id)aggregate:(NSString *)property method:(util::Optional<Mixed> (Results::*)(size_t))method
393      methodName:(NSString *)methodName returnNilForEmpty:(BOOL)returnNilForEmpty {
394     if (_results.get_mode() == Results::Mode::Empty) {
395         return returnNilForEmpty ? nil : @0;
396     }
397     size_t column = 0;
398     if (self.type == RLMPropertyTypeObject || ![property isEqualToString:@"self"]) {
399         column = _info->tableColumn(property);
400     }
401
402     auto value = translateRLMResultsErrors([&] { return (_results.*method)(column); }, methodName);
403     return value ? RLMMixedToObjc(*value) : nil;
404 }
405
406 - (id)minOfProperty:(NSString *)property {
407     return [self aggregate:property method:&Results::min
408                 methodName:@"minOfProperty" returnNilForEmpty:YES];
409 }
410
411 - (id)maxOfProperty:(NSString *)property {
412     return [self aggregate:property method:&Results::max
413                 methodName:@"maxOfProperty" returnNilForEmpty:YES];
414 }
415
416 - (id)sumOfProperty:(NSString *)property {
417     return [self aggregate:property method:&Results::sum
418                 methodName:@"sumOfProperty" returnNilForEmpty:NO];
419 }
420
421 - (id)averageOfProperty:(NSString *)property {
422     if (_results.get_mode() == Results::Mode::Empty) {
423         return nil;
424     }
425     size_t column = 0;
426     if (self.type == RLMPropertyTypeObject || ![property isEqualToString:@"self"]) {
427         column = _info->tableColumn(property);
428     }
429     auto value = translateRLMResultsErrors([&] { return _results.average(column); }, @"averageOfProperty");
430     return value ? @(*value) : nil;
431 }
432
433 - (void)deleteObjectsFromRealm {
434     if (self.type != RLMPropertyTypeObject) {
435         @throw RLMException(@"Cannot delete objects from RLMResults<%@>: only RLMObjects can be deleted.",
436                             RLMTypeToString(self.type));
437     }
438     return translateRLMResultsErrors([&] {
439         if (_results.get_mode() == Results::Mode::Table) {
440             RLMResultsValidateInWriteTransaction(self);
441             RLMClearTable(*_info);
442         }
443         else {
444             RLMTrackDeletions(_realm, [&] { _results.clear(); });
445         }
446     });
447 }
448
449 - (NSString *)description {
450     return RLMDescriptionWithMaxDepth(@"RLMResults", self, RLMDescriptionMaxDepth);
451 }
452
453 - (realm::TableView)tableView {
454     return translateRLMResultsErrors([&] { return _results.get_tableview(); });
455 }
456
457 - (RLMFastEnumerator *)fastEnumerator {
458     return translateRLMResultsErrors([&] {
459         return [[RLMFastEnumerator alloc] initWithResults:_results collection:self
460                                                     realm:_realm classInfo:*_info];
461     });
462 }
463
464 // The compiler complains about the method's argument type not matching due to
465 // it not having the generic type attached, but it doesn't seem to be possible
466 // to actually include the generic type
467 // http://www.openradar.me/radar?id=6135653276319744
468 #pragma clang diagnostic push
469 #pragma clang diagnostic ignored "-Wmismatched-parameter-types"
470 - (RLMNotificationToken *)addNotificationBlock:(void (^)(RLMResults *, RLMCollectionChange *, NSError *))block {
471     [_realm verifyNotificationsAreSupported:true];
472     return RLMAddNotificationBlock(self, _results, block, true);
473 }
474 #pragma clang diagnostic pop
475
476 - (BOOL)isAttached
477 {
478     return !!_realm;
479 }
480
481 #pragma mark - Thread Confined Protocol Conformance
482
483 - (std::unique_ptr<realm::ThreadSafeReferenceBase>)makeThreadSafeReference {
484     return std::make_unique<realm::ThreadSafeReference<Results>>(_realm->_realm->obtain_thread_safe_reference(_results));
485 }
486
487 - (id)objectiveCMetadata {
488     return nil;
489 }
490
491 + (instancetype)objectWithThreadSafeReference:(std::unique_ptr<realm::ThreadSafeReferenceBase>)reference
492                                      metadata:(__unused id)metadata
493                                         realm:(RLMRealm *)realm {
494     REALM_ASSERT_DEBUG(dynamic_cast<realm::ThreadSafeReference<Results> *>(reference.get()));
495     auto results_reference = static_cast<realm::ThreadSafeReference<Results> *>(reference.get());
496
497     Results results = realm->_realm->resolve_thread_safe_reference(std::move(*results_reference));
498
499     return [RLMResults resultsWithObjectInfo:realm->_info[RLMStringDataToNSString(results.get_object_type())]
500                                      results:std::move(results)];
501 }
502
503 @end
504
505 @implementation RLMLinkingObjects
506 @end