1 ////////////////////////////////////////////////////////////////////////////
3 // Copyright 2015 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 #include "shared_realm.hpp"
21 #include "impl/collection_notifier.hpp"
22 #include "impl/realm_coordinator.hpp"
23 #include "impl/transact_log_handler.hpp"
25 #include "binding_context.hpp"
28 #include "object_schema.hpp"
29 #include "object_store.hpp"
30 #include "results.hpp"
32 #include "thread_safe_reference.hpp"
35 #include <realm/history.hpp>
36 #include <realm/util/scope_exit.hpp>
39 #include "sync/impl/sync_file.hpp"
40 #include "sync/sync_manager.hpp"
41 #include <realm/sync/history.hpp>
44 using namespace realm;
45 using namespace realm::_impl;
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)
51 open_with_config(m_config, m_history, m_shared_group, m_read_only_group, this);
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);
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))
73 coordinator->cache_schema(m_schema, m_schema_version, m_schema_transaction_version);
74 m_shared_group->end_read();
78 m_coordinator = std::move(coordinator);
81 REALM_NOINLINE static void translate_file_exception(StringData path, bool immutable=false)
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"),
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()),
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());
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, "");
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());
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.",
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.",
133 #if REALM_ENABLE_SYNC
134 static bool is_nonupgradable_history(IncompatibleHistories const& ex)
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");
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,
147 bool server_synchronization_mode = bool(config.sync_config) || config.force_sync_history;
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);
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);
159 if (server_synchronization_mode) {
160 #if REALM_ENABLE_SYNC
161 history = realm::sync::make_client_history(config.path);
163 REALM_TERMINATE("Realm was not built with sync enabled");
167 history = realm::make_in_realm_history(config.path);
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) {
178 realm->upgrade_initial_version = from_version;
179 realm->upgrade_final_version = to_version;
182 shared_group = std::make_unique<SharedGroup>(*history, options);
185 catch (realm::FileFormatUpgradeRequired const&) {
186 if (config.schema_mode != SchemaMode::ResetFile) {
187 translate_file_exception(config.path, config.immutable());
189 util::File::remove(config.path);
190 open_with_config(config, history, shared_group, read_only_group, realm);
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
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);
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),
210 #endif // REALM_ENABLE_SYNC
212 translate_file_exception(config.path, config.immutable());
219 m_coordinator->unregister_realm(this);
223 Group& Realm::read_group()
228 begin_read(VersionID{});
232 void Realm::Internal::begin_read(Realm& realm, VersionID version_id)
234 realm.begin_read(version_id);
237 void Realm::begin_read(VersionID version_id)
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();
245 SharedRealm Realm::get_shared_realm(Config config)
247 auto coordinator = RealmCoordinator::get_coordinator(config.path);
248 return coordinator->get_realm(std::move(config));
251 void Realm::set_schema(Schema const& reference, Schema schema)
253 m_dynamic_schema = false;
254 schema.copy_table_columns_from(reference);
255 m_schema = std::move(schema);
256 notify_schema_changed();
259 void Realm::read_schema_from_group_if_needed()
261 REALM_ASSERT(!m_read_only_group);
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)
268 m_schema_transaction_version = current_version;
269 m_schema_version = ObjectStore::get_schema_version(group);
270 auto schema = ObjectStore::schema_from_group(group);
272 m_coordinator->cache_schema(schema, m_schema_version,
273 m_schema_transaction_version);
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);
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);
288 ObjectStore::verify_valid_external_changes(m_schema.compare(schema));
289 m_schema.copy_table_columns_from(schema);
291 notify_schema_changed();
294 bool Realm::reset_file(Schema& schema, std::vector<SchemaChange>& required_changes)
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
301 m_shared_group = nullptr;
303 util::File::remove(m_config.path);
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);
313 bool Realm::schema_change_needs_write_transaction(Schema& schema,
314 std::vector<SchemaChange>& changes,
317 if (version == m_schema_version && changes.empty())
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);
326 case SchemaMode::Immutable:
327 if (version != m_schema_version)
328 throw InvalidSchemaVersionException(m_schema_version, version);
330 case SchemaMode::ReadOnlyAlternative:
331 ObjectStore::verify_compatible_for_immutable_and_readonly(changes);
334 case SchemaMode::ResetFile:
335 if (m_schema_version == ObjectStore::NotVersioned)
337 if (m_schema_version == version && !ObjectStore::needs_migration(changes))
339 reset_file(schema, changes);
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))
346 return version != m_schema_version;
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
358 REALM_COMPILER_HINT_UNREACHABLE();
361 Schema Realm::get_full_schema()
363 if (!m_read_only_group)
366 // If the user hasn't specified a schema previously then m_schema is always
368 if (m_dynamic_schema)
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());
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;
385 void Realm::set_schema_subset(Schema schema)
387 REALM_ASSERT(m_dynamic_schema);
388 REALM_ASSERT(m_schema_version != ObjectStore::NotVersioned);
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);
397 case SchemaMode::Immutable:
398 case SchemaMode::ReadOnlyAlternative:
399 ObjectStore::verify_compatible_for_immutable_and_readonly(changes);
402 case SchemaMode::Additive:
403 ObjectStore::verify_valid_additive_changes(changes);
406 case SchemaMode::Manual:
407 ObjectStore::verify_no_changes_required(changes);
411 set_schema(m_schema, std::move(schema));
414 void Realm::update_schema(Schema schema, uint64_t version, MigrationFunction migration_function,
415 DataInitializationFunction initialization_function, bool in_transaction)
419 Schema actual_schema = get_full_schema();
420 std::vector<SchemaChange> required_changes = actual_schema.compare(schema);
422 if (!schema_change_needs_write_transaction(schema, required_changes, version)) {
423 set_schema(actual_schema, std::move(schema));
426 // Either the schema version has changed or we need to do non-migration changes
428 if (!in_transaction) {
429 transaction::begin_without_validation(*m_shared_group);
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
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();
439 set_schema(actual_schema, std::move(schema));
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();
453 uint64_t old_schema_version = m_schema_version;
454 bool additive = m_config.schema_mode == SchemaMode::Additive;
455 if (migration_function && !additive) {
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);
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;
474 ObjectStore::apply_schema_changes(read_group(), version, m_schema, m_schema_version,
475 m_config.schema_mode, required_changes, wrapper);
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());
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);
492 initialization_function(shared_from_this());
495 if (!in_transaction) {
496 commit_transaction();
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();
506 void Realm::add_schema_change_handler()
508 if (m_config.immutable())
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;
519 m_schema.copy_table_columns_from(*m_new_schema);
521 notify_schema_changed();
525 void Realm::cache_new_schema()
530 auto new_version = m_shared_group->get_version_of_current_transaction().version;
533 m_coordinator->cache_schema(std::move(*m_new_schema), m_schema_version, new_version);
535 m_coordinator->advance_schema_cache(m_schema_transaction_version, new_version);
537 m_schema_transaction_version = new_version;
538 m_new_schema = util::none;
541 void Realm::notify_schema_changed() {
542 if (m_binding_context) {
543 m_binding_context->schema_did_change(m_schema);
547 static void check_read_write(Realm *realm)
549 if (realm->config().immutable()) {
550 throw InvalidTransactionException("Can't perform transactions on read-only Realms.");
554 static void check_write(Realm* realm)
556 if (realm->config().immutable() || realm->config().read_only_alternative()) {
557 throw InvalidTransactionException("Can't perform transactions on read-only Realms.");
561 void Realm::verify_thread() const
563 if (!m_execution_context.contains<std::thread::id>())
566 auto thread_id = m_execution_context.get<std::thread::id>();
567 if (thread_id != std::this_thread::get_id())
568 throw IncorrectThreadException();
571 void Realm::verify_in_write() const
573 if (!is_in_transaction()) {
574 throw InvalidTransactionException("Cannot modify managed objects outside of a write transaction.");
578 void Realm::verify_open() const
581 throw ClosedRealmException();
585 bool Realm::is_in_transaction() const noexcept
587 if (!m_shared_group) {
590 return m_shared_group->get_transact_stage() == SharedGroup::transact_Writing;
593 void Realm::begin_transaction()
598 if (is_in_transaction()) {
599 throw InvalidTransactionException("The Realm is already in a write transaction");
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();
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);
616 // make sure we have a read transaction
619 m_is_sending_notifications = true;
620 auto cleanup = util::make_scope_exit([this]() noexcept { m_is_sending_notifications = false; });
622 m_coordinator->promote_to_write(*this);
626 void Realm::commit_transaction()
631 if (!is_in_transaction()) {
632 throw InvalidTransactionException("Can't commit a non-existing write transaction");
635 m_coordinator->commit_write(*this);
639 void Realm::cancel_transaction()
644 if (!is_in_transaction()) {
645 throw InvalidTransactionException("Can't cancel a non-existing write transaction");
648 transaction::cancel(*m_shared_group, m_binding_context.get());
651 void Realm::invalidate()
655 check_read_write(this);
657 if (m_is_sending_notifications) {
661 if (is_in_transaction()) {
662 cancel_transaction();
668 m_shared_group->end_read();
672 bool Realm::compact()
676 if (m_config.immutable() || m_config.read_only_alternative()) {
677 throw InvalidTransactionException("Can't compact a read-only Realm");
679 if (is_in_transaction()) {
680 throw InvalidTransactionException("Can't compact a Realm within a write transaction");
683 Group& group = read_group();
684 for (auto &object_schema : m_schema) {
685 ObjectStore::table_for_object_type(group, object_schema.name)->optimize();
687 m_shared_group->end_read();
690 return m_shared_group->compact();
693 void Realm::write_copy(StringData path, BinaryData key)
695 if (key.data() && key.size() != 64) {
696 throw InvalidEncryptionKeyException();
700 read_group().write(path, key.data());
703 translate_file_exception(path);
707 OwnedBinaryData Realm::write_copy()
710 BinaryData buffer = read_group().write_to_mem();
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());
719 if (is_closed() || is_in_transaction()) {
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();
729 if (m_binding_context) {
730 m_binding_context->before_notify();
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);
740 if (m_binding_context) {
741 m_binding_context->changes_available();
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())
749 m_is_sending_notifications = true;
750 if (m_auto_refresh) {
752 m_coordinator->advance_to_ready(*this);
756 if (m_binding_context) {
757 m_binding_context->did_change({}, {});
760 m_coordinator->process_available_async(*this);
766 bool Realm::refresh()
769 check_read_write(this);
771 // can't be any new changes if we're in a write transaction
772 if (is_in_transaction()) {
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) {
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();
785 m_is_sending_notifications = true;
786 auto cleanup = util::make_scope_exit([this]() noexcept { m_is_sending_notifications = false; });
788 if (m_binding_context) {
789 m_binding_context->before_notify();
792 bool version_changed = m_coordinator->advance_to_latest(*this);
794 return version_changed;
797 // No current read transaction, so just create a new one
799 m_coordinator->process_available_async(*this);
803 bool Realm::can_deliver_notifications() const noexcept
805 if (m_config.immutable()) {
809 if (m_binding_context && !m_binding_context->can_deliver_notifications()) {
816 uint64_t Realm::get_schema_version(const Realm::Config &config)
818 auto coordinator = RealmCoordinator::get_existing_coordinator(config.path);
820 return coordinator->get_schema_version();
823 return ObjectStore::get_schema_version(Realm(config, nullptr).read_group());
829 m_coordinator->unregister_realm(this);
833 m_shared_group = nullptr;
835 m_read_only_group = nullptr;
836 m_binding_context = nullptr;
837 m_coordinator = nullptr;
840 util::Optional<int> Realm::file_format_upgraded_from_version() const
842 if (upgrade_initial_version != upgrade_final_version) {
843 return upgrade_initial_version;
848 template <typename T>
849 realm::ThreadSafeReference<T> Realm::obtain_thread_safe_reference(T const& value)
852 if (is_in_transaction()) {
853 throw InvalidTransactionException("Cannot obtain thread safe reference during a write transaction.");
855 return ThreadSafeReference<T>(value);
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);
862 template <typename T>
863 T Realm::resolve_thread_safe_reference(ThreadSafeReference<T> reference)
866 if (is_in_transaction()) {
867 throw InvalidTransactionException("Cannot resolve thread safe reference during a write transaction.");
869 if (reference.is_invalidated()) {
870 throw std::logic_error("Cannot resolve thread safe reference more than once.");
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.");
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();
881 // Ensure we're on the same version as the reference
883 // A read transaction doesn't yet exist, so create at the reference's version
884 begin_read(reference.m_version_id);
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);
891 if (reference_version == current_version) {
892 return std::move(reference).import_into_realm(shared_from_this());
897 current_version = m_shared_group->get_version_of_current_transaction();
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);
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())
913 reference = ThreadSafeReference<T>(imported_value);
917 return std::move(reference).import_into_realm(shared_from_this());
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);
924 MismatchedConfigException::MismatchedConfigException(StringData message, StringData path)
925 : std::logic_error(util::format(message.data(), path)) { }
927 MismatchedRealmException::MismatchedRealmException(StringData message)
928 : std::logic_error(message.data()) { }
930 // FIXME Those are exposed for Java async queries, mainly because of handover related methods.
931 SharedGroup& RealmFriend::get_shared_group(Realm& realm)
933 return *realm.m_shared_group;
936 Group& RealmFriend::read_group_to(Realm& realm, VersionID version)
938 if (realm.m_group && realm.m_shared_group->get_version_of_current_transaction() == version)
939 return *realm.m_group;
942 realm.m_shared_group->end_read();
943 realm.begin_read(version);
944 return *realm.m_group;