added iOS source code
[wl-app.git] / iOS / Pods / Realm / Realm / RLMRealm.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 "RLMRealm_Private.hpp"
20
21 #import "RLMAnalytics.hpp"
22 #import "RLMArray_Private.hpp"
23 #import "RLMMigration_Private.h"
24 #import "RLMObject_Private.h"
25 #import "RLMObject_Private.hpp"
26 #import "RLMObjectSchema_Private.hpp"
27 #import "RLMObjectStore.h"
28 #import "RLMObservation.hpp"
29 #import "RLMProperty.h"
30 #import "RLMProperty_Private.h"
31 #import "RLMQueryUtil.hpp"
32 #import "RLMRealmConfiguration_Private.hpp"
33 #import "RLMRealmUtil.hpp"
34 #import "RLMSchema_Private.hpp"
35 #import "RLMSyncManager_Private.h"
36 #import "RLMSyncUtil_Private.hpp"
37 #import "RLMThreadSafeReference_Private.hpp"
38 #import "RLMUpdateChecker.hpp"
39 #import "RLMUtil.hpp"
40
41 #include "impl/realm_coordinator.hpp"
42 #include "object_store.hpp"
43 #include "schema.hpp"
44 #include "shared_realm.hpp"
45
46 #include <realm/disable_sync_to_disk.hpp>
47 #include <realm/util/scope_exit.hpp>
48 #include <realm/version.hpp>
49
50 #import "sync/sync_session.hpp"
51
52 using namespace realm;
53 using util::File;
54
55 @interface RLMRealmNotificationToken : RLMNotificationToken
56 @property (nonatomic, strong) RLMRealm *realm;
57 @property (nonatomic, copy) RLMNotificationBlock block;
58 @end
59
60 @interface RLMRealm ()
61 @property (nonatomic, strong) NSHashTable<RLMRealmNotificationToken *> *notificationHandlers;
62 - (void)sendNotifications:(RLMNotification)notification;
63 @end
64
65 void RLMDisableSyncToDisk() {
66     realm::disable_sync_to_disk();
67 }
68
69 static void RLMAddSkipBackupAttributeToItemAtPath(std::string const& path) {
70     [[NSURL fileURLWithPath:@(path.c_str())] setResourceValue:@YES forKey:NSURLIsExcludedFromBackupKey error:nil];
71 }
72
73 @implementation RLMRealmNotificationToken
74 - (void)invalidate {
75     [_realm verifyThread];
76     [_realm.notificationHandlers removeObject:self];
77     _realm = nil;
78     _block = nil;
79 }
80
81 - (void)suppressNextNotification {
82     // Temporarily replace the block with one which restores the old block
83     // rather than producing a notification.
84
85     // This briefly creates a retain cycle but it's fine because the block will
86     // be synchronously called shortly after this method is called. Unlike with
87     // collection notifications, this does not have to go through the object
88     // store or do fancy things to handle transaction coalescing because it's
89     // called synchronously by the obj-c code and not by the object store.
90     auto notificationBlock = _block;
91     _block = ^(RLMNotification, RLMRealm *) {
92         _block = notificationBlock;
93     };
94 }
95
96 - (void)dealloc {
97     if (_realm || _block) {
98         NSLog(@"RLMNotificationToken released without unregistering a notification. You must hold "
99               @"on to the RLMNotificationToken returned from addNotificationBlock and call "
100               @"-[RLMNotificationToken invalidate] when you no longer wish to receive RLMRealm notifications.");
101     }
102 }
103 @end
104
105 static bool shouldForciblyDisableEncryption() {
106     static bool disableEncryption = getenv("REALM_DISABLE_ENCRYPTION");
107     return disableEncryption;
108 }
109
110 NSData *RLMRealmValidatedEncryptionKey(NSData *key) {
111     if (shouldForciblyDisableEncryption()) {
112         return nil;
113     }
114
115     if (key && key.length != 64) {
116         @throw RLMException(@"Encryption key must be exactly 64 bytes long");
117     }
118
119     return key;
120 }
121
122 @implementation RLMRealm {
123     NSHashTable<RLMFastEnumerator *> *_collectionEnumerators;
124     bool _sendingNotifications;
125 }
126
127 + (BOOL)isCoreDebug {
128     return realm::Version::has_feature(realm::feature_Debug);
129 }
130
131 + (void)initialize {
132     static bool initialized;
133     if (initialized) {
134         return;
135     }
136     initialized = true;
137
138     RLMCheckForUpdates();
139     RLMSendAnalytics();
140 }
141
142 - (instancetype)initPrivate {
143     self = [super init];
144     return self;
145 }
146
147 - (BOOL)isEmpty {
148     return realm::ObjectStore::is_empty(self.group);
149 }
150
151 - (void)verifyThread {
152     try {
153         _realm->verify_thread();
154     }
155     catch (std::exception const& e) {
156         @throw RLMException(e);
157     }
158 }
159
160 - (BOOL)inWriteTransaction {
161     return _realm->is_in_transaction();
162 }
163
164 - (realm::Group &)group {
165     return _realm->read_group();
166 }
167
168 - (BOOL)autorefresh {
169     return _realm->auto_refresh();
170 }
171
172 - (void)setAutorefresh:(BOOL)autorefresh {
173     _realm->set_auto_refresh(autorefresh);
174 }
175
176 + (instancetype)defaultRealm {
177     return [RLMRealm realmWithConfiguration:[RLMRealmConfiguration rawDefaultConfiguration] error:nil];
178 }
179
180 + (instancetype)realmWithURL:(NSURL *)fileURL {
181     RLMRealmConfiguration *configuration = [RLMRealmConfiguration defaultConfiguration];
182     configuration.fileURL = fileURL;
183     return [RLMRealm realmWithConfiguration:configuration error:nil];
184 }
185
186 + (void)asyncOpenWithConfiguration:(RLMRealmConfiguration *)configuration
187                      callbackQueue:(dispatch_queue_t)callbackQueue
188                           callback:(RLMAsyncOpenRealmCallback)callback {
189     RLMRealm *strongReferenceToSyncedRealm = nil;
190     if (configuration.config.sync_config) {
191         NSError *error = nil;
192         strongReferenceToSyncedRealm = [RLMRealm uncachedSchemalessRealmWithConfiguration:configuration error:&error];
193         if (error) {
194             dispatch_async(callbackQueue, ^{
195                 callback(nil, error);
196             });
197             return;
198         }
199     }
200     static dispatch_queue_t queue = dispatch_queue_create("io.realm.asyncOpenDispatchQueue", DISPATCH_QUEUE_CONCURRENT);
201     dispatch_async(queue, ^{
202         @autoreleasepool {
203             if (strongReferenceToSyncedRealm) {
204                 // Sync behavior: get the raw session, then wait for it to download.
205                 if (auto session = sync_session_for_realm(strongReferenceToSyncedRealm)) {
206                     // Wait for the session to download, then open it.
207                     session->wait_for_download_completion([=](std::error_code error_code) {
208                         dispatch_async(callbackQueue, ^{
209                             (void)strongReferenceToSyncedRealm;
210                             NSError *error = nil;
211                             if (error_code == std::error_code{}) {
212                                 // Success
213                                 @autoreleasepool {
214                                     // Try opening the Realm on the destination queue.
215                                     RLMRealm *localRealm = [RLMRealm realmWithConfiguration:configuration error:&error];
216                                     callback(localRealm, error);
217                                 }
218                             } else {
219                                 // Failure
220                                 callback(nil, make_sync_error(RLMSyncSystemErrorKindSession,
221                                                               @(error_code.message().c_str()),
222                                                               error_code.value(),
223                                                               nil));
224                             }
225                         });
226                     });
227                 } else {
228                     dispatch_async(callbackQueue, ^{
229                         callback(nil, make_sync_error(RLMSyncSystemErrorKindSession,
230                                                       @"Cannot asynchronously open synced Realm, because the associated session previously experienced a fatal error",
231                                                       NSNotFound,
232                                                       nil));
233                     });
234                     return;
235                 }
236             } else {
237                 // Default behavior: just dispatch onto the destination queue and open the Realm.
238                 dispatch_async(callbackQueue, ^{
239                     @autoreleasepool {
240                         NSError *error = nil;
241                         RLMRealm *localRealm = [RLMRealm realmWithConfiguration:configuration error:&error];
242                         callback(localRealm, error);
243                     }
244                 });
245                 return;
246             }
247         }
248     });
249 }
250
251 // ARC tries to eliminate calls to autorelease when the value is then immediately
252 // returned, but this results in significantly different semantics between debug
253 // and release builds for RLMRealm, so force it to always autorelease.
254 static id RLMAutorelease(__unsafe_unretained id value) {
255     // +1 __bridge_retained, -1 CFAutorelease
256     return value ? (__bridge id)CFAutorelease((__bridge_retained CFTypeRef)value) : nil;
257 }
258
259 + (instancetype)realmWithSharedRealm:(SharedRealm)sharedRealm schema:(RLMSchema *)schema {
260     RLMRealm *realm = [[RLMRealm alloc] initPrivate];
261     realm->_realm = sharedRealm;
262     realm->_dynamic = YES;
263     realm->_schema = schema;
264     realm->_info = RLMSchemaInfo(realm);
265     return RLMAutorelease(realm);
266 }
267
268 REALM_NOINLINE void RLMRealmTranslateException(NSError **error) {
269     try {
270         throw;
271     }
272     catch (RealmFileException const& ex) {
273         switch (ex.kind()) {
274             case RealmFileException::Kind::PermissionDenied:
275                 RLMSetErrorOrThrow(RLMMakeError(RLMErrorFilePermissionDenied, ex), error);
276                 break;
277             case RealmFileException::Kind::IncompatibleLockFile: {
278                 NSString *err = @"Realm file is currently open in another process "
279                                  "which cannot share access with this process. All "
280                                  "processes sharing a single file must be the same "
281                                  "architecture. For sharing files between the Realm "
282                                  "Browser and an iOS simulator, this means that you "
283                                  "must use a 64-bit simulator.";
284                 RLMSetErrorOrThrow(RLMMakeError(RLMErrorIncompatibleLockFile,
285                                                 File::PermissionDenied(err.UTF8String, ex.path())), error);
286                 break;
287             }
288             case RealmFileException::Kind::NotFound:
289                 RLMSetErrorOrThrow(RLMMakeError(RLMErrorFileNotFound, ex), error);
290                 break;
291             case RealmFileException::Kind::Exists:
292                 RLMSetErrorOrThrow(RLMMakeError(RLMErrorFileExists, ex), error);
293                 break;
294             case RealmFileException::Kind::BadHistoryError: {
295                 NSString *err = @"Realm file's history format is incompatible with the "
296                                  "settings in the configuration object being used to open "
297                                  "the Realm. Note that Realms configured for sync cannot be "
298                                  "opened as non-synced Realms, and vice versa. Otherwise, the "
299                                  "file may be corrupt.";
300                 RLMSetErrorOrThrow(RLMMakeError(RLMErrorFileAccess,
301                                                 File::AccessError(err.UTF8String, ex.path())), error);
302                 break;
303             }
304             case RealmFileException::Kind::AccessError:
305                 RLMSetErrorOrThrow(RLMMakeError(RLMErrorFileAccess, ex), error);
306                 break;
307             case RealmFileException::Kind::FormatUpgradeRequired:
308                 RLMSetErrorOrThrow(RLMMakeError(RLMErrorFileFormatUpgradeRequired, ex), error);
309                 break;
310             default:
311                 RLMSetErrorOrThrow(RLMMakeError(RLMErrorFail, ex), error);
312                 break;
313         }
314     }
315     catch (AddressSpaceExhausted const &ex) {
316         RLMSetErrorOrThrow(RLMMakeError(RLMErrorAddressSpaceExhausted, ex), error);
317     }
318     catch (SchemaMismatchException const& ex) {
319         RLMSetErrorOrThrow(RLMMakeError(RLMErrorSchemaMismatch, ex), error);
320     }
321     catch (std::system_error const& ex) {
322         RLMSetErrorOrThrow(RLMMakeError(ex), error);
323     }
324     catch (const std::exception &exp) {
325         RLMSetErrorOrThrow(RLMMakeError(RLMErrorFail, exp), error);
326     }
327 }
328
329 REALM_NOINLINE static void translateSharedGroupOpenException(RLMRealmConfiguration *originalConfiguration, NSError **error) {
330     try {
331         throw;
332     }
333     catch (RealmFileException const& ex) {
334         switch (ex.kind()) {
335             case RealmFileException::Kind::IncompatibleSyncedRealm: {
336                 RLMRealmConfiguration *configuration = [originalConfiguration copy];
337                 configuration.fileURL = [NSURL fileURLWithPath:@(ex.path().data())];
338                 configuration.readOnly = YES;
339
340                 NSError *intermediateError = RLMMakeError(RLMErrorIncompatibleSyncedFile, ex);
341                 NSMutableDictionary *userInfo = [intermediateError.userInfo mutableCopy];
342                 userInfo[RLMBackupRealmConfigurationErrorKey] = configuration;
343                 NSError *finalError = [NSError errorWithDomain:intermediateError.domain code:intermediateError.code
344                                                       userInfo:userInfo];
345                 RLMSetErrorOrThrow(finalError, error);
346                 break;
347             }
348             default:
349                 RLMRealmTranslateException(error);
350                 break;
351         }
352     }
353     catch (...) {
354         RLMRealmTranslateException(error);
355     }
356 }
357
358
359 + (instancetype)realmWithConfiguration:(RLMRealmConfiguration *)configuration error:(NSError **)error {
360     bool dynamic = configuration.dynamic;
361     bool cache = configuration.cache;
362     bool readOnly = configuration.readOnly;
363
364     {
365         Realm::Config& config = configuration.config;
366
367         // try to reuse existing realm first
368         if (cache || dynamic) {
369             if (RLMRealm *realm = RLMGetThreadLocalCachedRealmForPath(config.path)) {
370                 auto const& old_config = realm->_realm->config();
371                 if (old_config.immutable() != config.immutable()
372                     || old_config.read_only_alternative() != config.read_only_alternative()) {
373                     @throw RLMException(@"Realm at path '%s' already opened with different read permissions", config.path.c_str());
374                 }
375                 if (old_config.in_memory != config.in_memory) {
376                     @throw RLMException(@"Realm at path '%s' already opened with different inMemory settings", config.path.c_str());
377                 }
378                 if (realm->_dynamic != dynamic) {
379                     @throw RLMException(@"Realm at path '%s' already opened with different dynamic settings", config.path.c_str());
380                 }
381                 if (old_config.encryption_key != config.encryption_key) {
382                     @throw RLMException(@"Realm at path '%s' already opened with different encryption key", config.path.c_str());
383                 }
384                 return RLMAutorelease(realm);
385             }
386         }
387     }
388
389     configuration = [configuration copy];
390     Realm::Config& config = configuration.config;
391
392     RLMRealm *realm = [[RLMRealm alloc] initPrivate];
393     realm->_dynamic = dynamic;
394
395     // protects the realm cache and accessors cache
396     static std::mutex& initLock = *new std::mutex();
397     std::lock_guard<std::mutex> lock(initLock);
398
399     try {
400         realm->_realm = Realm::get_shared_realm(config);
401     }
402     catch (...) {
403         translateSharedGroupOpenException(configuration, error);
404         return nil;
405     }
406
407     // if we have a cached realm on another thread we can skip a few steps and
408     // just grab its schema
409     @autoreleasepool {
410         // ensure that cachedRealm doesn't end up in this thread's autorelease pool
411         if (auto cachedRealm = RLMGetAnyCachedRealmForPath(config.path)) {
412             realm->_realm->set_schema_subset(cachedRealm->_realm->schema());
413             realm->_schema = cachedRealm.schema;
414             realm->_info = cachedRealm->_info.clone(cachedRealm->_realm->schema(), realm);
415         }
416     }
417
418     if (realm->_schema) { }
419     else if (dynamic) {
420         realm->_schema = [RLMSchema dynamicSchemaFromObjectStoreSchema:realm->_realm->schema()];
421         realm->_info = RLMSchemaInfo(realm);
422     }
423     else {
424         // set/align schema or perform migration if needed
425         RLMSchema *schema = configuration.customSchema ?: RLMSchema.sharedSchema;
426
427         Realm::MigrationFunction migrationFunction;
428         auto migrationBlock = configuration.migrationBlock;
429         if (migrationBlock && configuration.schemaVersion > 0) {
430             migrationFunction = [=](SharedRealm old_realm, SharedRealm realm, Schema& mutableSchema) {
431                 RLMSchema *oldSchema = [RLMSchema dynamicSchemaFromObjectStoreSchema:old_realm->schema()];
432                 RLMRealm *oldRealm = [RLMRealm realmWithSharedRealm:old_realm schema:oldSchema];
433
434                 // The destination RLMRealm can't just use the schema from the
435                 // SharedRealm because it doesn't have information about whether or
436                 // not a class was defined in Swift, which effects how new objects
437                 // are created
438                 RLMRealm *newRealm = [RLMRealm realmWithSharedRealm:realm schema:schema.copy];
439
440                 [[[RLMMigration alloc] initWithRealm:newRealm oldRealm:oldRealm schema:mutableSchema] execute:migrationBlock];
441
442                 oldRealm->_realm = nullptr;
443                 newRealm->_realm = nullptr;
444             };
445         }
446
447         try {
448             realm->_realm->update_schema(schema.objectStoreCopy, config.schema_version,
449                                          std::move(migrationFunction));
450         }
451         catch (...) {
452             RLMRealmTranslateException(error);
453             return nil;
454         }
455
456         realm->_schema = schema;
457         realm->_info = RLMSchemaInfo(realm);
458         RLMRealmCreateAccessors(realm.schema);
459
460         if (!readOnly) {
461             // initializing the schema started a read transaction, so end it
462             [realm invalidate];
463         }
464     }
465
466     if (cache) {
467         RLMCacheRealm(config.path, realm);
468     }
469
470     if (!readOnly) {
471         realm->_realm->m_binding_context = RLMCreateBindingContext(realm);
472         realm->_realm->m_binding_context->realm = realm->_realm;
473
474         RLMAddSkipBackupAttributeToItemAtPath(config.path + ".management");
475         RLMAddSkipBackupAttributeToItemAtPath(config.path + ".lock");
476         RLMAddSkipBackupAttributeToItemAtPath(config.path + ".note");
477     }
478
479     return RLMAutorelease(realm);
480 }
481
482 + (instancetype)uncachedSchemalessRealmWithConfiguration:(RLMRealmConfiguration *)configuration error:(NSError **)error {
483     RLMRealm *realm = [[RLMRealm alloc] initPrivate];
484     try {
485         realm->_realm = Realm::get_shared_realm(configuration.config);
486     }
487     catch (...) {
488         translateSharedGroupOpenException(configuration, error);
489         return nil;
490     }
491     return realm;
492 }
493
494 + (void)resetRealmState {
495     RLMClearRealmCache();
496     realm::_impl::RealmCoordinator::clear_cache();
497     [RLMRealmConfiguration resetRealmConfigurationState];
498 }
499
500 - (void)verifyNotificationsAreSupported:(bool)isCollection {
501     [self verifyThread];
502     if (_realm->config().immutable()) {
503         @throw RLMException(@"Read-only Realms do not change and do not have change notifications");
504     }
505     if (!_realm->can_deliver_notifications()) {
506         @throw RLMException(@"Can only add notification blocks from within runloops.");
507     }
508     if (isCollection && _realm->is_in_transaction()) {
509         @throw RLMException(@"Cannot register notification blocks from within write transactions.");
510     }
511 }
512
513 - (RLMNotificationToken *)addNotificationBlock:(RLMNotificationBlock)block {
514     if (!block) {
515         @throw RLMException(@"The notification block should not be nil");
516     }
517     [self verifyNotificationsAreSupported:false];
518
519     _realm->read_group();
520
521     if (!_notificationHandlers) {
522         _notificationHandlers = [NSHashTable hashTableWithOptions:NSPointerFunctionsWeakMemory];
523     }
524
525     RLMRealmNotificationToken *token = [[RLMRealmNotificationToken alloc] init];
526     token.realm = self;
527     token.block = block;
528     [_notificationHandlers addObject:token];
529     return token;
530 }
531
532 - (void)sendNotifications:(RLMNotification)notification {
533     NSAssert(!_realm->config().immutable(), @"Read-only realms do not have notifications");
534     if (_sendingNotifications) {
535         return;
536     }
537     NSUInteger count = _notificationHandlers.count;
538     if (count == 0) {
539         return;
540     }
541
542     _sendingNotifications = true;
543     auto cleanup = realm::util::make_scope_exit([&]() noexcept {
544         _sendingNotifications = false;
545     });
546
547     // call this realm's notification blocks
548     if (count == 1) {
549         if (auto block = [_notificationHandlers.anyObject block]) {
550             block(notification, self);
551         }
552     }
553     else {
554         for (RLMRealmNotificationToken *token in _notificationHandlers.allObjects) {
555             if (auto block = token.block) {
556                 block(notification, self);
557             }
558         }
559     }
560 }
561
562 - (RLMRealmConfiguration *)configuration {
563     RLMRealmConfiguration *configuration = [[RLMRealmConfiguration alloc] init];
564     configuration.config = _realm->config();
565     configuration.dynamic = _dynamic;
566     configuration.customSchema = _schema;
567     return configuration;
568 }
569
570 - (void)beginWriteTransaction {
571     try {
572         _realm->begin_transaction();
573     }
574     catch (std::exception &ex) {
575         @throw RLMException(ex);
576     }
577 }
578
579 - (void)commitWriteTransaction {
580     [self commitWriteTransaction:nil];
581 }
582
583 - (BOOL)commitWriteTransaction:(NSError **)outError {
584     try {
585         _realm->commit_transaction();
586         return YES;
587     }
588     catch (...) {
589         RLMRealmTranslateException(outError);
590         return NO;
591     }
592 }
593
594 - (BOOL)commitWriteTransactionWithoutNotifying:(NSArray<RLMNotificationToken *> *)tokens error:(NSError **)error {
595     for (RLMNotificationToken *token in tokens) {
596         if (token.realm != self) {
597             @throw RLMException(@"Incorrect Realm: only notifications for the Realm being modified can be skipped.");
598         }
599         [token suppressNextNotification];
600     }
601
602     try {
603         _realm->commit_transaction();
604         return YES;
605     }
606     catch (...) {
607         RLMRealmTranslateException(error);
608         return NO;
609     }
610 }
611
612 - (void)transactionWithBlock:(void(^)(void))block {
613     [self transactionWithBlock:block error:nil];
614 }
615
616 - (BOOL)transactionWithBlock:(void(^)(void))block error:(NSError **)outError {
617     [self beginWriteTransaction];
618     block();
619     if (_realm->is_in_transaction()) {
620         return [self commitWriteTransaction:outError];
621     }
622     return YES;
623 }
624
625 - (void)cancelWriteTransaction {
626     try {
627         _realm->cancel_transaction();
628     }
629     catch (std::exception &ex) {
630         @throw RLMException(ex);
631     }
632 }
633
634 - (void)invalidate {
635     if (_realm->is_in_transaction()) {
636         NSLog(@"WARNING: An RLMRealm instance was invalidated during a write "
637               "transaction and all pending changes have been rolled back.");
638     }
639
640     [self detachAllEnumerators];
641
642     for (auto& objectInfo : _info) {
643         for (RLMObservationInfo *info : objectInfo.second.observedObjects) {
644             info->willChange(RLMInvalidatedKey);
645         }
646     }
647
648     _realm->invalidate();
649
650     for (auto& objectInfo : _info) {
651         for (RLMObservationInfo *info : objectInfo.second.observedObjects) {
652             info->didChange(RLMInvalidatedKey);
653         }
654         objectInfo.second.releaseTable();
655     }
656 }
657
658 - (nullable id)resolveThreadSafeReference:(RLMThreadSafeReference *)reference {
659     return [reference resolveReferenceInRealm:self];
660 }
661
662 /**
663  Replaces all string columns in this Realm with a string enumeration column and compacts the
664  database file.
665
666  Cannot be called from a write transaction.
667
668  Compaction will not occur if other `RLMRealm` instances exist.
669
670  While compaction is in progress, attempts by other threads or processes to open the database will
671  wait.
672
673  Be warned that resource requirements for compaction is proportional to the amount of live data in
674  the database.
675
676  Compaction works by writing the database contents to a temporary database file and then replacing
677  the database with the temporary one. The name of the temporary file is formed by appending
678  `.tmp_compaction_space` to the name of the database.
679
680  @return YES if the compaction succeeded.
681  */
682 - (BOOL)compact {
683     // compact() automatically ends the read transaction, but we need to clean
684     // up cached state and send invalidated notifications when that happens, so
685     // explicitly end it first unless we're in a write transaction (in which
686     // case compact() will throw an exception)
687     if (!_realm->is_in_transaction()) {
688         [self invalidate];
689     }
690
691     try {
692         return _realm->compact();
693     }
694     catch (std::exception const& ex) {
695         @throw RLMException(ex);
696     }
697 }
698
699 - (void)dealloc {
700     if (_realm) {
701         if (_realm->is_in_transaction()) {
702             [self cancelWriteTransaction];
703             NSLog(@"WARNING: An RLMRealm instance was deallocated during a write transaction and all "
704                   "pending changes have been rolled back. Make sure to retain a reference to the "
705                   "RLMRealm for the duration of the write transaction.");
706         }
707     }
708 }
709
710 - (BOOL)refresh {
711     return _realm->refresh();
712 }
713
714 - (void)addObject:(__unsafe_unretained RLMObject *const)object {
715     RLMAddObjectToRealm(object, self, false);
716 }
717
718 - (void)addObjects:(id<NSFastEnumeration>)objects {
719     for (RLMObject *obj in objects) {
720         if (![obj isKindOfClass:RLMObjectBase.class]) {
721             @throw RLMException(@"Cannot insert objects of type %@ with addObjects:. Only RLMObjects are supported.",
722                                 NSStringFromClass(obj.class));
723         }
724         [self addObject:obj];
725     }
726 }
727
728 - (void)addOrUpdateObject:(RLMObject *)object {
729     // verify primary key
730     if (!object.objectSchema.primaryKeyProperty) {
731         @throw RLMException(@"'%@' does not have a primary key and can not be updated", object.objectSchema.className);
732     }
733
734     RLMAddObjectToRealm(object, self, true);
735 }
736
737 - (void)addOrUpdateObjects:(id<NSFastEnumeration>)objects {
738     for (RLMObject *obj in objects) {
739         if (![obj isKindOfClass:RLMObjectBase.class]) {
740             @throw RLMException(@"Cannot add or update objects of type %@ with addOrUpdateObjects:. Only RLMObjects are"
741                                 " supported.",
742                                 NSStringFromClass(obj.class));
743         }
744         [self addOrUpdateObject:obj];
745     }
746 }
747
748 - (void)deleteObject:(RLMObject *)object {
749     RLMDeleteObjectFromRealm(object, self);
750 }
751
752 - (void)deleteObjects:(id<NSFastEnumeration>)objects {
753     id idObjects = objects;
754     if ([idObjects respondsToSelector:@selector(realm)]
755         && [idObjects respondsToSelector:@selector(deleteObjectsFromRealm)]) {
756         if (self != (RLMRealm *)[idObjects realm]) {
757             @throw RLMException(@"Can only delete objects from the Realm they belong to.");
758         }
759         [idObjects deleteObjectsFromRealm];
760         return;
761     }
762     if (auto array = RLMDynamicCast<RLMArray>(objects)) {
763         if (array.type != RLMPropertyTypeObject) {
764             @throw RLMException(@"Cannot delete objects from RLMArray<%@>: only RLMObjects can be deleted.",
765                                 RLMTypeToString(array.type));
766         }
767     }
768     for (RLMObject *obj in objects) {
769         if (![obj isKindOfClass:RLMObjectBase.class]) {
770             @throw RLMException(@"Cannot delete objects of type %@ with deleteObjects:. Only RLMObjects can be deleted.",
771                                 NSStringFromClass(obj.class));
772         }
773         RLMDeleteObjectFromRealm(obj, self);
774     }
775 }
776
777 - (void)deleteAllObjects {
778     RLMDeleteAllObjectsFromRealm(self);
779 }
780
781 - (RLMResults *)allObjects:(NSString *)objectClassName {
782     return RLMGetObjects(self, objectClassName, nil);
783 }
784
785 - (RLMResults *)objects:(NSString *)objectClassName where:(NSString *)predicateFormat, ... {
786     va_list args;
787     va_start(args, predicateFormat);
788     RLMResults *results = [self objects:objectClassName where:predicateFormat args:args];
789     va_end(args);
790     return results;
791 }
792
793 - (RLMResults *)objects:(NSString *)objectClassName where:(NSString *)predicateFormat args:(va_list)args {
794     return [self objects:objectClassName withPredicate:[NSPredicate predicateWithFormat:predicateFormat arguments:args]];
795 }
796
797 - (RLMResults *)objects:(NSString *)objectClassName withPredicate:(NSPredicate *)predicate {
798     return RLMGetObjects(self, objectClassName, predicate);
799 }
800
801 - (RLMObject *)objectWithClassName:(NSString *)className forPrimaryKey:(id)primaryKey {
802     return RLMGetObject(self, className, primaryKey);
803 }
804
805 + (uint64_t)schemaVersionAtURL:(NSURL *)fileURL encryptionKey:(NSData *)key error:(NSError **)error {
806     RLMRealmConfiguration *config = [[RLMRealmConfiguration alloc] init];
807     try {
808         config.fileURL = fileURL;
809         config.encryptionKey = RLMRealmValidatedEncryptionKey(key);
810
811         uint64_t version = Realm::get_schema_version(config.config);
812         if (version == realm::ObjectStore::NotVersioned) {
813             RLMSetErrorOrThrow([NSError errorWithDomain:RLMErrorDomain code:RLMErrorFail userInfo:@{NSLocalizedDescriptionKey:@"Cannot open an uninitialized realm in read-only mode"}], error);
814         }
815         return version;
816     }
817     catch (...) {
818         translateSharedGroupOpenException(config, error);
819         return RLMNotVersioned;
820     }
821 }
822
823 + (BOOL)performMigrationForConfiguration:(RLMRealmConfiguration *)configuration error:(NSError **)error {
824     if (RLMGetAnyCachedRealmForPath(configuration.config.path)) {
825         @throw RLMException(@"Cannot migrate Realms that are already open.");
826     }
827
828     NSError *localError; // Prevents autorelease
829     BOOL success;
830     @autoreleasepool {
831         success = [RLMRealm realmWithConfiguration:configuration error:&localError] != nil;
832     }
833     if (!success && error) {
834         *error = localError; // Must set outside pool otherwise will free anyway
835     }
836     return success;
837 }
838
839 - (RLMObject *)createObject:(NSString *)className withValue:(id)value {
840     return (RLMObject *)RLMCreateObjectInRealmWithValue(self, className, value, false);
841 }
842
843 - (BOOL)writeCopyToURL:(NSURL *)fileURL encryptionKey:(NSData *)key error:(NSError **)error {
844     key = RLMRealmValidatedEncryptionKey(key);
845     NSString *path = fileURL.path;
846
847     try {
848         _realm->write_copy(path.UTF8String, {static_cast<const char *>(key.bytes), key.length});
849         return YES;
850     }
851     catch (...) {
852         __autoreleasing NSError *dummyError;
853         if (!error) {
854             error = &dummyError;
855         }
856         RLMRealmTranslateException(error);
857         return NO;
858     }
859
860     return NO;
861 }
862
863 - (void)registerEnumerator:(RLMFastEnumerator *)enumerator {
864     if (!_collectionEnumerators) {
865         _collectionEnumerators = [NSHashTable hashTableWithOptions:NSPointerFunctionsWeakMemory];
866     }
867     [_collectionEnumerators addObject:enumerator];
868 }
869
870 - (void)unregisterEnumerator:(RLMFastEnumerator *)enumerator {
871     [_collectionEnumerators removeObject:enumerator];
872 }
873
874 - (void)detachAllEnumerators {
875     for (RLMFastEnumerator *enumerator in _collectionEnumerators) {
876         [enumerator detach];
877     }
878     _collectionEnumerators = nil;
879 }
880
881 @end