545 lines
22 KiB
C++
545 lines
22 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.
|
|
//
|
|
////////////////////////////////////////////////////////////////////////////
|
|
|
|
#ifndef REALM_REALM_HPP
|
|
#define REALM_REALM_HPP
|
|
|
|
#include "execution_context_id.hpp"
|
|
#include "schema.hpp"
|
|
|
|
#include <realm/util/optional.hpp>
|
|
#include <realm/binary_data.hpp>
|
|
|
|
#if REALM_ENABLE_SYNC
|
|
#include <realm/sync/client.hpp>
|
|
#endif
|
|
|
|
#include <memory>
|
|
|
|
namespace realm {
|
|
class AsyncOpenTask;
|
|
class AuditInterface;
|
|
class BindingContext;
|
|
class Group;
|
|
class Realm;
|
|
class Replication;
|
|
class SharedGroup;
|
|
class StringData;
|
|
class Table;
|
|
struct SyncConfig;
|
|
class ThreadSafeReferenceBase;
|
|
template <typename T> class ThreadSafeReference;
|
|
struct VersionID;
|
|
template<typename Table> class BasicRow;
|
|
typedef BasicRow<Table> Row;
|
|
template<typename> class BasicRowExpr;
|
|
using RowExpr = BasicRowExpr<Table>;
|
|
typedef std::shared_ptr<Realm> SharedRealm;
|
|
typedef std::weak_ptr<Realm> WeakRealm;
|
|
|
|
namespace _impl {
|
|
class AnyHandover;
|
|
class CollectionNotifier;
|
|
class PartialSyncHelper;
|
|
class RealmCoordinator;
|
|
class RealmFriend;
|
|
}
|
|
namespace sync {
|
|
struct PermissionsCache;
|
|
struct TableInfoCache;
|
|
}
|
|
|
|
// How to handle update_schema() being called on a file which has
|
|
// already been initialized with a different schema
|
|
enum class SchemaMode : uint8_t {
|
|
// If the schema version has increased, automatically apply all
|
|
// changes, then call the migration function.
|
|
//
|
|
// If the schema version has not changed, verify that the only
|
|
// changes are to add new tables and add or remove indexes, and then
|
|
// apply them if so. Does not call the migration function.
|
|
//
|
|
// This mode does not automatically remove tables which are not
|
|
// present in the schema that must be manually done in the migration
|
|
// function, to support sharing a Realm file between processes using
|
|
// different class subsets.
|
|
//
|
|
// This mode allows using schemata with different subsets of tables
|
|
// on different threads, but the tables which are shared must be
|
|
// identical.
|
|
Automatic,
|
|
|
|
// Open the file in immutable mode. Schema version must match the
|
|
// version in the file, and all tables present in the file must
|
|
// exactly match the specified schema, except for indexes. Tables
|
|
// are allowed to be missing from the file.
|
|
// WARNING: This is the original ReadOnly mode.
|
|
Immutable,
|
|
|
|
// Open the Realm in read-only mode, transactions are not allowed to
|
|
// be performed on the Realm instance. The schema of the existing Realm
|
|
// file won't be changed through this Realm instance. Extra tables and
|
|
// extra properties are allowed in the existing Realm schema. The
|
|
// difference of indexes is allowed as well. Other schema differences
|
|
// than those will cause an exception. This is different from Immutable
|
|
// mode, sync Realm can be opened with ReadOnly mode. Changes
|
|
// can be made to the Realm file through another writable Realm instance.
|
|
// Thus, notifications are also allowed in this mode.
|
|
// FIXME: Rename this to ReadOnly
|
|
// WARNING: This is not the original ReadOnly mode. The original ReadOnly
|
|
// has been renamed to Immutable.
|
|
ReadOnlyAlternative,
|
|
|
|
// If the schema version matches and the only schema changes are new
|
|
// tables and indexes being added or removed, apply the changes to
|
|
// the existing file.
|
|
// Otherwise delete the file and recreate it from scratch.
|
|
// The migration function is not used.
|
|
//
|
|
// This mode allows using schemata with different subsets of tables
|
|
// on different threads, but the tables which are shared must be
|
|
// identical.
|
|
ResetFile,
|
|
|
|
// The only changes allowed are to add new tables, add columns to
|
|
// existing tables, and to add or remove indexes from existing
|
|
// columns. Extra tables not present in the schema are ignored.
|
|
// Indexes are only added to or removed from existing columns if the
|
|
// schema version is greater than the existing one (and unlike other
|
|
// modes, the schema version is allowed to be less than the existing
|
|
// one).
|
|
// The migration function is not used.
|
|
//
|
|
// This mode allows updating the schema with additive changes even
|
|
// if the Realm is already open on another thread.
|
|
Additive,
|
|
|
|
// Verify that the schema version has increased, call the migraiton
|
|
// function, and then verify that the schema now matches.
|
|
// The migration function is mandatory for this mode.
|
|
//
|
|
// This mode requires that all threads and processes which open a
|
|
// file use identical schemata.
|
|
Manual
|
|
};
|
|
|
|
enum class ComputedPrivileges : uint8_t {
|
|
None = 0,
|
|
|
|
Read = (1 << 0),
|
|
Update = (1 << 1),
|
|
Delete = (1 << 2),
|
|
SetPermissions = (1 << 3),
|
|
Query = (1 << 4),
|
|
Create = (1 << 5),
|
|
ModifySchema = (1 << 6),
|
|
|
|
AllRealm = Read | Update | SetPermissions | ModifySchema,
|
|
AllClass = Read | Update | Create | Query | SetPermissions,
|
|
AllObject = Read | Update | Delete | SetPermissions,
|
|
All = (1 << 7) - 1
|
|
};
|
|
|
|
class Realm : public std::enable_shared_from_this<Realm> {
|
|
public:
|
|
// A callback function to be called during a migration for Automatic and
|
|
// Manual schema modes. It is passed a SharedRealm at the version before
|
|
// the migration, the SharedRealm in the migration, and a mutable reference
|
|
// to the realm's Schema. Updating the schema with changes made within the
|
|
// migration function is only required if you wish to use the ObjectStore
|
|
// functions which take a Schema from within the migration function.
|
|
using MigrationFunction = std::function<void (SharedRealm old_realm, SharedRealm realm, Schema&)>;
|
|
|
|
// A callback function to be called the first time when a schema is created.
|
|
// It is passed a SharedRealm which is in a write transaction with the schema
|
|
// initialized. So it is possible to create some initial objects inside the callback
|
|
// with the given SharedRealm. Those changes will be committed together with the
|
|
// schema creation in a single transaction.
|
|
using DataInitializationFunction = std::function<void (SharedRealm realm)>;
|
|
|
|
// A callback function called when opening a SharedRealm when no cached
|
|
// version of this Realm exists. It is passed the total bytes allocated for
|
|
// the file (file size) and the total bytes used by data in the file.
|
|
// Return `true` to indicate that an attempt to compact the file should be made
|
|
// if it is possible to do so.
|
|
// Won't compact the file if another process is accessing it.
|
|
//
|
|
// WARNING / FIXME: compact() should NOT be exposed publicly on Windows
|
|
// because it's not crash safe! It may corrupt your database if something fails
|
|
using ShouldCompactOnLaunchFunction = std::function<bool (uint64_t total_bytes, uint64_t used_bytes)>;
|
|
|
|
struct Config {
|
|
// Path and binary data are mutually exclusive
|
|
std::string path;
|
|
BinaryData realm_data;
|
|
// User-supplied encryption key. Must be either empty or 64 bytes.
|
|
std::vector<char> encryption_key;
|
|
|
|
// Core and Object Store will in some cases need to create named pipes alongside the Realm file.
|
|
// But on some filesystems this can be a problem (e.g. external storage on Android that uses FAT32).
|
|
// In order to work around this, a separate path can be specified for these files.
|
|
std::string fifo_files_fallback_path;
|
|
|
|
bool in_memory = false;
|
|
SchemaMode schema_mode = SchemaMode::Automatic;
|
|
|
|
// Optional schema for the file.
|
|
// If the schema and schema version are supplied, update_schema() is
|
|
// called with the supplied schema, version and migration function when
|
|
// the Realm is actually opened and not just retrieved from the cache
|
|
util::Optional<Schema> schema;
|
|
uint64_t schema_version = -1;
|
|
MigrationFunction migration_function;
|
|
|
|
DataInitializationFunction initialization_function;
|
|
|
|
// A callback function called when opening a SharedRealm when no cached
|
|
// version of this Realm exists. It is passed the total bytes allocated for
|
|
// the file (file size) and the total bytes used by data in the file.
|
|
// Return `true` to indicate that an attempt to compact the file should be made
|
|
// if it is possible to do so.
|
|
// Won't compact the file if another process is accessing it.
|
|
//
|
|
// WARNING / FIXME: compact() should NOT be exposed publicly on Windows
|
|
// because it's not crash safe! It may corrupt your database if something fails
|
|
ShouldCompactOnLaunchFunction should_compact_on_launch_function;
|
|
|
|
// WARNING: The original read_only() has been renamed to immutable().
|
|
bool immutable() const { return schema_mode == SchemaMode::Immutable; }
|
|
// FIXME: Rename this to read_only().
|
|
bool read_only_alternative() const { return schema_mode == SchemaMode::ReadOnlyAlternative; }
|
|
|
|
// The following are intended for internal/testing purposes and
|
|
// should not be publicly exposed in binding APIs
|
|
|
|
// If false, always return a new Realm instance, and don't return
|
|
// that Realm instance for other requests for a cached Realm. Useful
|
|
// for dynamic Realms and for tests that need multiple instances on
|
|
// one thread
|
|
bool cache = true;
|
|
// Throw an exception rather than automatically upgrading the file
|
|
// format. Used by the browser to warn the user that it'll modify
|
|
// the file.
|
|
bool disable_format_upgrade = false;
|
|
// Disable the background worker thread for producing change
|
|
// notifications. Useful for tests for those notifications so that
|
|
// everything can be done deterministically on one thread, and
|
|
// speeds up tests that don't need notifications.
|
|
bool automatic_change_notifications = true;
|
|
|
|
// The identifier of the abstract execution context in which this Realm will be used.
|
|
// If unset, the current thread's identifier will be used to identify the execution context.
|
|
util::Optional<AbstractExecutionContextID> execution_context;
|
|
|
|
/// A data structure storing data used to configure the Realm for sync support.
|
|
std::shared_ptr<SyncConfig> sync_config;
|
|
|
|
// Open the Realm using the sync history mode even if a sync
|
|
// configuration is not supplied.
|
|
bool force_sync_history = false;
|
|
|
|
// A factory function which produces an audit implementation.
|
|
std::function<std::shared_ptr<AuditInterface>()> audit_factory;
|
|
};
|
|
|
|
// Get a cached Realm or create a new one if no cached copies exists
|
|
// Caching is done by path - mismatches for in_memory, schema mode or
|
|
// encryption key will raise an exception.
|
|
static SharedRealm get_shared_realm(Config config);
|
|
|
|
// Get a Realm for the given execution context (or current thread if `none`)
|
|
// from the thread safe reference. May return a cached Realm or create a new one.
|
|
static SharedRealm get_shared_realm(ThreadSafeReference<Realm>, util::Optional<AbstractExecutionContextID> = util::none);
|
|
|
|
#if REALM_ENABLE_SYNC
|
|
// Open a synchronized Realm and make sure it is fully up to date before
|
|
// returning it.
|
|
//
|
|
// It is possible to both cancel the download and listen to download progress
|
|
// using the `AsyncOpenTask` returned. Note that the download doesn't actually
|
|
// start until you call `AsyncOpenTask::start(callback)`
|
|
static std::shared_ptr<AsyncOpenTask> get_synchronized_realm(Config config);
|
|
#endif
|
|
|
|
// Updates a Realm to a given schema, using the Realm's pre-set schema mode.
|
|
void update_schema(Schema schema, uint64_t version=0,
|
|
MigrationFunction migration_function=nullptr,
|
|
DataInitializationFunction initialization_function=nullptr,
|
|
bool in_transaction=false);
|
|
|
|
// Set the schema used for this Realm, but do not update the file's schema
|
|
// if it is not compatible (and instead throw an error).
|
|
// Cannot be called multiple times on a single Realm instance or an instance
|
|
// which has already had update_schema() called on it.
|
|
void set_schema_subset(Schema schema);
|
|
|
|
// Read the schema version from the file specified by the given config, or
|
|
// ObjectStore::NotVersioned if it does not exist
|
|
static uint64_t get_schema_version(Config const& config);
|
|
|
|
Config const& config() const { return m_config; }
|
|
Schema const& schema() const { return m_schema; }
|
|
uint64_t schema_version() const { return m_schema_version; }
|
|
|
|
// Returns `true` if this Realm is a Partially synchronized Realm.
|
|
bool is_partial() const noexcept;
|
|
|
|
void begin_transaction();
|
|
void commit_transaction();
|
|
void cancel_transaction();
|
|
bool is_in_transaction() const noexcept;
|
|
|
|
bool is_in_read_transaction() const { return !!m_group; }
|
|
VersionID read_transaction_version() const;
|
|
Group& read_group();
|
|
|
|
bool is_in_migration() const noexcept { return m_in_migration; }
|
|
|
|
bool refresh();
|
|
void set_auto_refresh(bool auto_refresh) { m_auto_refresh = auto_refresh; }
|
|
bool auto_refresh() const { return m_auto_refresh; }
|
|
void notify();
|
|
|
|
void invalidate();
|
|
|
|
// WARNING / FIXME: compact() should NOT be exposed publicly on Windows
|
|
// because it's not crash safe! It may corrupt your database if something fails
|
|
bool compact();
|
|
void write_copy(StringData path, BinaryData encryption_key);
|
|
OwnedBinaryData write_copy();
|
|
|
|
void verify_thread() const;
|
|
void verify_in_write() const;
|
|
void verify_open() const;
|
|
|
|
bool can_deliver_notifications() const noexcept;
|
|
|
|
// Close this Realm and remove it from the cache. Continuing to use a
|
|
// Realm after closing it will throw ClosedRealmException
|
|
void close();
|
|
bool is_closed() const { return !m_read_only_group && !m_shared_group; }
|
|
|
|
// returns the file format version upgraded from if an upgrade took place
|
|
util::Optional<int> file_format_upgraded_from_version() const;
|
|
|
|
Realm(const Realm&) = delete;
|
|
Realm& operator=(const Realm&) = delete;
|
|
Realm(Realm&&) = delete;
|
|
Realm& operator=(Realm&&) = delete;
|
|
~Realm();
|
|
|
|
// Construct a thread safe reference, pinning the version in the process.
|
|
template <typename T>
|
|
ThreadSafeReference<T> obtain_thread_safe_reference(T const& value);
|
|
|
|
// Advances the read transaction to the latest version, resolving the thread safe reference and unpinning the
|
|
// version in the process.
|
|
template <typename T>
|
|
T resolve_thread_safe_reference(ThreadSafeReference<T> reference);
|
|
|
|
ComputedPrivileges get_privileges();
|
|
ComputedPrivileges get_privileges(StringData object_type);
|
|
ComputedPrivileges get_privileges(RowExpr row);
|
|
|
|
AuditInterface* audit_context() const noexcept;
|
|
|
|
static SharedRealm make_shared_realm(Config config, std::shared_ptr<_impl::RealmCoordinator> coordinator = nullptr) {
|
|
struct make_shared_enabler : public Realm {
|
|
make_shared_enabler(Config config, std::shared_ptr<_impl::RealmCoordinator> coordinator)
|
|
: Realm(std::move(config), std::move(coordinator)) { }
|
|
};
|
|
return std::make_shared<make_shared_enabler>(std::move(config), std::move(coordinator));
|
|
}
|
|
|
|
// Expose some internal functionality to other parts of the ObjectStore
|
|
// without making it public to everyone
|
|
class Internal {
|
|
friend class _impl::CollectionNotifier;
|
|
friend class _impl::PartialSyncHelper;
|
|
friend class _impl::RealmCoordinator;
|
|
friend class ThreadSafeReferenceBase;
|
|
friend class GlobalNotifier;
|
|
friend class TestHelper;
|
|
|
|
// ResultsNotifier and ListNotifier need access to the SharedGroup
|
|
// to be able to call the handover functions, which are not very wrappable
|
|
static const std::unique_ptr<SharedGroup>& get_shared_group(Realm& realm) { return realm.m_shared_group; }
|
|
|
|
// CollectionNotifier needs to be able to access the owning
|
|
// coordinator to wake up the worker thread when a callback is
|
|
// added, and coordinators need to be able to get themselves from a Realm
|
|
static _impl::RealmCoordinator& get_coordinator(Realm& realm) { return *realm.m_coordinator; }
|
|
|
|
static void begin_read(Realm&, VersionID);
|
|
};
|
|
|
|
static void 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);
|
|
|
|
private:
|
|
// `enable_shared_from_this` is unsafe with public constructors; use `make_shared_realm` instead
|
|
Realm(Config config, std::shared_ptr<_impl::RealmCoordinator> coordinator);
|
|
|
|
Config m_config;
|
|
AnyExecutionContextID m_execution_context;
|
|
bool m_auto_refresh = true;
|
|
|
|
std::unique_ptr<Replication> m_history;
|
|
std::unique_ptr<SharedGroup> m_shared_group;
|
|
std::unique_ptr<Group> m_read_only_group;
|
|
|
|
Group *m_group = nullptr;
|
|
|
|
uint64_t m_schema_version;
|
|
Schema m_schema;
|
|
util::Optional<Schema> m_new_schema;
|
|
uint64_t m_schema_transaction_version = -1;
|
|
|
|
// FIXME: this should be a Dynamic schema mode instead, but only once
|
|
// that's actually fully working
|
|
bool m_dynamic_schema = true;
|
|
|
|
std::shared_ptr<_impl::RealmCoordinator> m_coordinator;
|
|
std::unique_ptr<sync::TableInfoCache> m_table_info_cache;
|
|
std::unique_ptr<sync::PermissionsCache> m_permissions_cache;
|
|
|
|
// File format versions populated when a file format upgrade takes place during realm opening
|
|
int upgrade_initial_version = 0, upgrade_final_version = 0;
|
|
|
|
// True while sending the notifications caused by advancing the read
|
|
// transaction version, to avoid recursive notifications where possible
|
|
bool m_is_sending_notifications = false;
|
|
|
|
// True while we're performing a schema migration via this Realm instance
|
|
// to allow for different behavior (such as allowing modifications to
|
|
// primary key values)
|
|
bool m_in_migration = false;
|
|
|
|
void begin_read(VersionID);
|
|
|
|
void set_schema(Schema const& reference, Schema schema);
|
|
bool reset_file(Schema& schema, std::vector<SchemaChange>& changes_required);
|
|
bool schema_change_needs_write_transaction(Schema& schema, std::vector<SchemaChange>& changes, uint64_t version);
|
|
Schema get_full_schema();
|
|
|
|
// Ensure that m_schema and m_schema_version match that of the current
|
|
// version of the file
|
|
void read_schema_from_group_if_needed();
|
|
|
|
void add_schema_change_handler();
|
|
void cache_new_schema();
|
|
void translate_schema_error();
|
|
void notify_schema_changed();
|
|
|
|
bool init_permission_cache();
|
|
void invalidate_permission_cache();
|
|
|
|
public:
|
|
std::unique_ptr<BindingContext> m_binding_context;
|
|
|
|
// FIXME: This is currently needed by the adapter to get access to its changeset cooker
|
|
Replication* history() { return m_history.get(); }
|
|
|
|
friend class _impl::RealmFriend;
|
|
};
|
|
|
|
class RealmFileException : public std::runtime_error {
|
|
public:
|
|
enum class Kind {
|
|
/** Thrown for any I/O related exception scenarios when a realm is opened. */
|
|
AccessError,
|
|
/** Thrown if the history type of the on-disk Realm is unexpected or incompatible. */
|
|
BadHistoryError,
|
|
/** Thrown if the user does not have permission to open or create
|
|
the specified file in the specified access mode when the realm is opened. */
|
|
PermissionDenied,
|
|
/** Thrown if create_Always was specified and the file did already exist when the realm is opened. */
|
|
Exists,
|
|
/** Thrown if no_create was specified and the file was not found when the realm is opened. */
|
|
NotFound,
|
|
/** Thrown if the database file is currently open in another
|
|
process which cannot share with the current process due to an
|
|
architecture mismatch. */
|
|
IncompatibleLockFile,
|
|
/** Thrown if the file needs to be upgraded to a new format, but upgrades have been explicitly disabled. */
|
|
FormatUpgradeRequired,
|
|
/** Thrown if the local copy of a synced Realm file was created using an incompatible version of Realm.
|
|
The specified path is where the local file was moved for recovery. */
|
|
IncompatibleSyncedRealm,
|
|
};
|
|
RealmFileException(Kind kind, std::string path, std::string message, std::string underlying)
|
|
: std::runtime_error(std::move(message)), m_kind(kind), m_path(std::move(path)), m_underlying(std::move(underlying)) {}
|
|
Kind kind() const { return m_kind; }
|
|
const std::string& path() const { return m_path; }
|
|
const std::string& underlying() const { return m_underlying; }
|
|
|
|
private:
|
|
Kind m_kind;
|
|
std::string m_path;
|
|
std::string m_underlying;
|
|
};
|
|
|
|
class MismatchedConfigException : public std::logic_error {
|
|
public:
|
|
MismatchedConfigException(StringData message, StringData path);
|
|
};
|
|
|
|
class MismatchedRealmException : public std::logic_error {
|
|
public:
|
|
MismatchedRealmException(StringData message);
|
|
};
|
|
|
|
class InvalidTransactionException : public std::logic_error {
|
|
public:
|
|
InvalidTransactionException(std::string message) : std::logic_error(message) {}
|
|
};
|
|
|
|
class IncorrectThreadException : public std::logic_error {
|
|
public:
|
|
IncorrectThreadException() : std::logic_error("Realm accessed from incorrect thread.") {}
|
|
};
|
|
|
|
class ClosedRealmException : public std::logic_error {
|
|
public:
|
|
ClosedRealmException() : std::logic_error("Cannot access realm that has been closed.") {}
|
|
};
|
|
|
|
class UninitializedRealmException : public std::runtime_error {
|
|
public:
|
|
UninitializedRealmException(std::string message) : std::runtime_error(message) {}
|
|
};
|
|
|
|
class InvalidEncryptionKeyException : public std::logic_error {
|
|
public:
|
|
InvalidEncryptionKeyException() : std::logic_error("Encryption key must be 64 bytes.") {}
|
|
};
|
|
|
|
// FIXME Those are exposed for Java async queries, mainly because of handover related methods.
|
|
class _impl::RealmFriend {
|
|
public:
|
|
static SharedGroup& get_shared_group(Realm& realm);
|
|
static Group& read_group_to(Realm& realm, VersionID version);
|
|
};
|
|
|
|
} // namespace realm
|
|
|
|
#endif /* defined(REALM_REALM_HPP) */
|