1 ////////////////////////////////////////////////////////////////////////////
3 // Copyright 2015 Realm Inc.
5 // Licensed under the Apache License, Version 2.0 (the "License");
6 // you may not use this file except in compliance with the License.
7 // You may obtain a copy of the License at
9 // http://www.apache.org/licenses/LICENSE-2.0
11 // Unless required by applicable law or agreed to in writing, software
12 // distributed under the License is distributed on an "AS IS" BASIS,
13 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 // See the License for the specific language governing permissions and
15 // limitations under the License.
17 ////////////////////////////////////////////////////////////////////////////
19 #ifndef REALM_REALM_HPP
20 #define REALM_REALM_HPP
22 #include "execution_context_id.hpp"
25 #include <realm/util/optional.hpp>
26 #include <realm/binary_data.hpp>
29 #include <realm/sync/client.hpp>
43 class ThreadSafeReferenceBase;
44 template <typename T> class ThreadSafeReference;
46 template<typename Table> class BasicRow;
47 typedef BasicRow<Table> Row;
48 typedef std::shared_ptr<Realm> SharedRealm;
49 typedef std::weak_ptr<Realm> WeakRealm;
53 class CollectionNotifier;
54 class RealmCoordinator;
58 // How to handle update_schema() being called on a file which has
59 // already been initialized with a different schema
60 enum class SchemaMode : uint8_t {
61 // If the schema version has increased, automatically apply all
62 // changes, then call the migration function.
64 // If the schema version has not changed, verify that the only
65 // changes are to add new tables and add or remove indexes, and then
66 // apply them if so. Does not call the migration function.
68 // This mode does not automatically remove tables which are not
69 // present in the schema that must be manually done in the migration
70 // function, to support sharing a Realm file between processes using
71 // different class subsets.
73 // This mode allows using schemata with different subsets of tables
74 // on different threads, but the tables which are shared must be
78 // Open the file in immutable mode. Schema version must match the
79 // version in the file, and all tables present in the file must
80 // exactly match the specified schema, except for indexes. Tables
81 // are allowed to be missing from the file.
82 // WARNING: This is the original ReadOnly mode.
85 // Open the Realm in read-only mode, transactions are not allowed to
86 // be performed on the Realm instance. The schema of the existing Realm
87 // file won't be changed through this Realm instance. Extra tables and
88 // extra properties are allowed in the existing Realm schema. The
89 // difference of indexes is allowed as well. Other schema differences
90 // than those will cause an exception. This is different from Immutable
91 // mode, sync Realm can be opened with ReadOnly mode. Changes
92 // can be made to the Realm file through another writable Realm instance.
93 // Thus, notifications are also allowed in this mode.
94 // FIXME: Rename this to ReadOnly
95 // WARNING: This is not the original ReadOnly mode. The original ReadOnly
96 // has been renamed to Immutable.
99 // If the schema version matches and the only schema changes are new
100 // tables and indexes being added or removed, apply the changes to
101 // the existing file.
102 // Otherwise delete the file and recreate it from scratch.
103 // The migration function is not used.
105 // This mode allows using schemata with different subsets of tables
106 // on different threads, but the tables which are shared must be
110 // The only changes allowed are to add new tables, add columns to
111 // existing tables, and to add or remove indexes from existing
112 // columns. Extra tables not present in the schema are ignored.
113 // Indexes are only added to or removed from existing columns if the
114 // schema version is greater than the existing one (and unlike other
115 // modes, the schema version is allowed to be less than the existing
117 // The migration function is not used.
119 // This mode allows updating the schema with additive changes even
120 // if the Realm is already open on another thread.
123 // Verify that the schema version has increased, call the migraiton
124 // function, and then verify that the schema now matches.
125 // The migration function is mandatory for this mode.
127 // This mode requires that all threads and processes which open a
128 // file use identical schemata.
132 class Realm : public std::enable_shared_from_this<Realm> {
134 // A callback function to be called during a migration for Automatic and
135 // Manual schema modes. It is passed a SharedRealm at the version before
136 // the migration, the SharedRealm in the migration, and a mutable reference
137 // to the realm's Schema. Updating the schema with changes made within the
138 // migration function is only required if you wish to use the ObjectStore
139 // functions which take a Schema from within the migration function.
140 using MigrationFunction = std::function<void (SharedRealm old_realm, SharedRealm realm, Schema&)>;
142 // A callback function to be called the first time when a schema is created.
143 // It is passed a SharedRealm which is in a write transaction with the schema
144 // initialized. So it is possible to create some initial objects inside the callback
145 // with the given SharedRealm. Those changes will be committed together with the
146 // schema creation in a single transaction.
147 using DataInitializationFunction = std::function<void (SharedRealm realm)>;
149 // A callback function called when opening a SharedRealm when no cached
150 // version of this Realm exists. It is passed the total bytes allocated for
151 // the file (file size) and the total bytes used by data in the file.
152 // Return `true` to indicate that an attempt to compact the file should be made
153 // if it is possible to do so.
154 // Won't compact the file if another process is accessing it.
156 // WARNING / FIXME: compact() should NOT be exposed publicly on Windows
157 // because it's not crash safe! It may corrupt your database if something fails
158 using ShouldCompactOnLaunchFunction = std::function<bool (uint64_t total_bytes, uint64_t used_bytes)>;
161 // Path and binary data are mutually exclusive
163 BinaryData realm_data;
164 // User-supplied encryption key. Must be either empty or 64 bytes.
165 std::vector<char> encryption_key;
167 bool in_memory = false;
168 SchemaMode schema_mode = SchemaMode::Automatic;
170 // Optional schema for the file.
171 // If the schema and schema version are supplied, update_schema() is
172 // called with the supplied schema, version and migration function when
173 // the Realm is actually opened and not just retrieved from the cache
174 util::Optional<Schema> schema;
175 uint64_t schema_version = -1;
176 MigrationFunction migration_function;
178 DataInitializationFunction initialization_function;
180 // A callback function called when opening a SharedRealm when no cached
181 // version of this Realm exists. It is passed the total bytes allocated for
182 // the file (file size) and the total bytes used by data in the file.
183 // Return `true` to indicate that an attempt to compact the file should be made
184 // if it is possible to do so.
185 // Won't compact the file if another process is accessing it.
187 // WARNING / FIXME: compact() should NOT be exposed publicly on Windows
188 // because it's not crash safe! It may corrupt your database if something fails
189 ShouldCompactOnLaunchFunction should_compact_on_launch_function;
191 // WARNING: The original read_only() has been renamed to immutable().
192 bool immutable() const { return schema_mode == SchemaMode::Immutable; }
193 // FIXME: Rename this to read_only().
194 bool read_only_alternative() const { return schema_mode == SchemaMode::ReadOnlyAlternative; }
196 // The following are intended for internal/testing purposes and
197 // should not be publicly exposed in binding APIs
199 // If false, always return a new Realm instance, and don't return
200 // that Realm instance for other requests for a cached Realm. Useful
201 // for dynamic Realms and for tests that need multiple instances on
204 // Throw an exception rather than automatically upgrading the file
205 // format. Used by the browser to warn the user that it'll modify
207 bool disable_format_upgrade = false;
208 // Disable the background worker thread for producing change
209 // notifications. Useful for tests for those notifications so that
210 // everything can be done deterministically on one thread, and
211 // speeds up tests that don't need notifications.
212 bool automatic_change_notifications = true;
214 // The identifier of the abstract execution context in which this Realm will be used.
215 // If unset, the current thread's identifier will be used to identify the execution context.
216 util::Optional<AbstractExecutionContextID> execution_context;
218 /// A data structure storing data used to configure the Realm for sync support.
219 std::shared_ptr<SyncConfig> sync_config;
221 // FIXME: Realm Java manages sync at the Java level, so it needs to create Realms using the sync history
223 bool force_sync_history = false;
226 // Get a cached Realm or create a new one if no cached copies exists
227 // Caching is done by path - mismatches for in_memory, schema mode or
228 // encryption key will raise an exception.
229 static SharedRealm get_shared_realm(Config config);
231 // Updates a Realm to a given schema, using the Realm's pre-set schema mode.
232 void update_schema(Schema schema, uint64_t version=0,
233 MigrationFunction migration_function=nullptr,
234 DataInitializationFunction initialization_function=nullptr,
235 bool in_transaction=false);
237 // Set the schema used for this Realm, but do not update the file's schema
238 // if it is not compatible (and instead throw an error).
239 // Cannot be called multiple times on a single Realm instance or an instance
240 // which has already had update_schema() called on it.
241 void set_schema_subset(Schema schema);
243 // Read the schema version from the file specified by the given config, or
244 // ObjectStore::NotVersioned if it does not exist
245 static uint64_t get_schema_version(Config const& config);
247 Config const& config() const { return m_config; }
248 Schema const& schema() const { return m_schema; }
249 uint64_t schema_version() const { return m_schema_version; }
251 void begin_transaction();
252 void commit_transaction();
253 void cancel_transaction();
254 bool is_in_transaction() const noexcept;
255 bool is_in_read_transaction() const { return !!m_group; }
257 bool is_in_migration() const noexcept { return m_in_migration; }
260 void set_auto_refresh(bool auto_refresh) { m_auto_refresh = auto_refresh; }
261 bool auto_refresh() const { return m_auto_refresh; }
266 // WARNING / FIXME: compact() should NOT be exposed publicly on Windows
267 // because it's not crash safe! It may corrupt your database if something fails
269 void write_copy(StringData path, BinaryData encryption_key);
270 OwnedBinaryData write_copy();
272 void verify_thread() const;
273 void verify_in_write() const;
274 void verify_open() const;
276 bool can_deliver_notifications() const noexcept;
278 // Close this Realm and remove it from the cache. Continuing to use a
279 // Realm after closing it will throw ClosedRealmException
281 bool is_closed() const { return !m_read_only_group && !m_shared_group; }
283 // returns the file format version upgraded from if an upgrade took place
284 util::Optional<int> file_format_upgraded_from_version() const;
286 Realm(const Realm&) = delete;
287 Realm& operator=(const Realm&) = delete;
288 Realm(Realm&&) = delete;
289 Realm& operator=(Realm&&) = delete;
292 // Construct a thread safe reference, pinning the version in the process.
293 template <typename T>
294 ThreadSafeReference<T> obtain_thread_safe_reference(T const& value);
296 // Advances the read transaction to the latest version, resolving the thread safe reference and unpinning the
297 // version in the process.
298 template <typename T>
299 T resolve_thread_safe_reference(ThreadSafeReference<T> reference);
301 static SharedRealm make_shared_realm(Config config, std::shared_ptr<_impl::RealmCoordinator> coordinator = nullptr) {
302 struct make_shared_enabler : public Realm {
303 make_shared_enabler(Config config, std::shared_ptr<_impl::RealmCoordinator> coordinator)
304 : Realm(std::move(config), std::move(coordinator)) { }
306 return std::make_shared<make_shared_enabler>(std::move(config), std::move(coordinator));
309 // Expose some internal functionality to other parts of the ObjectStore
310 // without making it public to everyone
312 friend class _impl::CollectionNotifier;
313 friend class _impl::RealmCoordinator;
314 friend class ThreadSafeReferenceBase;
315 friend class GlobalNotifier;
316 friend class TestHelper;
318 // ResultsNotifier and ListNotifier need access to the SharedGroup
319 // to be able to call the handover functions, which are not very wrappable
320 static const std::unique_ptr<SharedGroup>& get_shared_group(Realm& realm) { return realm.m_shared_group; }
322 // CollectionNotifier needs to be able to access the owning
323 // coordinator to wake up the worker thread when a callback is
324 // added, and coordinators need to be able to get themselves from a Realm
325 static _impl::RealmCoordinator& get_coordinator(Realm& realm) { return *realm.m_coordinator; }
327 static void begin_read(Realm&, VersionID);
330 static void open_with_config(const Config& config,
331 std::unique_ptr<Replication>& history,
332 std::unique_ptr<SharedGroup>& shared_group,
333 std::unique_ptr<Group>& read_only_group,
337 // `enable_shared_from_this` is unsafe with public constructors; use `make_shared_realm` instead
338 Realm(Config config, std::shared_ptr<_impl::RealmCoordinator> coordinator);
341 AnyExecutionContextID m_execution_context;
342 bool m_auto_refresh = true;
344 std::unique_ptr<Replication> m_history;
345 std::unique_ptr<SharedGroup> m_shared_group;
346 std::unique_ptr<Group> m_read_only_group;
348 Group *m_group = nullptr;
350 uint64_t m_schema_version;
352 util::Optional<Schema> m_new_schema;
353 uint64_t m_schema_transaction_version = -1;
355 // FIXME: this should be a Dynamic schema mode instead, but only once
356 // that's actually fully working
357 bool m_dynamic_schema = true;
359 std::shared_ptr<_impl::RealmCoordinator> m_coordinator;
361 // File format versions populated when a file format upgrade takes place during realm opening
362 int upgrade_initial_version = 0, upgrade_final_version = 0;
364 // True while sending the notifications caused by advancing the read
365 // transaction version, to avoid recursive notifications where possible
366 bool m_is_sending_notifications = false;
368 // True while we're performing a schema migration via this Realm instance
369 // to allow for different behavior (such as allowing modifications to
370 // primary key values)
371 bool m_in_migration = false;
373 void begin_read(VersionID);
375 void set_schema(Schema const& reference, Schema schema);
376 bool reset_file(Schema& schema, std::vector<SchemaChange>& changes_required);
377 bool schema_change_needs_write_transaction(Schema& schema, std::vector<SchemaChange>& changes, uint64_t version);
378 Schema get_full_schema();
380 // Ensure that m_schema and m_schema_version match that of the current
381 // version of the file
382 void read_schema_from_group_if_needed();
384 void add_schema_change_handler();
385 void cache_new_schema();
386 void notify_schema_changed();
389 std::unique_ptr<BindingContext> m_binding_context;
394 Replication *history() { return m_history.get(); }
396 friend class _impl::RealmFriend;
399 class RealmFileException : public std::runtime_error {
402 /** Thrown for any I/O related exception scenarios when a realm is opened. */
404 /** Thrown if the history type of the on-disk Realm is unexpected or incompatible. */
406 /** Thrown if the user does not have permission to open or create
407 the specified file in the specified access mode when the realm is opened. */
409 /** Thrown if create_Always was specified and the file did already exist when the realm is opened. */
411 /** Thrown if no_create was specified and the file was not found when the realm is opened. */
413 /** Thrown if the database file is currently open in another
414 process which cannot share with the current process due to an
415 architecture mismatch. */
416 IncompatibleLockFile,
417 /** Thrown if the file needs to be upgraded to a new format, but upgrades have been explicitly disabled. */
418 FormatUpgradeRequired,
419 /** Thrown if the local copy of a synced Realm file was created using an incompatible version of Realm.
420 The specified path is where the local file was moved for recovery. */
421 IncompatibleSyncedRealm,
423 RealmFileException(Kind kind, std::string path, std::string message, std::string underlying)
424 : std::runtime_error(std::move(message)), m_kind(kind), m_path(std::move(path)), m_underlying(std::move(underlying)) {}
425 Kind kind() const { return m_kind; }
426 const std::string& path() const { return m_path; }
427 const std::string& underlying() const { return m_underlying; }
432 std::string m_underlying;
435 class MismatchedConfigException : public std::logic_error {
437 MismatchedConfigException(StringData message, StringData path);
440 class MismatchedRealmException : public std::logic_error {
442 MismatchedRealmException(StringData message);
445 class InvalidTransactionException : public std::logic_error {
447 InvalidTransactionException(std::string message) : std::logic_error(message) {}
450 class IncorrectThreadException : public std::logic_error {
452 IncorrectThreadException() : std::logic_error("Realm accessed from incorrect thread.") {}
455 class ClosedRealmException : public std::logic_error {
457 ClosedRealmException() : std::logic_error("Cannot access realm that has been closed.") {}
460 class UninitializedRealmException : public std::runtime_error {
462 UninitializedRealmException(std::string message) : std::runtime_error(message) {}
465 class InvalidEncryptionKeyException : public std::logic_error {
467 InvalidEncryptionKeyException() : std::logic_error("Encryption key must be 64 bytes.") {}
470 // FIXME Those are exposed for Java async queries, mainly because of handover related methods.
471 class _impl::RealmFriend {
473 static SharedGroup& get_shared_group(Realm& realm);
474 static Group& read_group_to(Realm& realm, VersionID version);
479 #endif /* defined(REALM_REALM_HPP) */