• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

realm / realm-core / 1660

11 Sep 2023 02:28PM UTC coverage: 91.236% (+0.02%) from 91.215%
1660

push

Evergreen

GitHub
make DB::get_number_of_versions() reflect the number of live versions (#6960)

95844 of 175764 branches covered (0.0%)

21 of 21 new or added lines in 2 files covered. (100.0%)

83 existing lines in 11 files now uncovered.

233544 of 255978 relevant lines covered (91.24%)

8147599.88 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

94.59
/src/realm/object-store/shared_realm.cpp
1
////////////////////////////////////////////////////////////////////////////
2
//
3
// Copyright 2015 Realm Inc.
4
//
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
8
//
9
// http://www.apache.org/licenses/LICENSE-2.0
10
//
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.
16
//
17
////////////////////////////////////////////////////////////////////////////
18

19
#include <realm/object-store/shared_realm.hpp>
20

21
#include <realm/object-store/impl/collection_notifier.hpp>
22
#include <realm/object-store/impl/realm_coordinator.hpp>
23
#include <realm/object-store/impl/transact_log_handler.hpp>
24

25
#include <realm/object-store/audit.hpp>
26
#include <realm/object-store/binding_context.hpp>
27
#include <realm/object-store/list.hpp>
28
#include <realm/object-store/object.hpp>
29
#include <realm/object-store/object_schema.hpp>
30
#include <realm/object-store/object_store.hpp>
31
#include <realm/object-store/results.hpp>
32
#include <realm/object-store/schema.hpp>
33
#include <realm/object-store/thread_safe_reference.hpp>
34

35
#include <realm/object-store/util/scheduler.hpp>
36

37
#include <realm/db.hpp>
38
#include <realm/util/fifo_helper.hpp>
39
#include <realm/util/file.hpp>
40
#include <realm/util/scope_exit.hpp>
41

42
#if REALM_ENABLE_SYNC
43
#include <realm/object-store/sync/impl/sync_file.hpp>
44
#include <realm/object-store/sync/sync_manager.hpp>
45
#include <realm/object-store/sync/sync_user.hpp>
46
#include <realm/object-store/sync/sync_session.hpp>
47

48
#include <realm/sync/config.hpp>
49
#include <realm/sync/history.hpp>
50
#include <realm/sync/noinst/client_history_impl.hpp>
51
#include <realm/history.hpp>
52
#endif
53

54
#include <thread>
55

56
using namespace realm;
57
using namespace realm::_impl;
58

59
namespace {
60
class CountGuard {
61
public:
62
    CountGuard(size_t& count)
63
        : m_count(count)
64
    {
389,521✔
65
        ++m_count;
389,521✔
66
    }
389,521✔
67
    ~CountGuard()
68
    {
389,556✔
69
        --m_count;
389,556✔
70
    }
389,556✔
71

72
private:
73
    size_t& m_count;
74
};
75
} // namespace
76

77
Realm::Realm(Config config, util::Optional<VersionID> version, std::shared_ptr<_impl::RealmCoordinator> coordinator,
78
             MakeSharedTag)
79
    : m_config(std::move(config))
80
    , m_frozen_version(version)
81
    , m_scheduler(m_config.scheduler)
82
{
59,028✔
83
    if (version) {
59,028✔
84
        m_auto_refresh = false;
992✔
85
        REALM_ASSERT(*version != VersionID());
992✔
86
    }
992✔
87
    else if (!coordinator->get_cached_schema(m_schema, m_schema_version, m_schema_transaction_version)) {
58,036✔
88
        m_transaction = coordinator->begin_read();
43,240✔
89
        read_schema_from_group_if_needed();
43,240✔
90
        coordinator->cache_schema(m_schema, m_schema_version, m_schema_transaction_version);
43,240✔
91
        m_transaction = nullptr;
43,240✔
92
    }
43,240✔
93
    m_coordinator = std::move(coordinator);
59,028✔
94
}
59,028✔
95

96
Realm::~Realm()
97
{
59,028✔
98
    if (m_transaction) {
59,028✔
99
        // Wait for potential syncing to finish
26,171✔
100
        m_transaction->prepare_for_close();
53,181✔
101
        call_completion_callbacks();
53,181✔
102
    }
53,181✔
103

29,073✔
104
    if (m_coordinator) {
59,028✔
105
        m_coordinator->unregister_realm(this);
55,015✔
106
    }
55,015✔
107
}
59,028✔
108

109
Group& Realm::read_group()
110
{
790,248✔
111
    return transaction();
790,248✔
112
}
790,248✔
113

114
Transaction& Realm::transaction()
115
{
1,943,993✔
116
    verify_open();
1,943,993✔
117
    if (!m_transaction)
1,943,993✔
118
        begin_read(m_frozen_version.value_or(VersionID{}));
113,549✔
119
    return *m_transaction;
1,943,993✔
120
}
1,943,993✔
121

122
Transaction& Realm::transaction() const
123
{
595,641✔
124
    // one day we should change the way we use constness
291,719✔
125
    Realm* nc_realm = const_cast<Realm*>(this);
595,641✔
126
    return nc_realm->transaction();
595,641✔
127
}
595,641✔
128

129
std::shared_ptr<Transaction> Realm::transaction_ref()
130
{
115,258✔
131
    return m_transaction;
115,258✔
132
}
115,258✔
133

134
std::shared_ptr<Transaction> Realm::duplicate() const
135
{
144✔
136
    auto version = read_transaction_version(); // does the validity check first
144✔
137
    return m_coordinator->begin_read(version, is_frozen());
144✔
138
}
144✔
139

140
std::shared_ptr<DB>& Realm::Internal::get_db(Realm& realm)
141
{
6,504✔
142
    return realm.m_coordinator->m_db;
6,504✔
143
}
6,504✔
144

145
void Realm::Internal::begin_read(Realm& realm, VersionID version_id)
146
{
46✔
147
    realm.begin_read(version_id);
46✔
148
}
46✔
149

150
void Realm::begin_read(VersionID version_id)
151
{
113,595✔
152
    REALM_ASSERT(!m_transaction);
113,595✔
153
    m_transaction = m_coordinator->begin_read(version_id, bool(m_frozen_version));
113,595✔
154
    add_schema_change_handler();
113,595✔
155
    read_schema_from_group_if_needed();
113,595✔
156
}
113,595✔
157

158
SharedRealm Realm::get_shared_realm(Config config)
159
{
57,285✔
160
    auto coordinator = RealmCoordinator::get_coordinator(config.path);
57,285✔
161
    return coordinator->get_realm(std::move(config), util::none);
57,285✔
162
}
57,285✔
163

164
SharedRealm Realm::get_frozen_realm(Config config, VersionID version)
165
{
274✔
166
    auto coordinator = RealmCoordinator::get_coordinator(config.path);
274✔
167
    return coordinator->get_realm(std::move(config), version);
274✔
168
}
274✔
169

170
SharedRealm Realm::get_shared_realm(ThreadSafeReference ref, std::shared_ptr<util::Scheduler> scheduler)
171
{
132✔
172
    if (!scheduler)
132✔
173
        scheduler = util::Scheduler::make_default();
60✔
174
    SharedRealm realm = ref.resolve<std::shared_ptr<Realm>>(nullptr);
132✔
175
    REALM_ASSERT(realm);
132✔
176
    auto& config = realm->config();
132✔
177
    auto coordinator = RealmCoordinator::get_coordinator(config.path);
132✔
178
    if (auto realm = coordinator->get_cached_realm(config, scheduler))
132✔
179
        return realm;
2✔
180
    realm->m_scheduler = scheduler;
130✔
181
    coordinator->bind_to_context(*realm);
130✔
182
    return realm;
130✔
183
}
130✔
184

185
#if REALM_ENABLE_SYNC
186
std::shared_ptr<AsyncOpenTask> Realm::get_synchronized_realm(Config config)
187
{
80✔
188
    auto coordinator = RealmCoordinator::get_coordinator(config.path);
80✔
189
    return coordinator->get_synchronized_realm(std::move(config));
80✔
190
}
80✔
191

192
std::shared_ptr<SyncSession> Realm::sync_session() const
193
{
386✔
194
    return m_coordinator ? m_coordinator->sync_session() : nullptr;
385✔
195
}
386✔
196

197
sync::SubscriptionSet Realm::get_latest_subscription_set()
198
{
421✔
199
    if (!m_config.sync_config || !m_config.sync_config->flx_sync_requested) {
421✔
200
        throw IllegalOperation("Flexible sync is not enabled");
4✔
201
    }
4✔
202
    // If there is a subscription store, then return the active set
200✔
203
    auto flx_sub_store = m_coordinator->sync_session()->get_flx_subscription_store();
417✔
204
    REALM_ASSERT(flx_sub_store);
417✔
205
    return flx_sub_store->get_latest();
417✔
206
}
417✔
207

208
sync::SubscriptionSet Realm::get_active_subscription_set()
209
{
32✔
210
    if (!m_config.sync_config || !m_config.sync_config->flx_sync_requested) {
32✔
211
        throw IllegalOperation("Flexible sync is not enabled");
6✔
212
    }
6✔
213
    // If there is a subscription store, then return the active set
13✔
214
    auto flx_sub_store = m_coordinator->sync_session()->get_flx_subscription_store();
26✔
215
    REALM_ASSERT(flx_sub_store);
26✔
216
    return flx_sub_store->get_active();
26✔
217
}
26✔
218
#endif
219

220
void Realm::set_schema(Schema const& reference, Schema schema)
221
{
35,253✔
222
    m_dynamic_schema = false;
35,253✔
223
    schema.copy_keys_from(reference, m_config.schema_subset_mode);
35,253✔
224
    m_schema = std::move(schema);
35,253✔
225
    notify_schema_changed();
35,253✔
226
}
35,253✔
227

228
void Realm::read_schema_from_group_if_needed()
229
{
156,827✔
230
    if (m_config.immutable()) {
156,827✔
231
        REALM_ASSERT(m_transaction);
118✔
232
        if (m_schema.empty()) {
118✔
233
            m_schema_version = ObjectStore::get_schema_version(*m_transaction);
64✔
234
            m_schema = ObjectStore::schema_from_group(*m_transaction);
64✔
235
            m_schema_transaction_version = m_transaction->get_version_of_current_transaction().version;
64✔
236
        }
64✔
237
        return;
118✔
238
    }
118✔
239

77,182✔
240
    Group& group = read_group();
156,709✔
241
    auto current_version = transaction().get_version_of_current_transaction().version;
156,709✔
242
    if (m_schema_transaction_version == current_version)
156,709✔
243
        return;
112,476✔
244

21,810✔
245
    m_schema_transaction_version = current_version;
44,233✔
246
    m_schema_version = ObjectStore::get_schema_version(group);
44,233✔
247
    auto schema = ObjectStore::schema_from_group(group);
44,233✔
248

21,810✔
249
    if (m_coordinator)
44,233✔
250
        m_coordinator->cache_schema(schema, m_schema_version, m_schema_transaction_version);
1,055✔
251

21,810✔
252
    if (m_dynamic_schema) {
44,233✔
253
        if (m_schema == schema) {
43,766✔
254
            // The structure of the schema hasn't changed. Bring the table column indices up to date.
10,650✔
255
            m_schema.copy_keys_from(schema, SchemaSubsetMode::Strict);
21,537✔
256
        }
21,537✔
257
        else {
22,229✔
258
            // The structure of the schema has changed, so replace our copy of the schema.
10,933✔
259
            m_schema = std::move(schema);
22,229✔
260
        }
22,229✔
261
    }
43,766✔
262
    else {
467✔
263
        ObjectStore::verify_valid_external_changes(m_schema.compare(schema, m_config.schema_mode));
467✔
264
        m_schema.copy_keys_from(schema, m_config.schema_subset_mode);
467✔
265
    }
467✔
266
    notify_schema_changed();
44,233✔
267
}
44,233✔
268

269
bool Realm::reset_file(Schema& schema, std::vector<SchemaChange>& required_changes)
270
{
20✔
271
    // FIXME: this does not work if multiple processes try to open the file at
10✔
272
    // the same time, or even multiple threads if there is not any external
10✔
273
    // synchronization. The latter is probably fixable, but making it
10✔
274
    // multi-process-safe requires some sort of multi-process exclusive lock
10✔
275
    m_transaction = nullptr;
20✔
276
    m_coordinator->delete_and_reopen();
20✔
277

10✔
278
    m_schema = ObjectStore::schema_from_group(read_group());
20✔
279
    m_schema_version = ObjectStore::get_schema_version(read_group());
20✔
280
    required_changes = m_schema.compare(schema, m_config.schema_mode);
20✔
281
    m_coordinator->clear_schema_cache_and_set_schema_version(m_schema_version);
20✔
282
    return false;
20✔
283
}
20✔
284

285
bool Realm::schema_change_needs_write_transaction(Schema& schema, std::vector<SchemaChange>& changes,
286
                                                  uint64_t version)
287
{
56,743✔
288
    if (version == m_schema_version && changes.empty())
56,743✔
289
        return false;
34,949✔
290

10,779✔
291
    switch (m_config.schema_mode) {
21,794✔
292
        case SchemaMode::Automatic:
13,736✔
293
            if (version < m_schema_version && m_schema_version != ObjectStore::NotVersioned)
13,736✔
294
                throw InvalidSchemaVersionException(m_schema_version, version, false);
2✔
295
            return true;
13,734✔
296

6,829✔
297
        case SchemaMode::Immutable:
6,835✔
298
            if (version != m_schema_version)
12✔
299
                throw InvalidSchemaVersionException(m_schema_version, version, true);
2✔
300
            REALM_FALLTHROUGH;
10✔
301
        case SchemaMode::ReadOnly:
36✔
302
            ObjectStore::verify_compatible_for_immutable_and_readonly(changes);
36✔
303
            return m_schema_version == ObjectStore::NotVersioned;
36✔
304

5✔
305
        case SchemaMode::SoftResetFile:
32✔
306
            if (m_schema_version == ObjectStore::NotVersioned)
32✔
307
                return true;
18✔
308
            if (m_schema_version == version && !ObjectStore::needs_migration(changes))
14✔
309
                return true;
8✔
310
            REALM_FALLTHROUGH;
6✔
311
        case SchemaMode::HardResetFile:
20✔
312
            reset_file(schema, changes);
20✔
313
            return true;
20✔
314

3✔
315
        case SchemaMode::AdditiveDiscovered:
3,901✔
316
        case SchemaMode::AdditiveExplicit: {
7,846✔
317
            bool will_apply_index_changes = version > m_schema_version;
7,846✔
318
            if (ObjectStore::verify_valid_additive_changes(changes, will_apply_index_changes))
7,846✔
319
                return true;
7,718✔
320
            return version != m_schema_version;
128✔
321
        }
128✔
322

64✔
323
        case SchemaMode::Manual:
128✔
324
            if (version < m_schema_version && m_schema_version != ObjectStore::NotVersioned)
128✔
325
                throw InvalidSchemaVersionException(m_schema_version, version, false);
2✔
326
            if (version == m_schema_version) {
126✔
327
                ObjectStore::verify_no_changes_required(changes);
28✔
328
                REALM_UNREACHABLE(); // changes is non-empty so above line always throws
28✔
329
            }
28✔
330
            return true;
126✔
331
    }
×
332
    REALM_COMPILER_HINT_UNREACHABLE();
×
333
}
×
334

335
Schema Realm::get_full_schema()
336
{
56,973✔
337
    if (!m_config.immutable())
56,973✔
338
        do_refresh();
56,907✔
339

28,048✔
340
    // If the user hasn't specified a schema previously then m_schema is always
28,048✔
341
    // the full schema if it's been read
28,048✔
342
    if (m_dynamic_schema && !m_schema.empty())
56,973✔
343
        return m_schema;
35,027✔
344

10,860✔
345
    // Otherwise we may have a subset of the file's schema, so we need to get
10,860✔
346
    // the complete thing to calculate what changes to make
10,860✔
347
    Schema actual_schema;
21,946✔
348
    uint64_t actual_version;
21,946✔
349
    uint64_t version = -1;
21,946✔
350
    bool got_cached = m_coordinator->get_cached_schema(actual_schema, actual_version, version);
21,946✔
351
    if (!got_cached || version != transaction().get_version_of_current_transaction().version)
21,946✔
352
        return ObjectStore::schema_from_group(read_group());
21,294✔
353
    return actual_schema;
652✔
354
}
652✔
355

356
void Realm::set_schema_subset(Schema schema)
357
{
12✔
358
    verify_thread();
12✔
359
    verify_open();
12✔
360
    REALM_ASSERT(m_dynamic_schema);
12✔
361
    REALM_ASSERT(m_schema_version != ObjectStore::NotVersioned);
12✔
362

6✔
363
    std::vector<SchemaChange> changes = m_schema.compare(schema, m_config.schema_mode);
12✔
364
    switch (m_config.schema_mode) {
12✔
365
        case SchemaMode::Automatic:
2✔
366
        case SchemaMode::SoftResetFile:
2✔
367
        case SchemaMode::HardResetFile:
2✔
368
            ObjectStore::verify_no_migration_required(changes);
2✔
369
            break;
2✔
370

1✔
371
        case SchemaMode::Immutable:
1✔
372
        case SchemaMode::ReadOnly:
✔
373
            ObjectStore::verify_compatible_for_immutable_and_readonly(changes);
×
374
            break;
×
375

376
        case SchemaMode::AdditiveDiscovered:
4✔
377
        case SchemaMode::AdditiveExplicit:
8✔
378
            ObjectStore::verify_valid_additive_changes(changes);
8✔
379
            break;
8✔
380

4✔
381
        case SchemaMode::Manual:
4✔
382
            ObjectStore::verify_no_changes_required(changes);
×
383
            break;
×
384
    }
10✔
385

5✔
386
    set_schema(m_schema, std::move(schema));
10✔
387
}
10✔
388

389
void Realm::update_schema(Schema schema, uint64_t version, MigrationFunction migration_function,
390
                          DataInitializationFunction initialization_function, bool in_transaction)
391
{
56,981✔
392
    uint64_t validation_mode = SchemaValidationMode::Basic;
56,981✔
393
#if REALM_ENABLE_SYNC
56,981✔
394
    if (auto sync_config = m_config.sync_config) {
56,981✔
395
        validation_mode |=
1,401✔
396
            sync_config->flx_sync_requested ? SchemaValidationMode::SyncFLX : SchemaValidationMode::SyncPBS;
1,191✔
397
    }
1,401✔
398
#endif
56,981✔
399
    if (m_config.schema_mode == SchemaMode::AdditiveExplicit) {
56,981✔
400
        validation_mode |= SchemaValidationMode::RejectEmbeddedOrphans;
8,463✔
401
    }
8,463✔
402

28,052✔
403
    schema.validate(static_cast<SchemaValidationMode>(validation_mode));
56,981✔
404

28,052✔
405
    bool was_in_read_transaction = is_in_read_transaction();
56,981✔
406
    Schema actual_schema = get_full_schema();
56,981✔
407

28,052✔
408
    // Frozen Realms never modify the schema on disk and we just need to verify
28,052✔
409
    // that the requested schema is compatible with what actually exists on disk
28,052✔
410
    // at that frozen version. Tables are allowed to be missing as those can be
28,052✔
411
    // represented by empty Results, but tables which exist must have all of the
28,052✔
412
    // requested properties with the correct type.
28,052✔
413
    if (m_frozen_version) {
56,981✔
414
        ObjectStore::verify_compatible_for_immutable_and_readonly(
240✔
415
            actual_schema.compare(schema, m_config.schema_mode, true));
240✔
416
        set_schema(actual_schema, std::move(schema));
240✔
417
        return;
240✔
418
    }
240✔
419

27,932✔
420
    std::vector<SchemaChange> required_changes = actual_schema.compare(schema, m_config.schema_mode);
56,741✔
421
    if (!schema_change_needs_write_transaction(schema, required_changes, version)) {
56,741✔
422
        if (!was_in_read_transaction)
35,021✔
423
            m_transaction = nullptr;
34,991✔
424
        set_schema(actual_schema, std::move(schema));
35,021✔
425
        return;
35,021✔
426
    }
35,021✔
427
    // Either the schema version has changed or we need to do non-migration changes
10,747✔
428

10,747✔
429
    // Cancel the write transaction if we exit this function before committing it
10,747✔
430
    auto cleanup = util::make_scope_exit([&]() noexcept {
21,720✔
431
        // When in_transaction is true, caller is responsible to cancel the transaction.
10,701✔
432
        if (!in_transaction && is_in_transaction())
21,628✔
433
            cancel_transaction();
110✔
434
        if (!was_in_read_transaction)
21,628✔
435
            m_transaction = nullptr;
21,276✔
436
    });
21,628✔
437

10,747✔
438
    if (!in_transaction) {
21,720✔
439
        transaction().promote_to_write();
21,602✔
440

10,688✔
441
        // Beginning the write transaction may have advanced the version and left
10,688✔
442
        // us with nothing to do if someone else initialized the schema on disk
10,688✔
443
        if (m_new_schema) {
21,602✔
444
            actual_schema = *m_new_schema;
12✔
445
            required_changes = actual_schema.compare(schema, m_config.schema_mode);
12✔
446
            if (!schema_change_needs_write_transaction(schema, required_changes, version)) {
12✔
447
                cancel_transaction();
2✔
448
                cache_new_schema();
2✔
449
                set_schema(actual_schema, std::move(schema));
2✔
450
                return;
2✔
451
            }
2✔
452
        }
21,600✔
453
        cache_new_schema();
21,600✔
454
    }
21,600✔
455

10,747✔
456
    schema.copy_keys_from(actual_schema, m_config.schema_subset_mode);
21,719✔
457

10,746✔
458
    uint64_t old_schema_version = m_schema_version;
21,718✔
459
    bool additive = m_config.schema_mode == SchemaMode::AdditiveDiscovered ||
21,718✔
460
                    m_config.schema_mode == SchemaMode::AdditiveExplicit ||
21,631✔
461
                    m_config.schema_mode == SchemaMode::ReadOnly;
17,724✔
462
    if (migration_function && !additive) {
21,718✔
463
        auto wrapper = [&] {
207✔
464
            auto config = m_config;
204✔
465
            config.schema_mode = SchemaMode::ReadOnly;
204✔
466
            config.schema = util::none;
204✔
467
            // Don't go through the normal codepath for opening a Realm because
102✔
468
            // we're using a mismatched config
102✔
469
            auto old_realm = std::make_shared<Realm>(std::move(config), none, m_coordinator, MakeSharedTag{});
204✔
470
            // block autorefresh for the old realm
102✔
471
            old_realm->m_auto_refresh = false;
204✔
472
            migration_function(old_realm, shared_from_this(), m_schema);
204✔
473
        };
204✔
474

105✔
475
        // migration function needs to see the target schema on the "new" Realm
105✔
476
        std::swap(m_schema, schema);
210✔
477
        std::swap(m_schema_version, version);
210✔
478
        m_in_migration = true;
210✔
479
        auto restore = util::make_scope_exit([&]() noexcept {
210✔
480
            std::swap(m_schema, schema);
210✔
481
            std::swap(m_schema_version, version);
210✔
482
            m_in_migration = false;
210✔
483
        });
210✔
484

105✔
485
        ObjectStore::apply_schema_changes(transaction(), version, m_schema, m_schema_version, m_config.schema_mode,
210✔
486
                                          required_changes, m_config.automatically_handle_backlinks_in_migrations,
210✔
487
                                          wrapper);
210✔
488
    }
210✔
489
    else {
21,508✔
490
        ObjectStore::apply_schema_changes(transaction(), m_schema_version, schema, version, m_config.schema_mode,
21,508✔
491
                                          required_changes, m_config.automatically_handle_backlinks_in_migrations);
21,508✔
492
        REALM_ASSERT_DEBUG(additive ||
21,508✔
493
                           (required_changes = ObjectStore::schema_from_group(read_group()).compare(schema)).empty());
21,508✔
494
    }
21,508✔
495

10,746✔
496
    if (initialization_function && old_schema_version == ObjectStore::NotVersioned) {
21,718✔
497
        // Initialization function needs to see the latest schema
15✔
498
        uint64_t temp_version = ObjectStore::get_schema_version(read_group());
30✔
499
        std::swap(m_schema, schema);
30✔
500
        std::swap(m_schema_version, temp_version);
30✔
501
        auto restore = util::make_scope_exit([&]() noexcept {
30✔
502
            std::swap(m_schema, schema);
30✔
503
            std::swap(m_schema_version, temp_version);
30✔
504
        });
30✔
505
        initialization_function(shared_from_this());
30✔
506
    }
30✔
507

10,746✔
508
    m_schema = std::move(schema);
21,718✔
509
    m_new_schema = ObjectStore::schema_from_group(read_group());
21,718✔
510
    m_schema_version = ObjectStore::get_schema_version(read_group());
21,718✔
511
    m_dynamic_schema = false;
21,718✔
512
    m_coordinator->clear_schema_cache_and_set_schema_version(version);
21,718✔
513

10,746✔
514
    if (!in_transaction) {
21,718✔
515
        m_coordinator->commit_write(*this);
21,490✔
516
        cache_new_schema();
21,490✔
517
    }
21,490✔
518

10,746✔
519
    notify_schema_changed();
21,718✔
520
}
21,718✔
521

522
void Realm::rename_property(Schema schema, StringData object_type, StringData old_name, StringData new_name)
523
{
4✔
524
    ObjectStore::rename_property(read_group(), schema, object_type, old_name, new_name);
4✔
525
}
4✔
526

527
void Realm::add_schema_change_handler()
528
{
113,587✔
529
    if (m_config.immutable())
113,587✔
530
        return;
56✔
531
    m_transaction->set_schema_change_notification_handler([&] {
113,531✔
532
        m_new_schema = ObjectStore::schema_from_group(read_group());
3,449✔
533
        m_schema_version = ObjectStore::get_schema_version(read_group());
3,449✔
534
        if (m_dynamic_schema) {
3,449✔
535
            m_schema = *m_new_schema;
32✔
536
        }
32✔
537
        else {
3,417✔
538
            m_schema.copy_keys_from(*m_new_schema, m_config.schema_subset_mode);
3,417✔
539
        }
3,417✔
540
        notify_schema_changed();
3,449✔
541
    });
3,449✔
542
}
113,531✔
543

544
void Realm::cache_new_schema()
545
{
204,711✔
546
    if (is_closed()) {
204,711✔
547
        return;
×
548
    }
×
549

100,197✔
550
    auto new_version = transaction().get_version_of_current_transaction().version;
204,711✔
551
    if (m_new_schema)
204,711✔
552
        m_coordinator->cache_schema(std::move(*m_new_schema), m_schema_version, new_version);
24,965✔
553
    else
179,746✔
554
        m_coordinator->advance_schema_cache(m_schema_transaction_version, new_version);
179,746✔
555
    m_schema_transaction_version = new_version;
204,711✔
556
    m_new_schema = util::none;
204,711✔
557
}
204,711✔
558

559
void Realm::translate_schema_error()
560
{
4✔
561
    // Read the new (incompatible) schema without changing our read transaction
2✔
562
    auto new_schema = ObjectStore::schema_from_group(*m_coordinator->begin_read());
4✔
563

2✔
564
    // Should always throw
2✔
565
    ObjectStore::verify_valid_external_changes(m_schema.compare(new_schema, m_config.schema_mode, true));
4✔
566

2✔
567
    // Something strange happened so just rethrow the old exception
2✔
568
    throw;
4✔
569
}
4✔
570

571
void Realm::notify_schema_changed()
572
{
104,445✔
573
    if (m_binding_context) {
104,445✔
574
        m_binding_context->schema_did_change(m_schema);
26✔
575
    }
26✔
576
}
104,445✔
577

578
static void check_can_create_write_transaction(const Realm* realm)
579
{
127,116✔
580
    realm->verify_thread();
127,116✔
581
    realm->verify_open();
127,116✔
582
    if (realm->config().immutable() || realm->config().read_only()) {
127,116✔
583
        throw WrongTransactionState("Can't perform transactions on read-only Realms.");
6✔
584
    }
6✔
585
    if (realm->is_frozen()) {
127,110✔
586
        throw WrongTransactionState("Can't perform transactions on a frozen Realm");
2✔
587
    }
2✔
588
    if (!realm->is_closed() && realm->get_number_of_versions() > realm->config().max_number_of_active_versions) {
127,108✔
589
        throw WrongTransactionState(
×
590
            util::format("Number of active versions (%1) in the Realm exceeded the limit of %2",
×
591
                         realm->get_number_of_versions(), realm->config().max_number_of_active_versions));
×
592
    }
×
593
}
127,108✔
594

595
void Realm::verify_thread() const
596
{
582,608✔
597
    if (m_scheduler && !m_scheduler->is_on_thread())
582,608✔
598
        throw LogicError(ErrorCodes::WrongThread, "Realm accessed from incorrect thread.");
8✔
599
}
582,608✔
600

601
void Realm::verify_in_write() const
602
{
11,928✔
603
    if (!is_in_transaction()) {
11,928✔
604
        throw WrongTransactionState("Cannot modify managed objects outside of a write transaction.");
4✔
605
    }
4✔
606
}
11,928✔
607

608
void Realm::verify_open() const
609
{
2,203,369✔
610
    if (is_closed()) {
2,203,369✔
611
        throw LogicError(ErrorCodes::ClosedRealm, "Cannot access realm that has been closed.");
44✔
612
    }
44✔
613
}
2,203,369✔
614

615
bool Realm::verify_notifications_available(bool throw_on_error) const
616
{
44,647✔
617
    if (is_frozen()) {
44,647✔
618
        if (throw_on_error)
56✔
619
            throw WrongTransactionState(
12✔
620
                "Notifications are not available on frozen collections since they do not change.");
12✔
621
        return false;
44✔
622
    }
44✔
623
    if (config().immutable()) {
44,591✔
624
        if (throw_on_error)
2✔
625
            throw WrongTransactionState("Cannot create asynchronous query for immutable Realms");
×
626
        return false;
2✔
627
    }
2✔
628
    if (throw_on_error) {
44,589✔
629
        if (m_transaction && m_transaction->get_commit_size() > 0)
9,870✔
630
            throw WrongTransactionState(
2✔
631
                "Cannot create asynchronous query after making changes in a write transaction.");
2✔
632
    }
34,719✔
633
    else {
34,719✔
634
        // Don't create implicit notifiers inside write transactions even if
17,246✔
635
        // we could as it wouldn't actually be used
17,246✔
636
        if (is_in_transaction())
34,719✔
637
            return false;
20,590✔
638
    }
23,997✔
639

11,921✔
640
    return true;
23,997✔
641
}
23,997✔
642

643
VersionID Realm::read_transaction_version() const
644
{
4,857✔
645
    verify_thread();
4,857✔
646
    verify_open();
4,857✔
647
    REALM_ASSERT(m_transaction);
4,857✔
648
    return m_transaction->get_version_of_current_transaction();
4,857✔
649
}
4,857✔
650

651
uint_fast64_t Realm::get_number_of_versions() const
652
{
127,102✔
653
    verify_open();
127,102✔
654
    return m_coordinator->get_number_of_versions();
127,102✔
655
}
127,102✔
656

657
bool Realm::is_in_transaction() const noexcept
658
{
693,814✔
659
    return !m_config.immutable() && !is_closed() && m_transaction &&
693,814✔
660
           transaction().get_transact_stage() == DB::transact_Writing;
643,905✔
661
}
693,814✔
662

663
bool Realm::is_in_async_transaction() const noexcept
664
{
691✔
665
    return !m_config.immutable() && !is_closed() && m_transaction && m_transaction->is_async();
691✔
666
}
691✔
667

668
util::Optional<VersionID> Realm::current_transaction_version() const
669
{
107,480✔
670
    util::Optional<VersionID> ret;
107,480✔
671
    if (m_transaction) {
107,480✔
672
        ret = m_transaction->get_version_of_current_transaction();
107,258✔
673
    }
107,258✔
674
    else if (m_frozen_version) {
222✔
675
        ret = m_frozen_version;
×
676
    }
×
677
    return ret;
107,480✔
678
}
107,480✔
679

680
// Get the version of the latest snapshot
681
util::Optional<DB::version_type> Realm::latest_snapshot_version() const
682
{
62✔
683
    util::Optional<DB::version_type> ret;
62✔
684
    if (m_transaction) {
62✔
685
        ret = m_transaction->get_version_of_latest_snapshot();
60✔
686
    }
60✔
687
    return ret;
62✔
688
}
62✔
689

690
void Realm::enable_wait_for_change()
691
{
2✔
692
    verify_open();
2✔
693
    m_coordinator->enable_wait_for_change();
2✔
694
}
2✔
695

696
bool Realm::wait_for_change()
697
{
6✔
698
    verify_open();
6✔
699
    if (m_frozen_version || m_config.schema_mode == SchemaMode::Immutable) {
6✔
700
        return false;
4✔
701
    }
4✔
702
    return m_transaction && m_coordinator->wait_for_change(m_transaction);
2!
703
}
2✔
704

705
void Realm::wait_for_change_release()
706
{
2✔
707
    verify_open();
2✔
708
    m_coordinator->wait_for_change_release();
2✔
709
}
2✔
710

711
bool Realm::has_pending_async_work() const
712
{
1,256✔
713
    verify_thread();
1,256✔
714
    return !m_async_commit_q.empty() || !m_async_write_q.empty() || (m_transaction && m_transaction->is_async());
1,256✔
715
}
1,256✔
716

717
void Realm::run_writes_on_proper_thread()
718
{
7✔
719
    m_scheduler->invoke([self = shared_from_this()] {
7✔
720
        self->run_writes();
7✔
721
    });
7✔
722
}
7✔
723

724
void Realm::call_completion_callbacks()
725
{
177,397✔
726
    if (m_is_running_async_commit_completions) {
177,397✔
727
        return;
4✔
728
    }
4✔
729

86,948✔
730
    CountGuard sending_completions(m_is_running_async_commit_completions);
177,393✔
731
    auto error = m_transaction->get_commit_exception();
177,393✔
732
    auto completions = std::move(m_async_commit_q);
177,393✔
733
    m_async_commit_q.clear();
177,393✔
734
    for (auto& cb : completions) {
88,319✔
735
        if (!cb.when_completed)
2,494✔
736
            continue;
16✔
737
        if (m_async_exception_handler) {
2,478✔
738
            try {
2✔
739
                cb.when_completed(error);
2✔
740
            }
2✔
741
            catch (...) {
2✔
742
                m_async_exception_handler(cb.handle, std::current_exception());
2✔
743
            }
2✔
744
        }
2✔
745
        else {
2,476✔
746
            cb.when_completed(error);
2,476✔
747
        }
2,476✔
748
    }
2,478✔
749
}
177,393✔
750

751
void Realm::run_async_completions()
752
{
314✔
753
    call_completion_callbacks();
314✔
754
    check_pending_write_requests();
314✔
755
}
314✔
756

757
void Realm::check_pending_write_requests()
758
{
59,918✔
759
    if (!m_async_write_q.empty()) {
59,918✔
760
        if (m_transaction->is_async()) {
408✔
761
            run_writes_on_proper_thread();
7✔
762
        }
7✔
763
        else {
401✔
764
            m_coordinator->async_request_write_mutex(*this);
401✔
765
        }
401✔
766
    }
408✔
767
}
59,918✔
768

769
void Realm::end_current_write(bool check_pending)
770
{
3,278✔
771
    if (!m_transaction) {
3,278✔
772
        return;
2✔
773
    }
2✔
774
    m_transaction->async_complete_writes([self = shared_from_this(), this]() mutable {
3,276✔
775
        m_scheduler->invoke([self = std::move(self), this]() mutable {
314✔
776
            run_async_completions();
314✔
777
            self.reset();
314✔
778
        });
314✔
779
    });
314✔
780
    if (check_pending && m_async_commit_q.empty()) {
3,276✔
781
        check_pending_write_requests();
2,836✔
782
    }
2,836✔
783
}
3,276✔
784

785
void Realm::run_writes()
786
{
485✔
787
    if (!m_transaction) {
485✔
788
        // Realm might have been closed
789
        return;
×
790
    }
×
791
    if (m_transaction->is_synchronizing()) {
485✔
792
        // Wait for the synchronization complete callback before we run more
793
        // writes as we can't add commits while in that state
UNCOV
794
        return;
×
UNCOV
795
    }
×
796
    if (is_in_transaction()) {
485✔
797
        // This is scheduled asynchronously after acquiring the write lock, so
1✔
798
        // in that time a synchronous transaction may have been started. If so,
1✔
799
        // we'll be re-invoked when that transaction ends.
1✔
800
        return;
2✔
801
    }
2✔
802

117✔
803
    CountGuard running_writes(m_is_running_async_writes);
483✔
804
    int run_limit = 20; // max number of commits without full sync to disk
483✔
805
    // this is tricky
117✔
806
    //  - each pending call may itself add other async writes
117✔
807
    //  - the 'run' will terminate as soon as a commit without grouping is requested
117✔
808
    while (!m_async_write_q.empty() && m_transaction) {
2,693✔
809
        // We might have made a sync commit and thereby given up the write lock
1,142✔
810
        if (!m_transaction->holds_write_mutex()) {
2,644✔
811
            return;
114✔
812
        }
114✔
813

1,141✔
814
        do_begin_transaction();
2,530✔
815

1,141✔
816
        auto write_desc = std::move(m_async_write_q.front());
2,530✔
817
        m_async_write_q.pop_front();
2,530✔
818

1,141✔
819
        // prevent any calls to commit/cancel during a simple notification
1,141✔
820
        m_notify_only = write_desc.notify_only;
2,530✔
821
        m_async_commit_barrier_requested = false;
2,530✔
822
        auto prev_version = m_transaction->get_version();
2,530✔
823
        try {
2,530✔
824
            write_desc.writer();
2,530✔
825
        }
2,530✔
826
        catch (const std::exception&) {
1,144✔
827
            if (m_transaction) {
6✔
828
                transaction::cancel(*m_transaction, m_binding_context.get());
4✔
829
            }
4✔
830
            m_notify_only = false;
6✔
831

3✔
832
            if (m_async_exception_handler) {
6✔
833
                m_async_exception_handler(write_desc.handle, std::current_exception());
2✔
834
                continue;
2✔
835
            }
2✔
836
            end_current_write();
4✔
837
            throw;
4✔
838
        }
4✔
839

1,138✔
840
        // if we've merely delivered a notification, the full transaction will follow later
1,138✔
841
        // and terminate with a call to async commit or async cancel
1,138✔
842
        if (m_notify_only) {
2,524✔
843
            m_notify_only = false;
12✔
844
            return;
12✔
845
        }
12✔
846

1,132✔
847
        // Realm may have been closed in the write function
1,132✔
848
        if (!m_transaction) {
2,512✔
849
            return;
20✔
850
        }
20✔
851

1,122✔
852
        auto new_version = m_transaction->get_version();
2,492✔
853
        // if we've run the full transaction, there is follow up work to do:
1,122✔
854
        if (new_version > prev_version) {
2,492✔
855
            // A commit was done during callback
1,117✔
856
            --run_limit;
2,482✔
857
            if (run_limit <= 0)
2,482✔
858
                break;
102✔
859
        }
10✔
860
        else {
10✔
861
            if (m_transaction->get_transact_stage() == DB::transact_Writing) {
10✔
862
                // Still in writing stage - we make a rollback
4✔
863
                transaction::cancel(transaction(), m_binding_context.get());
8✔
864
            }
8✔
865
        }
10✔
866
        if (m_async_commit_barrier_requested)
2,441✔
867
            break;
182✔
868
    }
2,390✔
869

117✔
870
    end_current_write();
352✔
871
}
333✔
872

873
auto Realm::async_begin_transaction(util::UniqueFunction<void()>&& the_write_block, bool notify_only) -> AsyncHandle
874
{
2,544✔
875
    check_can_create_write_transaction(this);
2,544✔
876
    if (m_is_running_async_commit_completions) {
2,544✔
877
        throw WrongTransactionState("Can't begin a write transaction from inside a commit completion callback.");
×
878
    }
×
879
    if (!m_scheduler->can_invoke()) {
2,544✔
880
        throw WrongTransactionState(
×
881
            "Cannot schedule async transaction. Make sure you are running from inside a run loop.");
×
882
    }
×
883
    REALM_ASSERT(the_write_block);
2,544✔
884

1,148✔
885
    // make sure we have a (at least a) read transaction
1,148✔
886
    transaction();
2,544✔
887
    auto handle = m_async_commit_handle++;
2,544✔
888
    m_async_write_q.push_back({std::move(the_write_block), notify_only, handle});
2,544✔
889

1,148✔
890
    if (!m_is_running_async_writes && !m_transaction->is_async() &&
2,544✔
891
        m_transaction->get_transact_stage() != DB::transact_Writing) {
1,226✔
892
        m_coordinator->async_request_write_mutex(*this);
121✔
893
    }
121✔
894
    return handle;
2,544✔
895
}
2,544✔
896

897
auto Realm::async_commit_transaction(util::UniqueFunction<void(std::exception_ptr)>&& completion, bool allow_grouping)
898
    -> AsyncHandle
899
{
2,498✔
900
    check_can_create_write_transaction(this);
2,498✔
901
    if (m_is_running_async_commit_completions) {
2,498✔
902
        throw WrongTransactionState("Can't commit a write transaction from inside a commit completion callback.");
×
903
    }
×
904
    if (!is_in_transaction()) {
2,498✔
905
        throw WrongTransactionState("Can't commit a non-existing write transaction");
×
906
    }
×
907

1,125✔
908
    m_transaction->promote_to_async();
2,498✔
909
    REALM_ASSERT(m_transaction->holds_write_mutex());
2,498✔
910
    REALM_ASSERT(!m_notify_only);
2,498✔
911
    // auditing is not supported
1,125✔
912
    REALM_ASSERT(!audit_context());
2,498✔
913
    // grab a version lock on current version, push it along with the done block
1,125✔
914
    // do in-buffer-cache commit_transaction();
1,125✔
915
    auto handle = m_async_commit_handle++;
2,498✔
916
    m_async_commit_q.push_back({std::move(completion), handle});
2,498✔
917
    try {
2,498✔
918
        m_coordinator->commit_write(*this, /* commit_to_disk: */ false);
2,498✔
919
    }
2,498✔
920
    catch (...) {
1,127✔
921
        // If the exception happened before the commit, we need to roll back the
2✔
922
        // transaction and remove the completion handler from the queue
2✔
923
        if (is_in_transaction()) {
4✔
924
            // Exception happened before the commit, so roll back the transaction
1✔
925
            // and remove the completion handler from the queue
1✔
926
            cancel_transaction();
2✔
927
            auto it = std::find_if(m_async_commit_q.begin(), m_async_commit_q.end(), [=](auto& e) {
2✔
928
                return e.handle == handle;
2✔
929
            });
2✔
930
            if (it != m_async_commit_q.end()) {
2✔
931
                m_async_commit_q.erase(it);
2✔
932
            }
2✔
933
        }
2✔
934
        else if (m_transaction) {
2✔
935
            end_current_write(false);
2✔
936
        }
2✔
937
        throw;
4✔
938
    }
4✔
939

1,122✔
940
    if (m_is_running_async_writes) {
2,492✔
941
        // we're called from with the callback loop and it will take care of releasing lock
1,114✔
942
        // if applicable, and of triggering followup runs of callbacks
1,114✔
943
        if (!allow_grouping) {
2,352✔
944
            m_async_commit_barrier_requested = true;
190✔
945
        }
190✔
946
    }
2,352✔
947
    else {
140✔
948
        // we're called from outside the callback loop so we have to take care of
8✔
949
        // releasing any lock and of keeping callbacks coming.
8✔
950
        if (allow_grouping) {
140✔
951
            run_writes();
×
952
        }
×
953
        else {
140✔
954
            end_current_write(false);
140✔
955
        }
140✔
956
    }
140✔
957
    return handle;
2,492✔
958
}
2,492✔
959

960
bool Realm::async_cancel_transaction(AsyncHandle handle)
961
{
6✔
962
    verify_thread();
6✔
963
    verify_open();
6✔
964
    auto compare = [handle](auto& elem) {
5✔
965
        return elem.handle == handle;
4✔
966
    };
4✔
967

3✔
968
    auto it1 = std::find_if(m_async_write_q.begin(), m_async_write_q.end(), compare);
6✔
969
    if (it1 != m_async_write_q.end()) {
6✔
970
        m_async_write_q.erase(it1);
2✔
971
        return true;
2✔
972
    }
2✔
973
    auto it2 = std::find_if(m_async_commit_q.begin(), m_async_commit_q.end(), compare);
4✔
974
    if (it2 != m_async_commit_q.end()) {
4✔
975
        // Just delete the callback. It is important that we know
1✔
976
        // that there are still commits pending.
1✔
977
        it2->when_completed = nullptr;
2✔
978
        return true;
2✔
979
    }
2✔
980
    return false;
2✔
981
}
2✔
982

983
void Realm::begin_transaction()
984
{
62,495✔
985
    check_can_create_write_transaction(this);
62,495✔
986

30,688✔
987
    if (is_in_transaction()) {
62,495✔
988
        throw WrongTransactionState("The Realm is already in a write transaction");
×
989
    }
×
990

30,688✔
991
    // Any of the callbacks to user code below could drop the last remaining
30,688✔
992
    // strong reference to `this`
30,688✔
993
    auto retain_self = shared_from_this();
62,495✔
994

30,688✔
995
    // make sure we have a read transaction
30,688✔
996
    read_group();
62,495✔
997

30,688✔
998
    do_begin_transaction();
62,495✔
999
}
62,495✔
1000

1001
void Realm::do_begin_transaction()
1002
{
65,017✔
1003
    CountGuard sending_notifications(m_is_sending_notifications);
65,017✔
1004
    try {
65,017✔
1005
        m_coordinator->promote_to_write(*this);
65,017✔
1006
    }
65,017✔
1007
    catch (_impl::UnsupportedSchemaChange const&) {
31,825✔
1008
        translate_schema_error();
×
1009
    }
×
1010
    cache_new_schema();
65,017✔
1011

31,825✔
1012
    if (m_transaction && !m_transaction->has_unsynced_commits()) {
65,017✔
1013
        call_completion_callbacks();
62,967✔
1014
    }
62,967✔
1015
}
65,017✔
1016

1017
void Realm::commit_transaction()
1018
{
56,764✔
1019
    check_can_create_write_transaction(this);
56,764✔
1020

27,823✔
1021
    if (!is_in_transaction()) {
56,764✔
1022
        throw WrongTransactionState("Can't commit a non-existing write transaction");
×
1023
    }
×
1024

27,823✔
1025
    DB::VersionID prev_version = transaction().get_version_of_current_transaction();
56,764✔
1026
    if (auto audit = audit_context()) {
56,764✔
1027
        audit->prepare_for_write(prev_version);
91✔
1028
    }
91✔
1029

27,823✔
1030
    m_coordinator->commit_write(*this, /* commit_to_disk */ true);
56,764✔
1031
    cache_new_schema();
56,764✔
1032

27,823✔
1033
    // Realm might have been closed
27,823✔
1034
    if (m_transaction) {
56,764✔
1035
        // Any previous async commits got flushed along with the sync commit
27,822✔
1036
        call_completion_callbacks();
56,762✔
1037
        // If we have pending async writes we need to rerequest the write mutex
27,822✔
1038
        check_pending_write_requests();
56,762✔
1039
    }
56,762✔
1040
    if (auto audit = audit_context()) {
56,764✔
1041
        audit->record_write(prev_version, transaction().get_version_of_current_transaction());
91✔
1042
    }
91✔
1043
}
56,764✔
1044

1045
void Realm::cancel_transaction()
1046
{
2,815✔
1047
    check_can_create_write_transaction(this);
2,815✔
1048

1,407✔
1049
    if (m_is_running_async_commit_completions) {
2,815✔
1050
        throw WrongTransactionState("Can't cancel a write transaction from inside a commit completion callback.");
×
1051
    }
×
1052
    if (!is_in_transaction()) {
2,815✔
1053
        throw WrongTransactionState("Can't cancel a non-existing write transaction");
×
1054
    }
×
1055

1,407✔
1056
    transaction::cancel(transaction(), m_binding_context.get());
2,815✔
1057

1,407✔
1058
    if (m_transaction && !m_is_running_async_writes) {
2,815✔
1059
        if (m_async_write_q.empty()) {
2,807✔
1060
            end_current_write();
2,799✔
1061
        }
2,799✔
1062
        else {
8✔
1063
            check_pending_write_requests();
8✔
1064
        }
8✔
1065
    }
2,807✔
1066
}
2,815✔
1067

1068
void Realm::invalidate()
1069
{
246✔
1070
    verify_thread();
246✔
1071
    verify_open();
246✔
1072

123✔
1073
    if (m_is_sending_notifications) {
246✔
1074
        // This was originally because closing the Realm during notification
3✔
1075
        // sending would break things, but we now support that. However, it's a
3✔
1076
        // breaking change so we keep the old behavior for now.
3✔
1077
        return;
6✔
1078
    }
6✔
1079

120✔
1080
    if (is_in_transaction()) {
240✔
1081
        cancel_transaction();
172✔
1082
    }
172✔
1083

120✔
1084
    do_invalidate();
240✔
1085
}
240✔
1086

1087
void Realm::do_invalidate()
1088
{
4,251✔
1089
    if (!m_config.immutable() && m_transaction) {
4,251✔
1090
        m_transaction->prepare_for_close();
4,173✔
1091
        call_completion_callbacks();
4,173✔
1092
        transaction().close();
4,173✔
1093
    }
4,173✔
1094

2,110✔
1095
    m_transaction = nullptr;
4,251✔
1096
    m_async_write_q.clear();
4,251✔
1097
    m_async_commit_q.clear();
4,251✔
1098
}
4,251✔
1099

1100
bool Realm::compact()
1101
{
8✔
1102
    verify_thread();
8✔
1103
    verify_open();
8✔
1104

4✔
1105
    if (m_config.immutable() || m_config.read_only()) {
8✔
1106
        throw WrongTransactionState("Can't compact a read-only Realm");
4✔
1107
    }
4✔
1108
    if (is_in_transaction()) {
4✔
1109
        throw WrongTransactionState("Can't compact a Realm within a write transaction");
×
1110
    }
×
1111

2✔
1112
    verify_open();
4✔
1113
    m_transaction = nullptr;
4✔
1114
    return m_coordinator->compact();
4✔
1115
}
4✔
1116

1117
void Realm::convert(const Config& config, bool merge_into_existing)
1118
{
60✔
1119
    verify_thread();
60✔
1120
    verify_open();
60✔
1121

30✔
1122
#if REALM_ENABLE_SYNC
60✔
1123
    auto src_is_flx_sync = m_config.sync_config && m_config.sync_config->flx_sync_requested;
60✔
1124
    auto dst_is_flx_sync = config.sync_config && config.sync_config->flx_sync_requested;
60✔
1125
    auto dst_is_pbs_sync = config.sync_config && !config.sync_config->flx_sync_requested;
60✔
1126

30✔
1127
    if (dst_is_flx_sync && !src_is_flx_sync) {
60✔
1128
        throw IllegalOperation(
4✔
1129
            "Realm cannot be converted to a flexible sync realm unless flexible sync is already enabled");
4✔
1130
    }
4✔
1131
    if (dst_is_pbs_sync && src_is_flx_sync) {
56✔
1132
        throw IllegalOperation(
2✔
1133
            "Realm cannot be converted from a flexible sync realm to a partition based sync realm");
2✔
1134
    }
2✔
1135

27✔
1136
#endif
54✔
1137

27✔
1138
    if (merge_into_existing && util::File::exists(config.path)) {
54✔
1139
        auto destination_realm = Realm::get_shared_realm(config);
6✔
1140
        destination_realm->begin_transaction();
6✔
1141
        auto destination = destination_realm->transaction_ref();
6✔
1142
        m_transaction->copy_to(destination);
6✔
1143
        destination_realm->commit_transaction();
6✔
1144
        return;
6✔
1145
    }
6✔
1146

24✔
1147
    if (config.encryption_key.size() && config.encryption_key.size() != 64) {
48!
1148
        throw InvalidEncryptionKey();
×
1149
    }
×
1150

24✔
1151
    auto& tr = transaction();
48✔
1152
    auto repl = tr.get_replication();
48✔
1153
    bool src_is_sync = repl && repl->get_history_type() == Replication::hist_SyncClient;
48✔
1154
    bool dst_is_sync = config.sync_config || config.force_sync_history;
48✔
1155

24✔
1156
    if (dst_is_sync) {
48✔
1157
        m_coordinator->write_copy(config.path, config.encryption_key.data());
20✔
1158
        if (!src_is_sync) {
20✔
1159
#if REALM_ENABLE_SYNC
8✔
1160
            DBOptions options;
8✔
1161
            if (config.encryption_key.size()) {
8✔
1162
                options.encryption_key = config.encryption_key.data();
×
1163
            }
×
1164
            auto db = DB::create(make_in_realm_history(), config.path, options);
8✔
1165
            db->create_new_history(sync::make_client_replication());
8✔
1166
#endif
8✔
1167
        }
8✔
1168
    }
20✔
1169
    else {
28✔
1170
        tr.write(config.path, config.encryption_key.data());
28✔
1171
    }
28✔
1172
}
48✔
1173

1174
OwnedBinaryData Realm::write_copy()
1175
{
6✔
1176
    verify_thread();
6✔
1177
    BinaryData buffer = read_group().write_to_mem();
6✔
1178

3✔
1179
    // Since OwnedBinaryData does not have a constructor directly taking
3✔
1180
    // ownership of BinaryData, we have to do this to avoid copying the buffer
3✔
1181
    return OwnedBinaryData(std::unique_ptr<char[]>((char*)buffer.data()), buffer.size());
6✔
1182
}
6✔
1183

1184
void Realm::notify()
1185
{
23,906✔
1186
    if (is_closed() || is_in_transaction() || is_frozen()) {
23,906✔
1187
        return;
330✔
1188
    }
330✔
1189

10,331✔
1190
    verify_thread();
23,576✔
1191

10,331✔
1192
    // Any of the callbacks to user code below could drop the last remaining
10,331✔
1193
    // strong reference to `this`
10,331✔
1194
    auto retain_self = shared_from_this();
23,576✔
1195

10,331✔
1196
    if (m_binding_context) {
23,576✔
1197
        m_binding_context->before_notify();
198✔
1198
        if (is_closed() || is_in_transaction()) {
198✔
1199
            return;
×
1200
        }
×
1201
    }
23,576✔
1202

10,331✔
1203
    if (!m_coordinator->can_advance(*this)) {
23,576✔
1204
        CountGuard sending_notifications(m_is_sending_notifications);
16,593✔
1205
        m_coordinator->process_available_async(*this);
16,593✔
1206
        return;
16,593✔
1207
    }
16,593✔
1208

2,900✔
1209
    if (m_binding_context) {
6,983✔
1210
        m_binding_context->changes_available();
70✔
1211

35✔
1212
        // changes_available() may have advanced the read version, and if
35✔
1213
        // so we don't need to do anything further
35✔
1214
        if (!m_coordinator->can_advance(*this))
70✔
1215
            return;
4✔
1216
    }
6,979✔
1217

2,898✔
1218
    CountGuard sending_notifications(m_is_sending_notifications);
6,979✔
1219
    if (m_auto_refresh) {
6,985✔
1220
        if (m_transaction) {
6,982✔
1221
            try {
6,952✔
1222
                m_coordinator->advance_to_ready(*this);
6,952✔
1223
            }
6,952✔
1224
            catch (_impl::UnsupportedSchemaChange const&) {
2,880✔
1225
                translate_schema_error();
×
1226
            }
×
1227
            if (!is_closed())
6,952✔
1228
                cache_new_schema();
6,950✔
1229
        }
6,952✔
1230
        else {
30✔
1231
            if (m_binding_context) {
30✔
1232
                m_binding_context->did_change({}, {});
4✔
1233
            }
4✔
1234
            if (!is_closed()) {
30✔
1235
                m_coordinator->process_available_async(*this);
28✔
1236
            }
28✔
1237
        }
30✔
1238
    }
6,982✔
1239
}
6,979✔
1240

1241
bool Realm::refresh()
1242
{
66,462✔
1243
    verify_thread();
66,462✔
1244
    return do_refresh();
66,462✔
1245
}
66,462✔
1246

1247
bool Realm::do_refresh()
1248
{
123,369✔
1249
    // Frozen Realms never change.
60,869✔
1250
    if (is_frozen()) {
123,369✔
1251
        return false;
244✔
1252
    }
244✔
1253

60,747✔
1254
    if (m_config.immutable()) {
123,125✔
1255
        throw WrongTransactionState("Can't refresh an immutable Realm.");
4✔
1256
    }
4✔
1257

60,745✔
1258
    // can't be any new changes if we're in a write transaction
60,745✔
1259
    if (is_in_transaction()) {
123,121✔
1260
        return false;
30✔
1261
    }
30✔
1262
    // don't advance if we're already in the process of advancing as that just
60,730✔
1263
    // makes things needlessly complicated
60,730✔
1264
    if (m_is_sending_notifications) {
123,091✔
1265
        return false;
8✔
1266
    }
8✔
1267

60,726✔
1268
    // Any of the callbacks to user code below could drop the last remaining
60,726✔
1269
    // strong reference to `this`
60,726✔
1270
    auto retain_self = shared_from_this();
123,083✔
1271

60,726✔
1272
    CountGuard sending_notifications(m_is_sending_notifications);
123,083✔
1273
    if (m_binding_context) {
123,083✔
1274
        m_binding_context->before_notify();
138✔
1275
    }
138✔
1276
    if (m_transaction) {
123,083✔
1277
        try {
32,900✔
1278
            bool version_changed = m_coordinator->advance_to_latest(*this);
32,900✔
1279
            if (is_closed())
32,900✔
1280
                return false;
6✔
1281
            cache_new_schema();
32,894✔
1282
            return version_changed;
32,894✔
1283
        }
32,894✔
1284
        catch (_impl::UnsupportedSchemaChange const&) {
4✔
1285
            translate_schema_error();
4✔
1286
        }
4✔
1287
    }
32,900✔
1288

60,726✔
1289
    // No current read transaction, so just create a new one
60,726✔
1290
    read_group();
106,539✔
1291
    m_coordinator->process_available_async(*this);
90,183✔
1292
    return true;
90,183✔
1293
}
123,083✔
1294

1295
void Realm::set_auto_refresh(bool auto_refresh)
1296
{
16✔
1297
    if (is_frozen() && auto_refresh) {
16✔
1298
        throw WrongTransactionState("Auto-refresh cannot be enabled for frozen Realms.");
2✔
1299
    }
2✔
1300
    m_auto_refresh = auto_refresh;
14✔
1301
}
14✔
1302

1303

1304
bool Realm::can_deliver_notifications() const noexcept
1305
{
14,129✔
1306
    if (m_config.immutable() || !m_config.automatic_change_notifications) {
14,129✔
1307
        return false;
14,073✔
1308
    }
14,073✔
1309

28✔
1310
    if (!m_scheduler || !m_scheduler->can_invoke()) {
56✔
1311
        return false;
×
1312
    }
×
1313

28✔
1314
    return true;
56✔
1315
}
56✔
1316

1317
uint64_t Realm::get_schema_version(const Realm::Config& config)
1318
{
×
1319
    auto coordinator = RealmCoordinator::get_coordinator(config.path);
×
1320
    auto version = coordinator->get_schema_version();
×
1321
    if (version == ObjectStore::NotVersioned)
×
1322
        version = ObjectStore::get_schema_version(coordinator->get_realm(config, util::none)->read_group());
×
1323
    return version;
×
1324
}
×
1325

1326

1327
bool Realm::is_frozen() const
1328
{
385,550✔
1329
    bool result = bool(m_frozen_version);
385,550✔
1330
    REALM_ASSERT_DEBUG(!result || !m_transaction || m_transaction->is_frozen());
385,550✔
1331
    return result;
385,550✔
1332
}
385,550✔
1333

1334
SharedRealm Realm::freeze()
1335
{
630✔
1336
    read_group(); // Freezing requires a read transaction
630✔
1337
    return m_coordinator->freeze_realm(*this);
630✔
1338
}
630✔
1339

1340
void Realm::copy_schema_from(const Realm& source)
1341
{
626✔
1342
    REALM_ASSERT(is_frozen());
626✔
1343
    REALM_ASSERT(m_frozen_version == source.read_transaction_version());
626✔
1344
    m_schema = source.m_schema;
626✔
1345
    m_schema_version = source.m_schema_version;
626✔
1346
    m_schema_transaction_version = m_frozen_version->version;
626✔
1347
    m_dynamic_schema = false;
626✔
1348
}
626✔
1349

1350
void Realm::close()
1351
{
4,033✔
1352
    if (is_closed()) {
4,033✔
1353
        return;
20✔
1354
    }
20✔
1355
    if (m_coordinator) {
4,013✔
1356
        m_coordinator->unregister_realm(this);
4,013✔
1357
    }
4,013✔
1358

1,991✔
1359
    do_invalidate();
4,013✔
1360

1,991✔
1361
    m_binding_context = nullptr;
4,013✔
1362
    m_coordinator = nullptr;
4,013✔
1363
    m_scheduler = nullptr;
4,013✔
1364
    m_config = {};
4,013✔
1365
}
4,013✔
1366

1367
void Realm::delete_files(const std::string& realm_file_path, bool* did_delete_realm)
1368
{
16✔
1369
    bool lock_successful = false;
16✔
1370
    try {
16✔
1371
        lock_successful = DB::call_with_lock(realm_file_path, [=](auto const& path) {
14✔
1372
            DB::delete_files(path, did_delete_realm);
12✔
1373
        });
12✔
1374
    }
16✔
1375
    catch (const FileAccessError& e) {
9✔
1376
        if (e.code() != ErrorCodes::FileNotFound) {
2✔
1377
            throw;
×
1378
        }
×
1379
        // Thrown only if the parent directory of the lock file does not exist,
1✔
1380
        // which obviously indicates that we didn't need to delete anything
1✔
1381
        if (did_delete_realm) {
2✔
1382
            *did_delete_realm = false;
2✔
1383
        }
2✔
1384
        return;
2✔
1385
    }
2✔
1386
    if (!lock_successful) {
14✔
1387
        throw FileAccessError(
2✔
1388
            ErrorCodes::DeleteOnOpenRealm,
2✔
1389
            util::format("Cannot delete files of an open Realm: '%1' is still in use.", realm_file_path),
2✔
1390
            realm_file_path);
2✔
1391
    }
2✔
1392
}
14✔
1393

1394
AuditInterface* Realm::audit_context() const noexcept
1395
{
183,401✔
1396
    return m_coordinator ? m_coordinator->audit_context() : nullptr;
183,401✔
1397
}
183,401✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc