X-Git-Url: https://git.mdrn.pl/wl-app.git/blobdiff_plain/53b27422d140022594fc241cca91c3183be57bca..48b2fe9f7c2dc3d9aeaaa6dbfb27c7da4f3235ff:/iOS/Pods/Realm/Realm/ObjectStore/src/shared_realm.cpp diff --git a/iOS/Pods/Realm/Realm/ObjectStore/src/shared_realm.cpp b/iOS/Pods/Realm/Realm/ObjectStore/src/shared_realm.cpp new file mode 100644 index 0000000..ce39268 --- /dev/null +++ b/iOS/Pods/Realm/Realm/ObjectStore/src/shared_realm.cpp @@ -0,0 +1,945 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2015 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#include "shared_realm.hpp" + +#include "impl/collection_notifier.hpp" +#include "impl/realm_coordinator.hpp" +#include "impl/transact_log_handler.hpp" + +#include "binding_context.hpp" +#include "list.hpp" +#include "object.hpp" +#include "object_schema.hpp" +#include "object_store.hpp" +#include "results.hpp" +#include "schema.hpp" +#include "thread_safe_reference.hpp" + + +#include +#include + +#if REALM_ENABLE_SYNC +#include "sync/impl/sync_file.hpp" +#include "sync/sync_manager.hpp" +#include +#endif + +using namespace realm; +using namespace realm::_impl; + +Realm::Realm(Config config, std::shared_ptr<_impl::RealmCoordinator> coordinator) +: m_config(std::move(config)) +, m_execution_context(m_config.execution_context) +{ + open_with_config(m_config, m_history, m_shared_group, m_read_only_group, this); + + if (m_read_only_group) { + m_group = m_read_only_group.get(); + m_schema_version = ObjectStore::get_schema_version(*m_group); + m_schema = ObjectStore::schema_from_group(*m_group); + } + else if (!coordinator || !coordinator->get_cached_schema(m_schema, m_schema_version, m_schema_transaction_version)) { + if (m_config.should_compact_on_launch_function) { + size_t free_space = -1; + size_t used_space = -1; + // getting stats requires committing a write transaction beforehand. + Group* group = nullptr; + if (m_shared_group->try_begin_write(group)) { + m_shared_group->commit(); + m_shared_group->get_stats(free_space, used_space); + if (m_config.should_compact_on_launch_function(free_space + used_space, used_space)) + compact(); + } + } + read_group(); + if (coordinator) + coordinator->cache_schema(m_schema, m_schema_version, m_schema_transaction_version); + m_shared_group->end_read(); + m_group = nullptr; + } + + m_coordinator = std::move(coordinator); +} + +REALM_NOINLINE static void translate_file_exception(StringData path, bool immutable=false) +{ + try { + throw; + } + catch (util::File::PermissionDenied const& ex) { + throw RealmFileException(RealmFileException::Kind::PermissionDenied, ex.get_path(), + util::format("Unable to open a realm at path '%1'. Please use a path where your app has %2 permissions.", + ex.get_path(), immutable ? "read" : "read-write"), + ex.what()); + } + catch (util::File::Exists const& ex) { + throw RealmFileException(RealmFileException::Kind::Exists, ex.get_path(), + util::format("File at path '%1' already exists.", ex.get_path()), + ex.what()); + } + catch (util::File::NotFound const& ex) { + throw RealmFileException(RealmFileException::Kind::NotFound, ex.get_path(), + util::format("Directory at path '%1' does not exist.", ex.get_path()), ex.what()); + } + catch (util::File::AccessError const& ex) { + // Errors for `open()` include the path, but other errors don't. We + // don't want two copies of the path in the error, so strip it out if it + // appears, and then include it in our prefix. + std::string underlying = ex.what(); + RealmFileException::Kind error_kind = RealmFileException::Kind::AccessError; + // FIXME: Replace this with a proper specific exception type once Core adds support for it. + if (underlying == "Bad or incompatible history type") + error_kind = RealmFileException::Kind::BadHistoryError; + auto pos = underlying.find(ex.get_path()); + if (pos != std::string::npos && pos > 0) { + // One extra char at each end for the quotes + underlying.replace(pos - 1, ex.get_path().size() + 2, ""); + } + throw RealmFileException(error_kind, ex.get_path(), + util::format("Unable to open a realm at path '%1': %2.", ex.get_path(), underlying), ex.what()); + } + catch (IncompatibleLockFile const& ex) { + throw RealmFileException(RealmFileException::Kind::IncompatibleLockFile, path, + "Realm file is currently open in another process " + "which cannot share access with this process. " + "All processes sharing a single file must be the same architecture.", + ex.what()); + } + catch (FileFormatUpgradeRequired const& ex) { + throw RealmFileException(RealmFileException::Kind::FormatUpgradeRequired, path, + "The Realm file format must be allowed to be upgraded " + "in order to proceed.", + ex.what()); + } +} + +#if REALM_ENABLE_SYNC +static bool is_nonupgradable_history(IncompatibleHistories const& ex) +{ + // FIXME: Replace this with a proper specific exception type once Core adds support for it. + return ex.what() == std::string("Incompatible histories. Nonupgradable history schema"); +} +#endif + +void Realm::open_with_config(const Config& config, + std::unique_ptr& history, + std::unique_ptr& shared_group, + std::unique_ptr& read_only_group, + Realm* realm) +{ + bool server_synchronization_mode = bool(config.sync_config) || config.force_sync_history; + try { + if (config.immutable()) { + if (config.realm_data.is_null()) { + read_only_group = std::make_unique(config.path, config.encryption_key.data(), Group::mode_ReadOnly); + } + else { + // Create in-memory read-only realm from existing buffer (without taking ownership of the buffer) + read_only_group = std::make_unique(config.realm_data, false); + } + } + else { + if (server_synchronization_mode) { +#if REALM_ENABLE_SYNC + history = realm::sync::make_client_history(config.path); +#else + REALM_TERMINATE("Realm was not built with sync enabled"); +#endif + } + else { + history = realm::make_in_realm_history(config.path); + } + + SharedGroupOptions options; + options.durability = config.in_memory ? SharedGroupOptions::Durability::MemOnly : + SharedGroupOptions::Durability::Full; + options.encryption_key = config.encryption_key.data(); + options.allow_file_format_upgrade = !config.disable_format_upgrade && + config.schema_mode != SchemaMode::ResetFile; + options.upgrade_callback = [&](int from_version, int to_version) { + if (realm) { + realm->upgrade_initial_version = from_version; + realm->upgrade_final_version = to_version; + } + }; + shared_group = std::make_unique(*history, options); + } + } + catch (realm::FileFormatUpgradeRequired const&) { + if (config.schema_mode != SchemaMode::ResetFile) { + translate_file_exception(config.path, config.immutable()); + } + util::File::remove(config.path); + open_with_config(config, history, shared_group, read_only_group, realm); + } +#if REALM_ENABLE_SYNC + catch (IncompatibleHistories const& ex) { + if (!server_synchronization_mode || !is_nonupgradable_history(ex)) + translate_file_exception(config.path, config.immutable()); // Throws + + // Move the Realm file into the recovery directory. + std::string recovery_directory = SyncManager::shared().recovery_directory_path(); + std::string new_realm_path = util::reserve_unique_file_name(recovery_directory, "synced-realm-XXXXXXX"); + util::File::move(config.path, new_realm_path); + + const char* message = "The local copy of this synced Realm was created with an incompatible version of " + "Realm. It has been moved aside, and the Realm will be re-downloaded the next time it " + "is opened. You should write a handler for this error that uses the provided " + "configuration to open the old Realm in read-only mode to recover any pending changes " + "and then remove the Realm file."; + throw RealmFileException(RealmFileException::Kind::IncompatibleSyncedRealm, std::move(new_realm_path), + message, ex.what()); + } +#endif // REALM_ENABLE_SYNC + catch (...) { + translate_file_exception(config.path, config.immutable()); + } +} + +Realm::~Realm() +{ + if (m_coordinator) { + m_coordinator->unregister_realm(this); + } +} + +Group& Realm::read_group() +{ + verify_open(); + + if (!m_group) + begin_read(VersionID{}); + return *m_group; +} + +void Realm::Internal::begin_read(Realm& realm, VersionID version_id) +{ + realm.begin_read(version_id); +} + +void Realm::begin_read(VersionID version_id) +{ + REALM_ASSERT(!m_group); + m_group = &const_cast(m_shared_group->begin_read(version_id)); + add_schema_change_handler(); + read_schema_from_group_if_needed(); +} + +SharedRealm Realm::get_shared_realm(Config config) +{ + auto coordinator = RealmCoordinator::get_coordinator(config.path); + return coordinator->get_realm(std::move(config)); +} + +void Realm::set_schema(Schema const& reference, Schema schema) +{ + m_dynamic_schema = false; + schema.copy_table_columns_from(reference); + m_schema = std::move(schema); + notify_schema_changed(); +} + +void Realm::read_schema_from_group_if_needed() +{ + REALM_ASSERT(!m_read_only_group); + + Group& group = read_group(); + auto current_version = m_shared_group->get_version_of_current_transaction().version; + if (m_schema_transaction_version == current_version) + return; + + m_schema_transaction_version = current_version; + m_schema_version = ObjectStore::get_schema_version(group); + auto schema = ObjectStore::schema_from_group(group); + if (m_coordinator) + m_coordinator->cache_schema(schema, m_schema_version, + m_schema_transaction_version); + + if (m_dynamic_schema) { + if (m_schema == schema) { + // The structure of the schema hasn't changed. Bring the table column indices up to date. + m_schema.copy_table_columns_from(schema); + } + else { + // The structure of the schema has changed, so replace our copy of the schema. + // FIXME: This invalidates any pointers to the object schemas within the schema vector, + // which will cause problems for anyone that caches such a pointer. + m_schema = std::move(schema); + } + } + else { + ObjectStore::verify_valid_external_changes(m_schema.compare(schema)); + m_schema.copy_table_columns_from(schema); + } + notify_schema_changed(); +} + +bool Realm::reset_file(Schema& schema, std::vector& required_changes) +{ + // FIXME: this does not work if multiple processes try to open the file at + // the same time, or even multiple threads if there is not any external + // synchronization. The latter is probably fixable, but making it + // multi-process-safe requires some sort of multi-process exclusive lock + m_group = nullptr; + m_shared_group = nullptr; + m_history = nullptr; + util::File::remove(m_config.path); + + open_with_config(m_config, m_history, m_shared_group, m_read_only_group, this); + m_schema = ObjectStore::schema_from_group(read_group()); + m_schema_version = ObjectStore::get_schema_version(read_group()); + required_changes = m_schema.compare(schema); + m_coordinator->clear_schema_cache_and_set_schema_version(m_schema_version); + return false; +} + +bool Realm::schema_change_needs_write_transaction(Schema& schema, + std::vector& changes, + uint64_t version) +{ + if (version == m_schema_version && changes.empty()) + return false; + + switch (m_config.schema_mode) { + case SchemaMode::Automatic: + if (version < m_schema_version && m_schema_version != ObjectStore::NotVersioned) + throw InvalidSchemaVersionException(m_schema_version, version); + return true; + + case SchemaMode::Immutable: + if (version != m_schema_version) + throw InvalidSchemaVersionException(m_schema_version, version); + REALM_FALLTHROUGH; + case SchemaMode::ReadOnlyAlternative: + ObjectStore::verify_compatible_for_immutable_and_readonly(changes); + return false; + + case SchemaMode::ResetFile: + if (m_schema_version == ObjectStore::NotVersioned) + return true; + if (m_schema_version == version && !ObjectStore::needs_migration(changes)) + return true; + reset_file(schema, changes); + return true; + + case SchemaMode::Additive: { + bool will_apply_index_changes = version > m_schema_version; + if (ObjectStore::verify_valid_additive_changes(changes, will_apply_index_changes)) + return true; + return version != m_schema_version; + } + + case SchemaMode::Manual: + if (version < m_schema_version && m_schema_version != ObjectStore::NotVersioned) + throw InvalidSchemaVersionException(m_schema_version, version); + if (version == m_schema_version) { + ObjectStore::verify_no_changes_required(changes); + REALM_UNREACHABLE(); // changes is non-empty so above line always throws + } + return true; + } + REALM_COMPILER_HINT_UNREACHABLE(); +} + +Schema Realm::get_full_schema() +{ + if (!m_read_only_group) + refresh(); + + // If the user hasn't specified a schema previously then m_schema is always + // the full schema + if (m_dynamic_schema) + return m_schema; + + // Otherwise we may have a subset of the file's schema, so we need to get + // the complete thing to calculate what changes to make + if (m_read_only_group) + return ObjectStore::schema_from_group(read_group()); + + Schema actual_schema; + uint64_t actual_version; + uint64_t transaction = -1; + bool got_cached = m_coordinator->get_cached_schema(actual_schema, actual_version, transaction); + if (!got_cached || transaction != m_shared_group->get_version_of_current_transaction().version) + return ObjectStore::schema_from_group(read_group()); + return actual_schema; +} + +void Realm::set_schema_subset(Schema schema) +{ + REALM_ASSERT(m_dynamic_schema); + REALM_ASSERT(m_schema_version != ObjectStore::NotVersioned); + + std::vector changes = m_schema.compare(schema); + switch (m_config.schema_mode) { + case SchemaMode::Automatic: + case SchemaMode::ResetFile: + ObjectStore::verify_no_migration_required(changes); + break; + + case SchemaMode::Immutable: + case SchemaMode::ReadOnlyAlternative: + ObjectStore::verify_compatible_for_immutable_and_readonly(changes); + break; + + case SchemaMode::Additive: + ObjectStore::verify_valid_additive_changes(changes); + break; + + case SchemaMode::Manual: + ObjectStore::verify_no_changes_required(changes); + break; + } + + set_schema(m_schema, std::move(schema)); +} + +void Realm::update_schema(Schema schema, uint64_t version, MigrationFunction migration_function, + DataInitializationFunction initialization_function, bool in_transaction) +{ + schema.validate(); + + Schema actual_schema = get_full_schema(); + std::vector required_changes = actual_schema.compare(schema); + + if (!schema_change_needs_write_transaction(schema, required_changes, version)) { + set_schema(actual_schema, std::move(schema)); + return; + } + // Either the schema version has changed or we need to do non-migration changes + + if (!in_transaction) { + transaction::begin_without_validation(*m_shared_group); + + // Beginning the write transaction may have advanced the version and left + // us with nothing to do if someone else initialized the schema on disk + if (m_new_schema) { + actual_schema = *m_new_schema; + required_changes = actual_schema.compare(schema); + if (!schema_change_needs_write_transaction(schema, required_changes, version)) { + cancel_transaction(); + cache_new_schema(); + set_schema(actual_schema, std::move(schema)); + return; + } + } + cache_new_schema(); + } + + // Cancel the write transaction if we exit this function before committing it + auto cleanup = util::make_scope_exit([&]() noexcept { + // When in_transaction is true, caller is responsible to cancel the transaction. + if (!in_transaction && is_in_transaction()) + cancel_transaction(); + }); + + uint64_t old_schema_version = m_schema_version; + bool additive = m_config.schema_mode == SchemaMode::Additive; + if (migration_function && !additive) { + auto wrapper = [&] { + SharedRealm old_realm(new Realm(m_config, nullptr)); + // Need to open in read-write mode so that it uses a SharedGroup, but + // users shouldn't actually be able to write via the old realm + old_realm->m_config.schema_mode = SchemaMode::Immutable; + migration_function(old_realm, shared_from_this(), m_schema); + }; + + // migration function needs to see the target schema on the "new" Realm + std::swap(m_schema, schema); + std::swap(m_schema_version, version); + m_in_migration = true; + auto restore = util::make_scope_exit([&]() noexcept { + std::swap(m_schema, schema); + std::swap(m_schema_version, version); + m_in_migration = false; + }); + + ObjectStore::apply_schema_changes(read_group(), version, m_schema, m_schema_version, + m_config.schema_mode, required_changes, wrapper); + } + else { + ObjectStore::apply_schema_changes(read_group(), m_schema_version, schema, version, + m_config.schema_mode, required_changes); + REALM_ASSERT_DEBUG(additive || (required_changes = ObjectStore::schema_from_group(read_group()).compare(schema)).empty()); + } + + if (initialization_function && old_schema_version == ObjectStore::NotVersioned) { + // Initialization function needs to see the latest schema + uint64_t temp_version = ObjectStore::get_schema_version(read_group()); + std::swap(m_schema, schema); + std::swap(m_schema_version, temp_version); + auto restore = util::make_scope_exit([&]() noexcept { + std::swap(m_schema, schema); + std::swap(m_schema_version, temp_version); + }); + initialization_function(shared_from_this()); + } + + if (!in_transaction) { + commit_transaction(); + } + + m_schema = std::move(schema); + m_schema_version = ObjectStore::get_schema_version(read_group()); + m_dynamic_schema = false; + m_coordinator->clear_schema_cache_and_set_schema_version(version); + notify_schema_changed(); +} + +void Realm::add_schema_change_handler() +{ + if (m_config.immutable()) + return; + m_group->set_schema_change_notification_handler([&] { + m_new_schema = ObjectStore::schema_from_group(read_group()); + m_schema_version = ObjectStore::get_schema_version(read_group()); + if (m_dynamic_schema) { + // FIXME: This invalidates any pointers to the object schemas within the schema vector, + // which will cause problems for anyone that caches such a pointer. + m_schema = *m_new_schema; + } + else + m_schema.copy_table_columns_from(*m_new_schema); + + notify_schema_changed(); + }); +} + +void Realm::cache_new_schema() +{ + if (!m_shared_group) + return; + + auto new_version = m_shared_group->get_version_of_current_transaction().version; + if (m_coordinator) { + if (m_new_schema) + m_coordinator->cache_schema(std::move(*m_new_schema), m_schema_version, new_version); + else + m_coordinator->advance_schema_cache(m_schema_transaction_version, new_version); + } + m_schema_transaction_version = new_version; + m_new_schema = util::none; +} + +void Realm::notify_schema_changed() { + if (m_binding_context) { + m_binding_context->schema_did_change(m_schema); + } +} + +static void check_read_write(Realm *realm) +{ + if (realm->config().immutable()) { + throw InvalidTransactionException("Can't perform transactions on read-only Realms."); + } +} + +static void check_write(Realm* realm) +{ + if (realm->config().immutable() || realm->config().read_only_alternative()) { + throw InvalidTransactionException("Can't perform transactions on read-only Realms."); + } +} + +void Realm::verify_thread() const +{ + if (!m_execution_context.contains()) + return; + + auto thread_id = m_execution_context.get(); + if (thread_id != std::this_thread::get_id()) + throw IncorrectThreadException(); +} + +void Realm::verify_in_write() const +{ + if (!is_in_transaction()) { + throw InvalidTransactionException("Cannot modify managed objects outside of a write transaction."); + } +} + +void Realm::verify_open() const +{ + if (is_closed()) { + throw ClosedRealmException(); + } +} + +bool Realm::is_in_transaction() const noexcept +{ + if (!m_shared_group) { + return false; + } + return m_shared_group->get_transact_stage() == SharedGroup::transact_Writing; +} + +void Realm::begin_transaction() +{ + check_write(this); + verify_thread(); + + if (is_in_transaction()) { + throw InvalidTransactionException("The Realm is already in a write transaction"); + } + + // Any of the callbacks to user code below could drop the last remaining + // strong reference to `this` + auto retain_self = shared_from_this(); + + // If we're already in the middle of sending notifications, just begin the + // write transaction without sending more notifications. If this actually + // advances the read version this could leave the user in an inconsistent + // state, but that's unavoidable. + if (m_is_sending_notifications) { + _impl::NotifierPackage notifiers; + transaction::begin(m_shared_group, m_binding_context.get(), notifiers); + return; + } + + // make sure we have a read transaction + read_group(); + + m_is_sending_notifications = true; + auto cleanup = util::make_scope_exit([this]() noexcept { m_is_sending_notifications = false; }); + + m_coordinator->promote_to_write(*this); + cache_new_schema(); +} + +void Realm::commit_transaction() +{ + check_write(this); + verify_thread(); + + if (!is_in_transaction()) { + throw InvalidTransactionException("Can't commit a non-existing write transaction"); + } + + m_coordinator->commit_write(*this); + cache_new_schema(); +} + +void Realm::cancel_transaction() +{ + check_write(this); + verify_thread(); + + if (!is_in_transaction()) { + throw InvalidTransactionException("Can't cancel a non-existing write transaction"); + } + + transaction::cancel(*m_shared_group, m_binding_context.get()); +} + +void Realm::invalidate() +{ + verify_open(); + verify_thread(); + check_read_write(this); + + if (m_is_sending_notifications) { + return; + } + + if (is_in_transaction()) { + cancel_transaction(); + } + if (!m_group) { + return; + } + + m_shared_group->end_read(); + m_group = nullptr; +} + +bool Realm::compact() +{ + verify_thread(); + + if (m_config.immutable() || m_config.read_only_alternative()) { + throw InvalidTransactionException("Can't compact a read-only Realm"); + } + if (is_in_transaction()) { + throw InvalidTransactionException("Can't compact a Realm within a write transaction"); + } + + Group& group = read_group(); + for (auto &object_schema : m_schema) { + ObjectStore::table_for_object_type(group, object_schema.name)->optimize(); + } + m_shared_group->end_read(); + m_group = nullptr; + + return m_shared_group->compact(); +} + +void Realm::write_copy(StringData path, BinaryData key) +{ + if (key.data() && key.size() != 64) { + throw InvalidEncryptionKeyException(); + } + verify_thread(); + try { + read_group().write(path, key.data()); + } + catch (...) { + translate_file_exception(path); + } +} + +OwnedBinaryData Realm::write_copy() +{ + verify_thread(); + BinaryData buffer = read_group().write_to_mem(); + + // Since OwnedBinaryData does not have a constructor directly taking + // ownership of BinaryData, we have to do this to avoid copying the buffer + return OwnedBinaryData(std::unique_ptr((char*)buffer.data()), buffer.size()); +} + +void Realm::notify() +{ + if (is_closed() || is_in_transaction()) { + return; + } + + verify_thread(); + + // Any of the callbacks to user code below could drop the last remaining + // strong reference to `this` + auto retain_self = shared_from_this(); + + if (m_binding_context) { + m_binding_context->before_notify(); + } + + auto cleanup = util::make_scope_exit([this]() noexcept { m_is_sending_notifications = false; }); + if (!m_shared_group->has_changed()) { + m_is_sending_notifications = true; + m_coordinator->process_available_async(*this); + return; + } + + if (m_binding_context) { + m_binding_context->changes_available(); + + // changes_available() may have advanced the read version, and if + // so we don't need to do anything further + if (!m_shared_group->has_changed()) + return; + } + + m_is_sending_notifications = true; + if (m_auto_refresh) { + if (m_group) { + m_coordinator->advance_to_ready(*this); + cache_new_schema(); + } + else { + if (m_binding_context) { + m_binding_context->did_change({}, {}); + } + if (!is_closed()) { + m_coordinator->process_available_async(*this); + } + } + } +} + +bool Realm::refresh() +{ + verify_thread(); + check_read_write(this); + + // can't be any new changes if we're in a write transaction + if (is_in_transaction()) { + return false; + } + // don't advance if we're already in the process of advancing as that just + // makes things needlessly complicated + if (m_is_sending_notifications) { + return false; + } + + // Any of the callbacks to user code below could drop the last remaining + // strong reference to `this` + auto retain_self = shared_from_this(); + + m_is_sending_notifications = true; + auto cleanup = util::make_scope_exit([this]() noexcept { m_is_sending_notifications = false; }); + + if (m_binding_context) { + m_binding_context->before_notify(); + } + if (m_group) { + bool version_changed = m_coordinator->advance_to_latest(*this); + cache_new_schema(); + return version_changed; + } + + // No current read transaction, so just create a new one + read_group(); + m_coordinator->process_available_async(*this); + return true; +} + +bool Realm::can_deliver_notifications() const noexcept +{ + if (m_config.immutable()) { + return false; + } + + if (m_binding_context && !m_binding_context->can_deliver_notifications()) { + return false; + } + + return true; +} + +uint64_t Realm::get_schema_version(const Realm::Config &config) +{ + auto coordinator = RealmCoordinator::get_existing_coordinator(config.path); + if (coordinator) { + return coordinator->get_schema_version(); + } + + return ObjectStore::get_schema_version(Realm(config, nullptr).read_group()); +} + +void Realm::close() +{ + if (m_coordinator) { + m_coordinator->unregister_realm(this); + } + + m_group = nullptr; + m_shared_group = nullptr; + m_history = nullptr; + m_read_only_group = nullptr; + m_binding_context = nullptr; + m_coordinator = nullptr; +} + +util::Optional Realm::file_format_upgraded_from_version() const +{ + if (upgrade_initial_version != upgrade_final_version) { + return upgrade_initial_version; + } + return util::none; +} + +template +realm::ThreadSafeReference Realm::obtain_thread_safe_reference(T const& value) +{ + verify_thread(); + if (is_in_transaction()) { + throw InvalidTransactionException("Cannot obtain thread safe reference during a write transaction."); + } + return ThreadSafeReference(value); +} + +template ThreadSafeReference Realm::obtain_thread_safe_reference(Object const& value); +template ThreadSafeReference Realm::obtain_thread_safe_reference(List const& value); +template ThreadSafeReference Realm::obtain_thread_safe_reference(Results const& value); + +template +T Realm::resolve_thread_safe_reference(ThreadSafeReference reference) +{ + verify_thread(); + if (is_in_transaction()) { + throw InvalidTransactionException("Cannot resolve thread safe reference during a write transaction."); + } + if (reference.is_invalidated()) { + throw std::logic_error("Cannot resolve thread safe reference more than once."); + } + if (!reference.has_same_config(*this)) { + throw MismatchedRealmException("Cannot resolve thread safe reference in Realm with different configuration " + "than the source Realm."); + } + + // Any of the callbacks to user code below could drop the last remaining + // strong reference to `this` + auto retain_self = shared_from_this(); + + // Ensure we're on the same version as the reference + if (!m_group) { + // A read transaction doesn't yet exist, so create at the reference's version + begin_read(reference.m_version_id); + } + else { + // A read transaction does exist, but let's make sure that its version matches the reference's + auto current_version = m_shared_group->get_version_of_current_transaction(); + VersionID reference_version(reference.m_version_id); + + if (reference_version == current_version) { + return std::move(reference).import_into_realm(shared_from_this()); + } + + refresh(); + + current_version = m_shared_group->get_version_of_current_transaction(); + + // If the reference's version is behind, advance it to our version + if (reference_version < current_version) { + // Duplicate config for uncached Realm so we don't advance the user's Realm + Realm::Config config = m_coordinator->get_config(); + config.cache = false; + config.schema = util::none; + SharedRealm temporary_realm = m_coordinator->get_realm(config); + temporary_realm->begin_read(reference_version); + + // With reference imported, advance temporary Realm to our version + T imported_value = std::move(reference).import_into_realm(temporary_realm); + transaction::advance(*temporary_realm->m_shared_group, nullptr, current_version); + if (!imported_value.is_valid()) + return T{}; + reference = ThreadSafeReference(imported_value); + } + } + + return std::move(reference).import_into_realm(shared_from_this()); +} + +template Object Realm::resolve_thread_safe_reference(ThreadSafeReference reference); +template List Realm::resolve_thread_safe_reference(ThreadSafeReference reference); +template Results Realm::resolve_thread_safe_reference(ThreadSafeReference reference); + +MismatchedConfigException::MismatchedConfigException(StringData message, StringData path) +: std::logic_error(util::format(message.data(), path)) { } + +MismatchedRealmException::MismatchedRealmException(StringData message) +: std::logic_error(message.data()) { } + +// FIXME Those are exposed for Java async queries, mainly because of handover related methods. +SharedGroup& RealmFriend::get_shared_group(Realm& realm) +{ + return *realm.m_shared_group; +} + +Group& RealmFriend::read_group_to(Realm& realm, VersionID version) +{ + if (realm.m_group && realm.m_shared_group->get_version_of_current_transaction() == version) + return *realm.m_group; + + if (realm.m_group) + realm.m_shared_group->end_read(); + realm.begin_read(version); + return *realm.m_group; +}