1 ////////////////////////////////////////////////////////////////////////////
3 // Copyright 2014 Realm Inc.
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
9 // http://www.apache.org/licenses/LICENSE-2.0
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.
17 ////////////////////////////////////////////////////////////////////////////
19 #import "RLMResults_Private.hpp"
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"
36 #import "shared_realm.hpp"
38 #import <objc/message.h>
39 #import <realm/table_view.hpp>
41 using namespace realm;
43 #pragma clang diagnostic push
44 #pragma clang diagnostic ignored "-Wincomplete-implementation"
45 @implementation RLMNotificationToken
47 #pragma clang diagnostic pop
49 @interface RLMResults () <RLMThreadConfined_Private>
53 // RLMResults implementation
55 @implementation RLMResults {
60 - (instancetype)initPrivate {
65 - (instancetype)initWithResults:(Results)results {
66 if (self = [super init]) {
67 _results = std::move(results);
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.");
78 void RLMThrowResultsError(NSString *aggregateMethod) {
82 catch (realm::InvalidTransactionException const&) {
83 @throw RLMException(@"Cannot modify Results outside of a write transaction.");
85 catch (realm::IncorrectThreadException const&) {
86 @throw RLMException(@"Realm accessed from incorrect thread.");
88 catch (realm::Results::InvalidatedException const&) {
89 @throw RLMException(@"RLMResults has been invalidated.");
91 catch (realm::Results::DetatchedAccessorException const&) {
92 @throw RLMException(@"Object has been invalidated.");
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());
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);
102 catch (realm::Results::UnsupportedColumnTypeException const& e) {
103 @throw RLMException(@"%@ is not supported for %s%s property '%s'.",
105 string_for_property_type(e.property_type),
106 is_nullable(e.property_type) ? "?" : "",
107 e.column_name.data());
109 catch (std::exception const& e) {
110 @throw RLMException(e);
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;
123 + (instancetype)emptyDetachedResults {
124 return [[self alloc] initPrivate];
127 static inline void RLMResultsValidateInWriteTransaction(__unsafe_unretained RLMResults *const ar) {
128 ar->_realm->_realm->verify_thread();
129 ar->_realm->_realm->verify_in_write();
132 - (BOOL)isInvalidated {
133 return translateRLMResultsErrors([&] { return !_results.is_valid(); });
136 - (NSUInteger)count {
137 return translateRLMResultsErrors([&] { return _results.size(); });
140 - (RLMPropertyType)type {
141 return translateRLMResultsErrors([&] {
142 return static_cast<RLMPropertyType>(_results.get_type() & ~realm::PropertyType::Nullable);
147 return translateRLMResultsErrors([&] {
148 return is_nullable(_results.get_type());
152 - (NSString *)objectClassName {
153 return translateRLMResultsErrors([&] {
154 return RLMStringDataToNSString(_results.get_object_type());
158 - (RLMClassInfo *)objectInfo {
162 - (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state
163 objects:(__unused __unsafe_unretained id [])buffer
164 count:(NSUInteger)len {
168 return RLMFastEnumerate(state, len, self);
171 - (NSUInteger)indexOfObjectWhere:(NSString *)predicateFormat, ... {
173 va_start(args, predicateFormat);
174 NSUInteger index = [self indexOfObjectWhere:predicateFormat args:args];
179 - (NSUInteger)indexOfObjectWhere:(NSString *)predicateFormat args:(va_list)args {
180 return [self indexOfObjectWithPredicate:[NSPredicate predicateWithFormat:predicateFormat
184 - (NSUInteger)indexOfObjectWithPredicate:(NSPredicate *)predicate {
185 if (_results.get_mode() == Results::Mode::Empty) {
189 return translateRLMResultsErrors([&] {
190 if (_results.get_type() != realm::PropertyType::Object) {
191 @throw RLMException(@"Querying is currently only implemented for arrays of Realm Objects");
193 return RLMConvertNotFound(_results.index_of(RLMPredicateToQuery(predicate, _info->rlmObjectSchema, _realm.schema, _realm.group)));
197 - (id)objectAtIndex:(NSUInteger)index {
198 RLMAccessorContext ctx(_realm, *_info);
199 return translateRLMResultsErrors([&] {
200 return _results.get(ctx, index);
208 RLMAccessorContext ctx(_realm, *_info);
209 return translateRLMResultsErrors([&] {
210 return _results.first(ctx);
218 RLMAccessorContext ctx(_realm, *_info);
219 return translateRLMResultsErrors([&] {
220 return _results.last(ctx);
224 - (NSUInteger)indexOfObject:(RLMObject *)object {
225 if (!_info || !object || (!object->_realm && !object.invalidated)) {
228 RLMAccessorContext ctx(_realm, *_info);
229 return translateRLMResultsErrors([&] {
230 return RLMConvertNotFound(_results.index_of(ctx, object));
234 - (id)valueForKeyPath:(NSString *)keyPath {
235 if ([keyPath characterAtIndex:0] != '@') {
236 return [super valueForKeyPath:keyPath];
238 if ([keyPath isEqualToString:@"@count"]) {
239 return @(self.count);
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);
250 if (separatorIndex >= keyPathLength - 1) {
251 @throw RLMException(@"Missing key path for KVC collection operator %@ in key path '%@'",
252 operatorName, keyPath);
254 NSString *operatorKeyPath = [keyPath substringFromIndex:separatorIndex + 1];
255 return ((id(*)(id, SEL, id))objc_msgSend)(self, opSelector, operatorKeyPath);
258 - (id)valueForKey:(NSString *)key {
259 return translateRLMResultsErrors([&] {
260 return RLMCollectionValueForKey(_results, key, _realm, *_info);
264 - (void)setValue:(id)value forKey:(NSString *)key {
265 translateRLMResultsErrors([&] { RLMResultsValidateInWriteTransaction(self); });
266 RLMCollectionSetValueForKey(self, key, value);
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];
276 - (NSNumber *)_minForKeyPath:(NSString *)keyPath {
277 return [self _aggregateForKeyPath:keyPath method:&Results::min methodName:@"@min" returnNilForEmpty:YES];
280 - (NSNumber *)_maxForKeyPath:(NSString *)keyPath {
281 return [self _aggregateForKeyPath:keyPath method:&Results::max methodName:@"@max" returnNilForEmpty:YES];
284 - (NSNumber *)_sumForKeyPath:(NSString *)keyPath {
285 return [self _aggregateForKeyPath:keyPath method:&Results::sum methodName:@"@sum" returnNilForEmpty:NO];
288 - (NSNumber *)_avgForKeyPath:(NSString *)keyPath {
289 assertKeyPathIsNotNested(keyPath);
290 return [self averageOfProperty:keyPath];
293 - (NSArray *)_unionOfObjectsForKeyPath:(NSString *)keyPath {
294 assertKeyPathIsNotNested(keyPath);
295 return translateRLMResultsErrors([&] {
296 return RLMCollectionValueForKey(_results, keyPath, _realm, *_info);
300 - (NSArray *)_distinctUnionOfObjectsForKeyPath:(NSString *)keyPath {
301 return [NSSet setWithArray:[self _unionOfObjectsForKeyPath:keyPath]].allObjects;
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'.");
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];
321 - (NSArray *)_distinctUnionOfArraysForKeyPath:(__unused NSString *)keyPath {
322 return [NSSet setWithArray:[self _unionOfArraysForKeyPath:keyPath]].allObjects;
325 - (RLMResults *)objectsWhere:(NSString *)predicateFormat, ... {
327 va_start(args, predicateFormat);
328 RLMResults *results = [self objectsWhere:predicateFormat args:args];
333 - (RLMResults *)objectsWhere:(NSString *)predicateFormat args:(va_list)args {
334 return [self objectsWithPredicate:[NSPredicate predicateWithFormat:predicateFormat arguments:args]];
337 - (RLMResults *)objectsWithPredicate:(NSPredicate *)predicate {
338 return translateRLMResultsErrors([&] {
339 if (_results.get_mode() == Results::Mode::Empty) {
342 if (_results.get_type() != realm::PropertyType::Object) {
343 @throw RLMException(@"Querying is currently only implemented for arrays of Realm Objects");
345 auto query = RLMPredicateToQuery(predicate, _info->rlmObjectSchema, _realm.schema, _realm.group);
346 return [RLMResults resultsWithObjectInfo:*_info results:_results.filter(std::move(query))];
350 - (RLMResults *)sortedResultsUsingKeyPath:(NSString *)keyPath ascending:(BOOL)ascending {
351 return [self sortedResultsUsingDescriptors:@[[RLMSortDescriptor sortDescriptorWithKeyPath:keyPath ascending:ascending]]];
354 - (RLMResults *)sortedResultsUsingDescriptors:(NSArray<RLMSortDescriptor *> *)properties {
355 if (properties.count == 0) {
358 return translateRLMResultsErrors([&] {
359 if (_results.get_mode() == Results::Mode::Empty) {
362 return [RLMResults resultsWithObjectInfo:*_info
363 results:_results.sort(RLMSortDescriptorsToKeypathArray(properties))];
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);
374 return translateRLMResultsErrors([&] {
375 if (_results.get_mode() == Results::Mode::Empty) {
379 std::vector<std::string> keyPathsVector;
380 for (NSString *keyPath in keyPaths) {
381 keyPathsVector.push_back(keyPath.UTF8String);
384 return [RLMResults resultsWithObjectInfo:*_info results:_results.distinct(keyPathsVector)];
388 - (id)objectAtIndexedSubscript:(NSUInteger)index {
389 return [self objectAtIndex:index];
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;
398 if (self.type == RLMPropertyTypeObject || ![property isEqualToString:@"self"]) {
399 column = _info->tableColumn(property);
402 auto value = translateRLMResultsErrors([&] { return (_results.*method)(column); }, methodName);
403 return value ? RLMMixedToObjc(*value) : nil;
406 - (id)minOfProperty:(NSString *)property {
407 return [self aggregate:property method:&Results::min
408 methodName:@"minOfProperty" returnNilForEmpty:YES];
411 - (id)maxOfProperty:(NSString *)property {
412 return [self aggregate:property method:&Results::max
413 methodName:@"maxOfProperty" returnNilForEmpty:YES];
416 - (id)sumOfProperty:(NSString *)property {
417 return [self aggregate:property method:&Results::sum
418 methodName:@"sumOfProperty" returnNilForEmpty:NO];
421 - (id)averageOfProperty:(NSString *)property {
422 if (_results.get_mode() == Results::Mode::Empty) {
426 if (self.type == RLMPropertyTypeObject || ![property isEqualToString:@"self"]) {
427 column = _info->tableColumn(property);
429 auto value = translateRLMResultsErrors([&] { return _results.average(column); }, @"averageOfProperty");
430 return value ? @(*value) : nil;
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));
438 return translateRLMResultsErrors([&] {
439 if (_results.get_mode() == Results::Mode::Table) {
440 RLMResultsValidateInWriteTransaction(self);
441 RLMClearTable(*_info);
444 RLMTrackDeletions(_realm, [&] { _results.clear(); });
449 - (NSString *)description {
450 return RLMDescriptionWithMaxDepth(@"RLMResults", self, RLMDescriptionMaxDepth);
453 - (realm::TableView)tableView {
454 return translateRLMResultsErrors([&] { return _results.get_tableview(); });
457 - (RLMFastEnumerator *)fastEnumerator {
458 return translateRLMResultsErrors([&] {
459 return [[RLMFastEnumerator alloc] initWithResults:_results collection:self
460 realm:_realm classInfo:*_info];
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);
474 #pragma clang diagnostic pop
481 #pragma mark - Thread Confined Protocol Conformance
483 - (std::unique_ptr<realm::ThreadSafeReferenceBase>)makeThreadSafeReference {
484 return std::make_unique<realm::ThreadSafeReference<Results>>(_realm->_realm->obtain_thread_safe_reference(_results));
487 - (id)objectiveCMetadata {
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());
497 Results results = realm->_realm->resolve_thread_safe_reference(std::move(*results_reference));
499 return [RLMResults resultsWithObjectInfo:realm->_info[RLMStringDataToNSString(results.get_object_type())]
500 results:std::move(results)];
505 @implementation RLMLinkingObjects