added iOS source code
[wl-app.git] / 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 (file)
index 0000000..ce39268
--- /dev/null
@@ -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 <realm/history.hpp>
+#include <realm/util/scope_exit.hpp>
+
+#if REALM_ENABLE_SYNC
+#include "sync/impl/sync_file.hpp"
+#include "sync/sync_manager.hpp"
+#include <realm/sync/history.hpp>
+#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<Replication>& history,
+                             std::unique_ptr<SharedGroup>& shared_group,
+                             std::unique_ptr<Group>& 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<Group>(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<Group>(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<SharedGroup>(*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<Group&>(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<SchemaChange>& 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<SchemaChange>& 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<SchemaChange> 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<SchemaChange> 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<std::thread::id>())
+        return;
+
+    auto thread_id = m_execution_context.get<std::thread::id>();
+    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[]>((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<int> Realm::file_format_upgraded_from_version() const
+{
+    if (upgrade_initial_version != upgrade_final_version) {
+        return upgrade_initial_version;
+    }
+    return util::none;
+}
+
+template <typename T>
+realm::ThreadSafeReference<T> 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<T>(value);
+}
+
+template ThreadSafeReference<Object> Realm::obtain_thread_safe_reference(Object const& value);
+template ThreadSafeReference<List> Realm::obtain_thread_safe_reference(List const& value);
+template ThreadSafeReference<Results> Realm::obtain_thread_safe_reference(Results const& value);
+
+template <typename T>
+T Realm::resolve_thread_safe_reference(ThreadSafeReference<T> 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<T>(imported_value);
+        }
+    }
+
+    return std::move(reference).import_into_realm(shared_from_this());
+}
+
+template Object Realm::resolve_thread_safe_reference(ThreadSafeReference<Object> reference);
+template List Realm::resolve_thread_safe_reference(ThreadSafeReference<List> reference);
+template Results Realm::resolve_thread_safe_reference(ThreadSafeReference<Results> 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;
+}