mirror of
https://github.com/status-im/realm-js.git
synced 2025-01-11 23:04:29 +00:00
b91c69a3b5
7701ba1 Merge pull request #16 from realm/tg-close 883ef12 Add Realm::close() and call it in RealmCache::clear() e9ca54e Merge pull request #12 from realm/tg-core-0.94.4 0823a62 Merge pull request #13 from realm/tg-is-empty 62f59d9 Merge pull request #15 from realm/tg-file-error-path b93e5ce Include the path of the file which actually failed to open in exceptions e1e9cd8 Add ObjectStore::is_empty() 52e7e61 Update for core 0.94.4 271432b Merge pull request #11 from realm/kd-rename-delegate-to-bindingcontext db36ca5 Remove Realm suffix 70e1967 Rename realm delegate in transact_log_handler as well 4973827 Rename RealmDelegate to RealmBindingContext 347145b Merge pull request #8 from realm/mar-migrate-required-to-optional 0b45772 Add a test showing our behavior when migrating from an optional column to a required column. 9f1702a Support migrating required columns to optional, preserving their contents. f5b790c Merge pull request #6 from realm/tg-impl 6dfeaf8 Move things which are not part of the API to an impl directory/namespace 513e483 Merge pull request #4 from realm/tg-realm f646777 Send changes_available() even if autorefresh is enabled 21d32bf Add a bit of documentation for RealmDelegate 95c80c9 Fix error in cleanup after an error during a migration b7936bb Simplify column shifting for removed properties a little a0f1dab Add a Schema class, move lookup by name and internal-consistency checks there b381437 Make Realm::compact() more robust 6133eeb Reduce the scope of a variable 0c111a2 Fix a comment ba278c5 Fix checks for what types of columns can be optional 7de20ea USe more const refs to avoid copies dbac77f Make a bunch of things const 1400450 Remove an unused function 06e0ff8 Share ExternalCommitHelpers between Realm instances for a single path f79dec9 Allow more nullable property types when supported 0eb0bd1 Honor is_nullable when creating columns ea5c475 Refactor schema initialization a bit e4f29fe Move the interprocess notification functionality to the object store b129ebe Shuffle stuff around and clean some things up eeb2ddd Improve array KVO performance a bit c3649fb Skip PK uniqueness checking when first creating a Realm file 0a41c85 Improve performance of realm_requires_update() and make more things const efdfa08 Port some of the KVO support functionality to the object store 65e1eb5 Add the ability to bypass the Realm cache entirely 3f226cf Rework change notifications 045c7b2 Add Realm::get_schema_version() e4377bb Change realm::Schema to a vector rather than a map cae4cf2 Remove property.hpp include from object_schema.hpp 55e6cca Convert RLMRealmConfiguration to a wrapper around Realm::Config 563a837 Use NSDMIs for realm::Property 0ae1bb1 Don't cache dynamic realms in the ObjectStore cache either 25a6734 Eliminate some copies 45890f2 Use NSDMIs for Realm 348f4a7 Reduce s_init_mutex's scope b4f856b Use NSDMIs for Realm::Config and make it moveable a91839b Store a copy of the encryption key 0700428 Merge pull request #3 from realm/al-bugfixes b084335 clear Realm cache between tests cb8364c property copy schema from cached realms 8712c8b fixes for latest object store changes 453e4d8 Fix crash when adding a property to a model without updating the schema version. git-subtree-dir: src/object-store git-subtree-split: 7701ba173d6c8d928a4f736a33c2850343b050e3
475 lines
14 KiB
C++
475 lines
14 KiB
C++
////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// 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 "external_commit_helper.hpp"
|
|
#include "binding_context.hpp"
|
|
#include "schema.hpp"
|
|
#include "transact_log_handler.hpp"
|
|
|
|
#include <realm/commit_log.hpp>
|
|
#include <realm/group_shared.hpp>
|
|
|
|
#include <mutex>
|
|
|
|
using namespace realm;
|
|
using namespace realm::_impl;
|
|
|
|
RealmCache Realm::s_global_cache;
|
|
|
|
Realm::Config::Config(const Config& c)
|
|
: path(c.path)
|
|
, read_only(c.read_only)
|
|
, in_memory(c.in_memory)
|
|
, cache(c.cache)
|
|
, encryption_key(c.encryption_key)
|
|
, schema_version(c.schema_version)
|
|
, migration_function(c.migration_function)
|
|
{
|
|
if (c.schema) {
|
|
schema = std::make_unique<Schema>(*c.schema);
|
|
}
|
|
}
|
|
|
|
Realm::Config::~Config() = default;
|
|
|
|
Realm::Config& Realm::Config::operator=(realm::Realm::Config const& c)
|
|
{
|
|
if (&c != this) {
|
|
*this = Config(c);
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
Realm::Realm(Config config)
|
|
: m_config(std::move(config))
|
|
{
|
|
try {
|
|
if (m_config.read_only) {
|
|
m_read_only_group = std::make_unique<Group>(m_config.path, m_config.encryption_key.data(), Group::mode_ReadOnly);
|
|
m_group = m_read_only_group.get();
|
|
}
|
|
else {
|
|
m_history = realm::make_client_history(m_config.path, m_config.encryption_key.data());
|
|
SharedGroup::DurabilityLevel durability = m_config.in_memory ? SharedGroup::durability_MemOnly :
|
|
SharedGroup::durability_Full;
|
|
m_shared_group = std::make_unique<SharedGroup>(*m_history, durability, m_config.encryption_key.data());
|
|
}
|
|
}
|
|
catch (util::File::PermissionDenied const& ex) {
|
|
throw RealmFileException(RealmFileException::Kind::PermissionDenied, ex.get_path(),
|
|
"Unable to open a realm at path '" + ex.get_path() +
|
|
"'. Please use a path where your app has " + (m_config.read_only ? "read" : "read-write") + " permissions.");
|
|
}
|
|
catch (util::File::Exists const& ex) {
|
|
throw RealmFileException(RealmFileException::Kind::Exists, ex.get_path(),
|
|
"File at path '" + ex.get_path() + "' already exists.");
|
|
}
|
|
catch (util::File::AccessError const& ex) {
|
|
throw RealmFileException(RealmFileException::Kind::AccessError, ex.get_path(),
|
|
"Unable to open a realm at path '" + ex.get_path() + "'");
|
|
}
|
|
catch (IncompatibleLockFile const& ex) {
|
|
throw RealmFileException(RealmFileException::Kind::IncompatibleLockFile, m_config.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.");
|
|
}
|
|
}
|
|
|
|
Realm::~Realm() {
|
|
if (m_notifier) { // might not exist yet if an error occurred during init
|
|
m_notifier->remove_realm(this);
|
|
}
|
|
}
|
|
|
|
Group *Realm::read_group()
|
|
{
|
|
if (!m_group) {
|
|
m_group = &const_cast<Group&>(m_shared_group->begin_read());
|
|
}
|
|
return m_group;
|
|
}
|
|
|
|
SharedRealm Realm::get_shared_realm(Config config)
|
|
{
|
|
if (config.cache) {
|
|
if (SharedRealm realm = s_global_cache.get_realm(config.path)) {
|
|
if (realm->config().read_only != config.read_only) {
|
|
throw MismatchedConfigException("Realm at path already opened with different read permissions.");
|
|
}
|
|
if (realm->config().in_memory != config.in_memory) {
|
|
throw MismatchedConfigException("Realm at path already opened with different inMemory settings.");
|
|
}
|
|
if (realm->config().encryption_key != config.encryption_key) {
|
|
throw MismatchedConfigException("Realm at path already opened with a different encryption key.");
|
|
}
|
|
if (realm->config().schema_version != config.schema_version && config.schema_version != ObjectStore::NotVersioned) {
|
|
throw MismatchedConfigException("Realm at path already opened with different schema version.");
|
|
}
|
|
// FIXME - enable schma comparison
|
|
/*if (realm->config().schema != config.schema) {
|
|
throw MismatchedConfigException("Realm at path already opened with different schema");
|
|
}*/
|
|
realm->m_config.migration_function = config.migration_function;
|
|
|
|
return realm;
|
|
}
|
|
}
|
|
|
|
SharedRealm realm(new Realm(std::move(config)));
|
|
|
|
auto target_schema = std::move(realm->m_config.schema);
|
|
auto target_schema_version = realm->m_config.schema_version;
|
|
realm->m_config.schema_version = ObjectStore::get_schema_version(realm->read_group());
|
|
|
|
// we want to ensure we are only initializing a single realm at a time
|
|
static std::mutex s_init_mutex;
|
|
std::lock_guard<std::mutex> lock(s_init_mutex);
|
|
if (auto existing = s_global_cache.get_any_realm(realm->config().path)) {
|
|
// if there is an existing realm at the current path steal its schema/column mapping
|
|
// FIXME - need to validate that schemas match
|
|
realm->m_config.schema = std::make_unique<Schema>(*existing->m_config.schema);
|
|
|
|
realm->m_notifier = existing->m_notifier;
|
|
realm->m_notifier->add_realm(realm.get());
|
|
}
|
|
else {
|
|
realm->m_notifier = std::make_shared<ExternalCommitHelper>(realm.get());
|
|
|
|
// otherwise get the schema from the group
|
|
realm->m_config.schema = std::make_unique<Schema>(ObjectStore::schema_from_group(realm->read_group()));
|
|
|
|
// if a target schema is supplied, verify that it matches or migrate to
|
|
// it, as neeeded
|
|
if (target_schema) {
|
|
if (realm->m_config.read_only) {
|
|
if (realm->m_config.schema_version == ObjectStore::NotVersioned) {
|
|
throw UnitializedRealmException("Can't open an un-initialized Realm without a Schema");
|
|
}
|
|
target_schema->validate();
|
|
ObjectStore::verify_schema(*realm->m_config.schema, *target_schema, true);
|
|
realm->m_config.schema = std::move(target_schema);
|
|
}
|
|
else {
|
|
realm->update_schema(std::move(target_schema), target_schema_version);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (config.cache) {
|
|
s_global_cache.cache_realm(realm, realm->m_thread_id);
|
|
}
|
|
return realm;
|
|
}
|
|
|
|
bool Realm::update_schema(std::unique_ptr<Schema> schema, uint64_t version)
|
|
{
|
|
schema->validate();
|
|
|
|
bool needs_update = !m_config.read_only && (m_config.schema_version != version || ObjectStore::needs_update(*m_config.schema, *schema));
|
|
if (!needs_update) {
|
|
ObjectStore::verify_schema(*m_config.schema, *schema, m_config.read_only);
|
|
m_config.schema = std::move(schema);
|
|
m_config.schema_version = version;
|
|
return false;
|
|
}
|
|
|
|
// Store the old config/schema for the migration function, and update
|
|
// our schema to the new one
|
|
auto old_schema = std::move(m_config.schema);
|
|
Config old_config(m_config);
|
|
old_config.read_only = true;
|
|
old_config.schema = std::move(old_schema);
|
|
|
|
m_config.schema = std::move(schema);
|
|
m_config.schema_version = version;
|
|
|
|
auto migration_function = [&](Group*, Schema&) {
|
|
SharedRealm old_realm(new Realm(old_config));
|
|
auto updated_realm = shared_from_this();
|
|
m_config.migration_function(old_realm, updated_realm);
|
|
};
|
|
|
|
try {
|
|
// update and migrate
|
|
begin_transaction();
|
|
bool changed = ObjectStore::update_realm_with_schema(read_group(), *old_config.schema,
|
|
version, *m_config.schema,
|
|
migration_function);
|
|
commit_transaction();
|
|
return changed;
|
|
}
|
|
catch (...) {
|
|
if (is_in_transaction()) {
|
|
cancel_transaction();
|
|
}
|
|
m_config.schema_version = old_config.schema_version;
|
|
m_config.schema = std::move(old_config.schema);
|
|
throw;
|
|
}
|
|
}
|
|
|
|
static void check_read_write(Realm *realm)
|
|
{
|
|
if (realm->config().read_only) {
|
|
throw InvalidTransactionException("Can't perform transactions on read-only Realms.");
|
|
}
|
|
}
|
|
|
|
void Realm::verify_thread() const
|
|
{
|
|
if (m_thread_id != std::this_thread::get_id()) {
|
|
throw IncorrectThreadException("Realm accessed from incorrect thread.");
|
|
}
|
|
}
|
|
|
|
void Realm::begin_transaction()
|
|
{
|
|
check_read_write(this);
|
|
verify_thread();
|
|
|
|
if (m_in_transaction) {
|
|
throw InvalidTransactionException("The Realm is already in a write transaction");
|
|
}
|
|
|
|
// make sure we have a read transaction
|
|
read_group();
|
|
|
|
transaction::begin(*m_shared_group, *m_history, m_binding_context.get());
|
|
m_in_transaction = true;
|
|
}
|
|
|
|
void Realm::commit_transaction()
|
|
{
|
|
check_read_write(this);
|
|
verify_thread();
|
|
|
|
if (!m_in_transaction) {
|
|
throw InvalidTransactionException("Can't commit a non-existing write transaction");
|
|
}
|
|
|
|
m_in_transaction = false;
|
|
transaction::commit(*m_shared_group, *m_history, m_binding_context.get());
|
|
m_notifier->notify_others();
|
|
}
|
|
|
|
void Realm::cancel_transaction()
|
|
{
|
|
check_read_write(this);
|
|
verify_thread();
|
|
|
|
if (!m_in_transaction) {
|
|
throw InvalidTransactionException("Can't cancel a non-existing write transaction");
|
|
}
|
|
|
|
m_in_transaction = false;
|
|
transaction::cancel(*m_shared_group, *m_history, m_binding_context.get());
|
|
}
|
|
|
|
void Realm::invalidate()
|
|
{
|
|
verify_thread();
|
|
check_read_write(this);
|
|
|
|
if (m_in_transaction) {
|
|
cancel_transaction();
|
|
}
|
|
if (!m_group) {
|
|
return;
|
|
}
|
|
|
|
m_shared_group->end_read();
|
|
m_group = nullptr;
|
|
}
|
|
|
|
bool Realm::compact()
|
|
{
|
|
verify_thread();
|
|
|
|
if (m_config.read_only) {
|
|
throw InvalidTransactionException("Can't compact a read-only Realm");
|
|
}
|
|
if (m_in_transaction) {
|
|
throw InvalidTransactionException("Can't compact a Realm within a write transaction");
|
|
}
|
|
|
|
Group* group = read_group();
|
|
for (auto &object_schema : *m_config.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::notify()
|
|
{
|
|
verify_thread();
|
|
|
|
if (m_shared_group->has_changed()) { // Throws
|
|
if (m_binding_context) {
|
|
m_binding_context->changes_available();
|
|
}
|
|
if (m_auto_refresh) {
|
|
if (m_group) {
|
|
transaction::advance(*m_shared_group, *m_history, m_binding_context.get());
|
|
}
|
|
else if (m_binding_context) {
|
|
m_binding_context->did_change({}, {});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
bool Realm::refresh()
|
|
{
|
|
verify_thread();
|
|
check_read_write(this);
|
|
|
|
// can't be any new changes if we're in a write transaction
|
|
if (m_in_transaction) {
|
|
return false;
|
|
}
|
|
|
|
// advance transaction if database has changed
|
|
if (!m_shared_group->has_changed()) { // Throws
|
|
return false;
|
|
}
|
|
|
|
if (m_group) {
|
|
transaction::advance(*m_shared_group, *m_history, m_binding_context.get());
|
|
}
|
|
else {
|
|
// Create the read transaction
|
|
read_group();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
uint64_t Realm::get_schema_version(const realm::Realm::Config &config)
|
|
{
|
|
if (auto existing_realm = s_global_cache.get_any_realm(config.path)) {
|
|
return existing_realm->config().schema_version;
|
|
}
|
|
|
|
return ObjectStore::get_schema_version(Realm(config).read_group());
|
|
}
|
|
|
|
void Realm::close()
|
|
{
|
|
invalidate();
|
|
|
|
if (m_notifier) {
|
|
m_notifier->remove_realm(this);
|
|
}
|
|
|
|
m_group = nullptr;
|
|
m_shared_group = nullptr;
|
|
m_history = nullptr;
|
|
m_read_only_group = nullptr;
|
|
m_notifier = nullptr;
|
|
m_binding_context = nullptr;
|
|
}
|
|
|
|
SharedRealm RealmCache::get_realm(const std::string &path, std::thread::id thread_id)
|
|
{
|
|
std::lock_guard<std::mutex> lock(m_mutex);
|
|
|
|
auto path_iter = m_cache.find(path);
|
|
if (path_iter == m_cache.end()) {
|
|
return SharedRealm();
|
|
}
|
|
|
|
auto thread_iter = path_iter->second.find(thread_id);
|
|
if (thread_iter == path_iter->second.end()) {
|
|
return SharedRealm();
|
|
}
|
|
|
|
return thread_iter->second.lock();
|
|
}
|
|
|
|
SharedRealm RealmCache::get_any_realm(const std::string &path)
|
|
{
|
|
std::lock_guard<std::mutex> lock(m_mutex);
|
|
|
|
auto path_iter = m_cache.find(path);
|
|
if (path_iter == m_cache.end()) {
|
|
return SharedRealm();
|
|
}
|
|
|
|
auto thread_iter = path_iter->second.begin();
|
|
while (thread_iter != path_iter->second.end()) {
|
|
if (auto realm = thread_iter->second.lock()) {
|
|
return realm;
|
|
}
|
|
path_iter->second.erase(thread_iter++);
|
|
}
|
|
|
|
return SharedRealm();
|
|
}
|
|
|
|
void RealmCache::remove(const std::string &path, std::thread::id thread_id)
|
|
{
|
|
std::lock_guard<std::mutex> lock(m_mutex);
|
|
|
|
auto path_iter = m_cache.find(path);
|
|
if (path_iter == m_cache.end()) {
|
|
return;
|
|
}
|
|
|
|
auto thread_iter = path_iter->second.find(thread_id);
|
|
if (thread_iter != path_iter->second.end()) {
|
|
path_iter->second.erase(thread_iter);
|
|
}
|
|
|
|
if (path_iter->second.size() == 0) {
|
|
m_cache.erase(path_iter);
|
|
}
|
|
}
|
|
|
|
void RealmCache::cache_realm(SharedRealm &realm, std::thread::id thread_id)
|
|
{
|
|
std::lock_guard<std::mutex> lock(m_mutex);
|
|
|
|
auto path_iter = m_cache.find(realm->config().path);
|
|
if (path_iter == m_cache.end()) {
|
|
m_cache.emplace(realm->config().path, std::map<std::thread::id, WeakRealm>{{thread_id, realm}});
|
|
}
|
|
else {
|
|
path_iter->second.emplace(thread_id, realm);
|
|
}
|
|
}
|
|
|
|
void RealmCache::clear()
|
|
{
|
|
std::lock_guard<std::mutex> lock(m_mutex);
|
|
for (auto const& path : m_cache) {
|
|
for (auto const& thread : path.second) {
|
|
if (auto realm = thread.second.lock()) {
|
|
realm->close();
|
|
}
|
|
}
|
|
}
|
|
|
|
m_cache.clear();
|
|
}
|