added iOS source code
[wl-app.git] / iOS / Pods / Realm / Realm / ObjectStore / src / shared_realm.cpp
1 ////////////////////////////////////////////////////////////////////////////
2 //
3 // Copyright 2015 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 #include "shared_realm.hpp"
20
21 #include "impl/collection_notifier.hpp"
22 #include "impl/realm_coordinator.hpp"
23 #include "impl/transact_log_handler.hpp"
24
25 #include "binding_context.hpp"
26 #include "list.hpp"
27 #include "object.hpp"
28 #include "object_schema.hpp"
29 #include "object_store.hpp"
30 #include "results.hpp"
31 #include "schema.hpp"
32 #include "thread_safe_reference.hpp"
33
34
35 #include <realm/history.hpp>
36 #include <realm/util/scope_exit.hpp>
37
38 #if REALM_ENABLE_SYNC
39 #include "sync/impl/sync_file.hpp"
40 #include "sync/sync_manager.hpp"
41 #include <realm/sync/history.hpp>
42 #endif
43
44 using namespace realm;
45 using namespace realm::_impl;
46
47 Realm::Realm(Config config, std::shared_ptr<_impl::RealmCoordinator> coordinator)
48 : m_config(std::move(config))
49 , m_execution_context(m_config.execution_context)
50 {
51     open_with_config(m_config, m_history, m_shared_group, m_read_only_group, this);
52
53     if (m_read_only_group) {
54         m_group = m_read_only_group.get();
55         m_schema_version = ObjectStore::get_schema_version(*m_group);
56         m_schema = ObjectStore::schema_from_group(*m_group);
57     }
58     else if (!coordinator || !coordinator->get_cached_schema(m_schema, m_schema_version, m_schema_transaction_version)) {
59         if (m_config.should_compact_on_launch_function) {
60             size_t free_space = -1;
61             size_t used_space = -1;
62             // getting stats requires committing a write transaction beforehand.
63             Group* group = nullptr;
64             if (m_shared_group->try_begin_write(group)) {
65                 m_shared_group->commit();
66                 m_shared_group->get_stats(free_space, used_space);
67                 if (m_config.should_compact_on_launch_function(free_space + used_space, used_space))
68                     compact();
69             }
70         }
71         read_group();
72         if (coordinator)
73             coordinator->cache_schema(m_schema, m_schema_version, m_schema_transaction_version);
74         m_shared_group->end_read();
75         m_group = nullptr;
76     }
77
78     m_coordinator = std::move(coordinator);
79 }
80
81 REALM_NOINLINE static void translate_file_exception(StringData path, bool immutable=false)
82 {
83     try {
84         throw;
85     }
86     catch (util::File::PermissionDenied const& ex) {
87         throw RealmFileException(RealmFileException::Kind::PermissionDenied, ex.get_path(),
88                                  util::format("Unable to open a realm at path '%1'. Please use a path where your app has %2 permissions.",
89                                               ex.get_path(), immutable ? "read" : "read-write"),
90                                  ex.what());
91     }
92     catch (util::File::Exists const& ex) {
93         throw RealmFileException(RealmFileException::Kind::Exists, ex.get_path(),
94                                  util::format("File at path '%1' already exists.", ex.get_path()),
95                                  ex.what());
96     }
97     catch (util::File::NotFound const& ex) {
98         throw RealmFileException(RealmFileException::Kind::NotFound, ex.get_path(),
99                                  util::format("Directory at path '%1' does not exist.", ex.get_path()), ex.what());
100     }
101     catch (util::File::AccessError const& ex) {
102         // Errors for `open()` include the path, but other errors don't. We
103         // don't want two copies of the path in the error, so strip it out if it
104         // appears, and then include it in our prefix.
105         std::string underlying = ex.what();
106         RealmFileException::Kind error_kind = RealmFileException::Kind::AccessError;
107         // FIXME: Replace this with a proper specific exception type once Core adds support for it.
108         if (underlying == "Bad or incompatible history type")
109             error_kind = RealmFileException::Kind::BadHistoryError;
110         auto pos = underlying.find(ex.get_path());
111         if (pos != std::string::npos && pos > 0) {
112             // One extra char at each end for the quotes
113             underlying.replace(pos - 1, ex.get_path().size() + 2, "");
114         }
115         throw RealmFileException(error_kind, ex.get_path(),
116                                  util::format("Unable to open a realm at path '%1': %2.", ex.get_path(), underlying), ex.what());
117     }
118     catch (IncompatibleLockFile const& ex) {
119         throw RealmFileException(RealmFileException::Kind::IncompatibleLockFile, path,
120                                  "Realm file is currently open in another process "
121                                  "which cannot share access with this process. "
122                                  "All processes sharing a single file must be the same architecture.",
123                                  ex.what());
124     }
125     catch (FileFormatUpgradeRequired const& ex) {
126         throw RealmFileException(RealmFileException::Kind::FormatUpgradeRequired, path,
127                                  "The Realm file format must be allowed to be upgraded "
128                                  "in order to proceed.",
129                                  ex.what());
130     }
131 }
132
133 #if REALM_ENABLE_SYNC
134 static bool is_nonupgradable_history(IncompatibleHistories const& ex)
135 {
136     // FIXME: Replace this with a proper specific exception type once Core adds support for it.
137     return ex.what() == std::string("Incompatible histories. Nonupgradable history schema");
138 }
139 #endif
140
141 void Realm::open_with_config(const Config& config,
142                              std::unique_ptr<Replication>& history,
143                              std::unique_ptr<SharedGroup>& shared_group,
144                              std::unique_ptr<Group>& read_only_group,
145                              Realm* realm)
146 {
147     bool server_synchronization_mode = bool(config.sync_config) || config.force_sync_history;
148     try {
149         if (config.immutable()) {
150             if (config.realm_data.is_null()) {
151                 read_only_group = std::make_unique<Group>(config.path, config.encryption_key.data(), Group::mode_ReadOnly);
152             }
153             else {
154                 // Create in-memory read-only realm from existing buffer (without taking ownership of the buffer)
155                 read_only_group = std::make_unique<Group>(config.realm_data, false);
156             }
157         }
158         else {
159             if (server_synchronization_mode) {
160 #if REALM_ENABLE_SYNC
161                 history = realm::sync::make_client_history(config.path);
162 #else
163                 REALM_TERMINATE("Realm was not built with sync enabled");
164 #endif
165             }
166             else {
167                 history = realm::make_in_realm_history(config.path);
168             }
169
170             SharedGroupOptions options;
171             options.durability = config.in_memory ? SharedGroupOptions::Durability::MemOnly :
172                                                     SharedGroupOptions::Durability::Full;
173             options.encryption_key = config.encryption_key.data();
174             options.allow_file_format_upgrade = !config.disable_format_upgrade &&
175                                                 config.schema_mode != SchemaMode::ResetFile;
176             options.upgrade_callback = [&](int from_version, int to_version) {
177                 if (realm) {
178                     realm->upgrade_initial_version = from_version;
179                     realm->upgrade_final_version = to_version;
180                 }
181             };
182             shared_group = std::make_unique<SharedGroup>(*history, options);
183         }
184     }
185     catch (realm::FileFormatUpgradeRequired const&) {
186         if (config.schema_mode != SchemaMode::ResetFile) {
187             translate_file_exception(config.path, config.immutable());
188         }
189         util::File::remove(config.path);
190         open_with_config(config, history, shared_group, read_only_group, realm);
191     }
192 #if REALM_ENABLE_SYNC
193     catch (IncompatibleHistories const& ex) {
194         if (!server_synchronization_mode || !is_nonupgradable_history(ex))
195             translate_file_exception(config.path, config.immutable()); // Throws
196
197         // Move the Realm file into the recovery directory.
198         std::string recovery_directory = SyncManager::shared().recovery_directory_path();
199         std::string new_realm_path = util::reserve_unique_file_name(recovery_directory, "synced-realm-XXXXXXX");
200         util::File::move(config.path, new_realm_path);
201
202         const char* message = "The local copy of this synced Realm was created with an incompatible version of "
203                               "Realm. It has been moved aside, and the Realm will be re-downloaded the next time it "
204                               "is opened. You should write a handler for this error that uses the provided "
205                               "configuration to open the old Realm in read-only mode to recover any pending changes "
206                               "and then remove the Realm file.";
207         throw RealmFileException(RealmFileException::Kind::IncompatibleSyncedRealm, std::move(new_realm_path),
208                                  message, ex.what());
209     }
210 #endif // REALM_ENABLE_SYNC
211     catch (...) {
212         translate_file_exception(config.path, config.immutable());
213     }
214 }
215
216 Realm::~Realm()
217 {
218     if (m_coordinator) {
219         m_coordinator->unregister_realm(this);
220     }
221 }
222
223 Group& Realm::read_group()
224 {
225     verify_open();
226
227     if (!m_group)
228         begin_read(VersionID{});
229     return *m_group;
230 }
231
232 void Realm::Internal::begin_read(Realm& realm, VersionID version_id)
233 {
234     realm.begin_read(version_id);
235 }
236
237 void Realm::begin_read(VersionID version_id)
238 {
239     REALM_ASSERT(!m_group);
240     m_group = &const_cast<Group&>(m_shared_group->begin_read(version_id));
241     add_schema_change_handler();
242     read_schema_from_group_if_needed();
243 }
244
245 SharedRealm Realm::get_shared_realm(Config config)
246 {
247     auto coordinator = RealmCoordinator::get_coordinator(config.path);
248     return coordinator->get_realm(std::move(config));
249 }
250
251 void Realm::set_schema(Schema const& reference, Schema schema)
252 {
253     m_dynamic_schema = false;
254     schema.copy_table_columns_from(reference);
255     m_schema = std::move(schema);
256     notify_schema_changed();
257 }
258
259 void Realm::read_schema_from_group_if_needed()
260 {
261     REALM_ASSERT(!m_read_only_group);
262
263     Group& group = read_group();
264     auto current_version = m_shared_group->get_version_of_current_transaction().version;
265     if (m_schema_transaction_version == current_version)
266         return;
267
268     m_schema_transaction_version = current_version;
269     m_schema_version = ObjectStore::get_schema_version(group);
270     auto schema = ObjectStore::schema_from_group(group);
271     if (m_coordinator)
272         m_coordinator->cache_schema(schema, m_schema_version,
273                                     m_schema_transaction_version);
274
275     if (m_dynamic_schema) {
276         if (m_schema == schema) {
277             // The structure of the schema hasn't changed. Bring the table column indices up to date.
278             m_schema.copy_table_columns_from(schema);
279         }
280         else {
281             // The structure of the schema has changed, so replace our copy of the schema.
282             // FIXME: This invalidates any pointers to the object schemas within the schema vector,
283             // which will cause problems for anyone that caches such a pointer.
284             m_schema = std::move(schema);
285         }
286     }
287     else {
288         ObjectStore::verify_valid_external_changes(m_schema.compare(schema));
289         m_schema.copy_table_columns_from(schema);
290     }
291     notify_schema_changed();
292 }
293
294 bool Realm::reset_file(Schema& schema, std::vector<SchemaChange>& required_changes)
295 {
296     // FIXME: this does not work if multiple processes try to open the file at
297     // the same time, or even multiple threads if there is not any external
298     // synchronization. The latter is probably fixable, but making it
299     // multi-process-safe requires some sort of multi-process exclusive lock
300     m_group = nullptr;
301     m_shared_group = nullptr;
302     m_history = nullptr;
303     util::File::remove(m_config.path);
304
305     open_with_config(m_config, m_history, m_shared_group, m_read_only_group, this);
306     m_schema = ObjectStore::schema_from_group(read_group());
307     m_schema_version = ObjectStore::get_schema_version(read_group());
308     required_changes = m_schema.compare(schema);
309     m_coordinator->clear_schema_cache_and_set_schema_version(m_schema_version);
310     return false;
311 }
312
313 bool Realm::schema_change_needs_write_transaction(Schema& schema,
314                                                   std::vector<SchemaChange>& changes,
315                                                   uint64_t version)
316 {
317     if (version == m_schema_version && changes.empty())
318         return false;
319
320     switch (m_config.schema_mode) {
321         case SchemaMode::Automatic:
322             if (version < m_schema_version && m_schema_version != ObjectStore::NotVersioned)
323                 throw InvalidSchemaVersionException(m_schema_version, version);
324             return true;
325
326         case SchemaMode::Immutable:
327             if (version != m_schema_version)
328                 throw InvalidSchemaVersionException(m_schema_version, version);
329             REALM_FALLTHROUGH;
330         case SchemaMode::ReadOnlyAlternative:
331             ObjectStore::verify_compatible_for_immutable_and_readonly(changes);
332             return false;
333
334         case SchemaMode::ResetFile:
335             if (m_schema_version == ObjectStore::NotVersioned)
336                 return true;
337             if (m_schema_version == version && !ObjectStore::needs_migration(changes))
338                 return true;
339             reset_file(schema, changes);
340             return true;
341
342         case SchemaMode::Additive: {
343             bool will_apply_index_changes = version > m_schema_version;
344             if (ObjectStore::verify_valid_additive_changes(changes, will_apply_index_changes))
345                 return true;
346             return version != m_schema_version;
347         }
348
349         case SchemaMode::Manual:
350             if (version < m_schema_version && m_schema_version != ObjectStore::NotVersioned)
351                 throw InvalidSchemaVersionException(m_schema_version, version);
352             if (version == m_schema_version) {
353                 ObjectStore::verify_no_changes_required(changes);
354                 REALM_UNREACHABLE(); // changes is non-empty so above line always throws
355             }
356             return true;
357     }
358     REALM_COMPILER_HINT_UNREACHABLE();
359 }
360
361 Schema Realm::get_full_schema()
362 {
363     if (!m_read_only_group)
364         refresh();
365
366     // If the user hasn't specified a schema previously then m_schema is always
367     // the full schema
368     if (m_dynamic_schema)
369         return m_schema;
370
371     // Otherwise we may have a subset of the file's schema, so we need to get
372     // the complete thing to calculate what changes to make
373     if (m_read_only_group)
374         return ObjectStore::schema_from_group(read_group());
375
376     Schema actual_schema;
377     uint64_t actual_version;
378     uint64_t transaction = -1;
379     bool got_cached = m_coordinator->get_cached_schema(actual_schema, actual_version, transaction);
380     if (!got_cached || transaction != m_shared_group->get_version_of_current_transaction().version)
381         return ObjectStore::schema_from_group(read_group());
382     return actual_schema;
383 }
384
385 void Realm::set_schema_subset(Schema schema)
386 {
387     REALM_ASSERT(m_dynamic_schema);
388     REALM_ASSERT(m_schema_version != ObjectStore::NotVersioned);
389
390     std::vector<SchemaChange> changes = m_schema.compare(schema);
391     switch (m_config.schema_mode) {
392         case SchemaMode::Automatic:
393         case SchemaMode::ResetFile:
394             ObjectStore::verify_no_migration_required(changes);
395             break;
396
397         case SchemaMode::Immutable:
398         case SchemaMode::ReadOnlyAlternative:
399             ObjectStore::verify_compatible_for_immutable_and_readonly(changes);
400             break;
401
402         case SchemaMode::Additive:
403             ObjectStore::verify_valid_additive_changes(changes);
404             break;
405
406         case SchemaMode::Manual:
407             ObjectStore::verify_no_changes_required(changes);
408             break;
409     }
410
411     set_schema(m_schema, std::move(schema));
412 }
413
414 void Realm::update_schema(Schema schema, uint64_t version, MigrationFunction migration_function,
415                           DataInitializationFunction initialization_function, bool in_transaction)
416 {
417     schema.validate();
418
419     Schema actual_schema = get_full_schema();
420     std::vector<SchemaChange> required_changes = actual_schema.compare(schema);
421
422     if (!schema_change_needs_write_transaction(schema, required_changes, version)) {
423         set_schema(actual_schema, std::move(schema));
424         return;
425     }
426     // Either the schema version has changed or we need to do non-migration changes
427
428     if (!in_transaction) {
429         transaction::begin_without_validation(*m_shared_group);
430
431         // Beginning the write transaction may have advanced the version and left
432         // us with nothing to do if someone else initialized the schema on disk
433         if (m_new_schema) {
434             actual_schema = *m_new_schema;
435             required_changes = actual_schema.compare(schema);
436             if (!schema_change_needs_write_transaction(schema, required_changes, version)) {
437                 cancel_transaction();
438                 cache_new_schema();
439                 set_schema(actual_schema, std::move(schema));
440                 return;
441             }
442         }
443         cache_new_schema();
444     }
445
446     // Cancel the write transaction if we exit this function before committing it
447     auto cleanup = util::make_scope_exit([&]() noexcept {
448         // When in_transaction is true, caller is responsible to cancel the transaction.
449         if (!in_transaction && is_in_transaction())
450             cancel_transaction();
451     });
452
453     uint64_t old_schema_version = m_schema_version;
454     bool additive = m_config.schema_mode == SchemaMode::Additive;
455     if (migration_function && !additive) {
456         auto wrapper = [&] {
457             SharedRealm old_realm(new Realm(m_config, nullptr));
458             // Need to open in read-write mode so that it uses a SharedGroup, but
459             // users shouldn't actually be able to write via the old realm
460             old_realm->m_config.schema_mode = SchemaMode::Immutable;
461             migration_function(old_realm, shared_from_this(), m_schema);
462         };
463
464         // migration function needs to see the target schema on the "new" Realm
465         std::swap(m_schema, schema);
466         std::swap(m_schema_version, version);
467         m_in_migration = true;
468         auto restore = util::make_scope_exit([&]() noexcept {
469             std::swap(m_schema, schema);
470             std::swap(m_schema_version, version);
471             m_in_migration = false;
472         });
473
474         ObjectStore::apply_schema_changes(read_group(), version, m_schema, m_schema_version,
475                                           m_config.schema_mode, required_changes, wrapper);
476     }
477     else {
478         ObjectStore::apply_schema_changes(read_group(), m_schema_version, schema, version,
479                                           m_config.schema_mode, required_changes);
480         REALM_ASSERT_DEBUG(additive || (required_changes = ObjectStore::schema_from_group(read_group()).compare(schema)).empty());
481     }
482
483     if (initialization_function && old_schema_version == ObjectStore::NotVersioned) {
484         // Initialization function needs to see the latest schema
485         uint64_t temp_version = ObjectStore::get_schema_version(read_group());
486         std::swap(m_schema, schema);
487         std::swap(m_schema_version, temp_version);
488         auto restore = util::make_scope_exit([&]() noexcept {
489             std::swap(m_schema, schema);
490             std::swap(m_schema_version, temp_version);
491         });
492         initialization_function(shared_from_this());
493     }
494
495     if (!in_transaction) {
496         commit_transaction();
497     }
498
499     m_schema = std::move(schema);
500     m_schema_version = ObjectStore::get_schema_version(read_group());
501     m_dynamic_schema = false;
502     m_coordinator->clear_schema_cache_and_set_schema_version(version);
503     notify_schema_changed();
504 }
505
506 void Realm::add_schema_change_handler()
507 {
508     if (m_config.immutable())
509         return;
510     m_group->set_schema_change_notification_handler([&] {
511         m_new_schema = ObjectStore::schema_from_group(read_group());
512         m_schema_version = ObjectStore::get_schema_version(read_group());
513         if (m_dynamic_schema) {
514             // FIXME: This invalidates any pointers to the object schemas within the schema vector,
515             // which will cause problems for anyone that caches such a pointer.
516             m_schema = *m_new_schema;
517         }
518         else
519             m_schema.copy_table_columns_from(*m_new_schema);
520
521         notify_schema_changed();
522     });
523 }
524
525 void Realm::cache_new_schema()
526 {
527     if (!m_shared_group)
528         return;
529
530     auto new_version = m_shared_group->get_version_of_current_transaction().version;
531     if (m_coordinator) {
532         if (m_new_schema)
533             m_coordinator->cache_schema(std::move(*m_new_schema), m_schema_version, new_version);
534         else
535             m_coordinator->advance_schema_cache(m_schema_transaction_version, new_version);
536     }
537     m_schema_transaction_version = new_version;
538     m_new_schema = util::none;
539 }
540
541 void Realm::notify_schema_changed() {
542     if (m_binding_context) {
543         m_binding_context->schema_did_change(m_schema);
544     }
545 }
546
547 static void check_read_write(Realm *realm)
548 {
549     if (realm->config().immutable()) {
550         throw InvalidTransactionException("Can't perform transactions on read-only Realms.");
551     }
552 }
553
554 static void check_write(Realm* realm)
555 {
556     if (realm->config().immutable() || realm->config().read_only_alternative()) {
557         throw InvalidTransactionException("Can't perform transactions on read-only Realms.");
558     }
559 }
560
561 void Realm::verify_thread() const
562 {
563     if (!m_execution_context.contains<std::thread::id>())
564         return;
565
566     auto thread_id = m_execution_context.get<std::thread::id>();
567     if (thread_id != std::this_thread::get_id())
568         throw IncorrectThreadException();
569 }
570
571 void Realm::verify_in_write() const
572 {
573     if (!is_in_transaction()) {
574         throw InvalidTransactionException("Cannot modify managed objects outside of a write transaction.");
575     }
576 }
577
578 void Realm::verify_open() const
579 {
580     if (is_closed()) {
581         throw ClosedRealmException();
582     }
583 }
584
585 bool Realm::is_in_transaction() const noexcept
586 {
587     if (!m_shared_group) {
588         return false;
589     }
590     return m_shared_group->get_transact_stage() == SharedGroup::transact_Writing;
591 }
592
593 void Realm::begin_transaction()
594 {
595     check_write(this);
596     verify_thread();
597
598     if (is_in_transaction()) {
599         throw InvalidTransactionException("The Realm is already in a write transaction");
600     }
601
602     // Any of the callbacks to user code below could drop the last remaining
603     // strong reference to `this`
604     auto retain_self = shared_from_this();
605
606     // If we're already in the middle of sending notifications, just begin the
607     // write transaction without sending more notifications. If this actually
608     // advances the read version this could leave the user in an inconsistent
609     // state, but that's unavoidable.
610     if (m_is_sending_notifications) {
611         _impl::NotifierPackage notifiers;
612         transaction::begin(m_shared_group, m_binding_context.get(), notifiers);
613         return;
614     }
615
616     // make sure we have a read transaction
617     read_group();
618
619     m_is_sending_notifications = true;
620     auto cleanup = util::make_scope_exit([this]() noexcept { m_is_sending_notifications = false; });
621
622     m_coordinator->promote_to_write(*this);
623     cache_new_schema();
624 }
625
626 void Realm::commit_transaction()
627 {
628     check_write(this);
629     verify_thread();
630
631     if (!is_in_transaction()) {
632         throw InvalidTransactionException("Can't commit a non-existing write transaction");
633     }
634
635     m_coordinator->commit_write(*this);
636     cache_new_schema();
637 }
638
639 void Realm::cancel_transaction()
640 {
641     check_write(this);
642     verify_thread();
643
644     if (!is_in_transaction()) {
645         throw InvalidTransactionException("Can't cancel a non-existing write transaction");
646     }
647
648     transaction::cancel(*m_shared_group, m_binding_context.get());
649 }
650
651 void Realm::invalidate()
652 {
653     verify_open();
654     verify_thread();
655     check_read_write(this);
656
657     if (m_is_sending_notifications) {
658         return;
659     }
660
661     if (is_in_transaction()) {
662         cancel_transaction();
663     }
664     if (!m_group) {
665         return;
666     }
667
668     m_shared_group->end_read();
669     m_group = nullptr;
670 }
671
672 bool Realm::compact()
673 {
674     verify_thread();
675
676     if (m_config.immutable() || m_config.read_only_alternative()) {
677         throw InvalidTransactionException("Can't compact a read-only Realm");
678     }
679     if (is_in_transaction()) {
680         throw InvalidTransactionException("Can't compact a Realm within a write transaction");
681     }
682
683     Group& group = read_group();
684     for (auto &object_schema : m_schema) {
685         ObjectStore::table_for_object_type(group, object_schema.name)->optimize();
686     }
687     m_shared_group->end_read();
688     m_group = nullptr;
689
690     return m_shared_group->compact();
691 }
692
693 void Realm::write_copy(StringData path, BinaryData key)
694 {
695     if (key.data() && key.size() != 64) {
696         throw InvalidEncryptionKeyException();
697     }
698     verify_thread();
699     try {
700         read_group().write(path, key.data());
701     }
702     catch (...) {
703         translate_file_exception(path);
704     }
705 }
706
707 OwnedBinaryData Realm::write_copy()
708 {
709     verify_thread();
710     BinaryData buffer = read_group().write_to_mem();
711
712     // Since OwnedBinaryData does not have a constructor directly taking
713     // ownership of BinaryData, we have to do this to avoid copying the buffer
714     return OwnedBinaryData(std::unique_ptr<char[]>((char*)buffer.data()), buffer.size());
715 }
716
717 void Realm::notify()
718 {
719     if (is_closed() || is_in_transaction()) {
720         return;
721     }
722
723     verify_thread();
724
725     // Any of the callbacks to user code below could drop the last remaining
726     // strong reference to `this`
727     auto retain_self = shared_from_this();
728
729     if (m_binding_context) {
730         m_binding_context->before_notify();
731     }
732
733     auto cleanup = util::make_scope_exit([this]() noexcept { m_is_sending_notifications = false; });
734     if (!m_shared_group->has_changed()) {
735         m_is_sending_notifications = true;
736         m_coordinator->process_available_async(*this);
737         return;
738     }
739
740     if (m_binding_context) {
741         m_binding_context->changes_available();
742
743         // changes_available() may have advanced the read version, and if
744         // so we don't need to do anything further
745         if (!m_shared_group->has_changed())
746             return;
747     }
748
749     m_is_sending_notifications = true;
750     if (m_auto_refresh) {
751         if (m_group) {
752             m_coordinator->advance_to_ready(*this);
753             cache_new_schema();
754         }
755         else  {
756             if (m_binding_context) {
757                 m_binding_context->did_change({}, {});
758             }
759             if (!is_closed()) {
760                 m_coordinator->process_available_async(*this);
761             }
762         }
763     }
764 }
765
766 bool Realm::refresh()
767 {
768     verify_thread();
769     check_read_write(this);
770
771     // can't be any new changes if we're in a write transaction
772     if (is_in_transaction()) {
773         return false;
774     }
775     // don't advance if we're already in the process of advancing as that just
776     // makes things needlessly complicated
777     if (m_is_sending_notifications) {
778         return false;
779     }
780
781     // Any of the callbacks to user code below could drop the last remaining
782     // strong reference to `this`
783     auto retain_self = shared_from_this();
784
785     m_is_sending_notifications = true;
786     auto cleanup = util::make_scope_exit([this]() noexcept { m_is_sending_notifications = false; });
787
788     if (m_binding_context) {
789         m_binding_context->before_notify();
790     }
791     if (m_group) {
792         bool version_changed = m_coordinator->advance_to_latest(*this);
793         cache_new_schema();
794         return version_changed;
795     }
796
797     // No current read transaction, so just create a new one
798     read_group();
799     m_coordinator->process_available_async(*this);
800     return true;
801 }
802
803 bool Realm::can_deliver_notifications() const noexcept
804 {
805     if (m_config.immutable()) {
806         return false;
807     }
808
809     if (m_binding_context && !m_binding_context->can_deliver_notifications()) {
810         return false;
811     }
812
813     return true;
814 }
815
816 uint64_t Realm::get_schema_version(const Realm::Config &config)
817 {
818     auto coordinator = RealmCoordinator::get_existing_coordinator(config.path);
819     if (coordinator) {
820         return coordinator->get_schema_version();
821     }
822
823     return ObjectStore::get_schema_version(Realm(config, nullptr).read_group());
824 }
825
826 void Realm::close()
827 {
828     if (m_coordinator) {
829         m_coordinator->unregister_realm(this);
830     }
831
832     m_group = nullptr;
833     m_shared_group = nullptr;
834     m_history = nullptr;
835     m_read_only_group = nullptr;
836     m_binding_context = nullptr;
837     m_coordinator = nullptr;
838 }
839
840 util::Optional<int> Realm::file_format_upgraded_from_version() const
841 {
842     if (upgrade_initial_version != upgrade_final_version) {
843         return upgrade_initial_version;
844     }
845     return util::none;
846 }
847
848 template <typename T>
849 realm::ThreadSafeReference<T> Realm::obtain_thread_safe_reference(T const& value)
850 {
851     verify_thread();
852     if (is_in_transaction()) {
853         throw InvalidTransactionException("Cannot obtain thread safe reference during a write transaction.");
854     }
855     return ThreadSafeReference<T>(value);
856 }
857
858 template ThreadSafeReference<Object> Realm::obtain_thread_safe_reference(Object const& value);
859 template ThreadSafeReference<List> Realm::obtain_thread_safe_reference(List const& value);
860 template ThreadSafeReference<Results> Realm::obtain_thread_safe_reference(Results const& value);
861
862 template <typename T>
863 T Realm::resolve_thread_safe_reference(ThreadSafeReference<T> reference)
864 {
865     verify_thread();
866     if (is_in_transaction()) {
867         throw InvalidTransactionException("Cannot resolve thread safe reference during a write transaction.");
868     }
869     if (reference.is_invalidated()) {
870         throw std::logic_error("Cannot resolve thread safe reference more than once.");
871     }
872     if (!reference.has_same_config(*this)) {
873         throw MismatchedRealmException("Cannot resolve thread safe reference in Realm with different configuration "
874                                        "than the source Realm.");
875     }
876
877     // Any of the callbacks to user code below could drop the last remaining
878     // strong reference to `this`
879     auto retain_self = shared_from_this();
880
881     // Ensure we're on the same version as the reference
882     if (!m_group) {
883         // A read transaction doesn't yet exist, so create at the reference's version
884         begin_read(reference.m_version_id);
885     }
886     else {
887         // A read transaction does exist, but let's make sure that its version matches the reference's
888         auto current_version = m_shared_group->get_version_of_current_transaction();
889         VersionID reference_version(reference.m_version_id);
890
891         if (reference_version == current_version) {
892             return std::move(reference).import_into_realm(shared_from_this());
893         }
894
895         refresh();
896
897         current_version = m_shared_group->get_version_of_current_transaction();
898
899         // If the reference's version is behind, advance it to our version
900         if (reference_version < current_version) {
901             // Duplicate config for uncached Realm so we don't advance the user's Realm
902             Realm::Config config = m_coordinator->get_config();
903             config.cache = false;
904             config.schema = util::none;
905             SharedRealm temporary_realm = m_coordinator->get_realm(config);
906             temporary_realm->begin_read(reference_version);
907
908             // With reference imported, advance temporary Realm to our version
909             T imported_value = std::move(reference).import_into_realm(temporary_realm);
910             transaction::advance(*temporary_realm->m_shared_group, nullptr, current_version);
911             if (!imported_value.is_valid())
912                 return T{};
913             reference = ThreadSafeReference<T>(imported_value);
914         }
915     }
916
917     return std::move(reference).import_into_realm(shared_from_this());
918 }
919
920 template Object Realm::resolve_thread_safe_reference(ThreadSafeReference<Object> reference);
921 template List Realm::resolve_thread_safe_reference(ThreadSafeReference<List> reference);
922 template Results Realm::resolve_thread_safe_reference(ThreadSafeReference<Results> reference);
923
924 MismatchedConfigException::MismatchedConfigException(StringData message, StringData path)
925 : std::logic_error(util::format(message.data(), path)) { }
926
927 MismatchedRealmException::MismatchedRealmException(StringData message)
928 : std::logic_error(message.data()) { }
929
930 // FIXME Those are exposed for Java async queries, mainly because of handover related methods.
931 SharedGroup& RealmFriend::get_shared_group(Realm& realm)
932 {
933     return *realm.m_shared_group;
934 }
935
936 Group& RealmFriend::read_group_to(Realm& realm, VersionID version)
937 {
938     if (realm.m_group && realm.m_shared_group->get_version_of_current_transaction() == version)
939         return *realm.m_group;
940
941     if (realm.m_group)
942         realm.m_shared_group->end_read();
943     realm.begin_read(version);
944     return *realm.m_group;
945 }