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

realm / realm-core / github_pull_request_281922

31 Oct 2023 09:13AM UTC coverage: 90.445% (-0.08%) from 90.528%
github_pull_request_281922

Pull #7039

Evergreen

jedelbo
Merge branch 'next-major' into je/global-key
Pull Request #7039: Remove ability to synchronize objects without primary key

95324 of 175822 branches covered (0.0%)

101 of 105 new or added lines in 13 files covered. (96.19%)

238 existing lines in 19 files now uncovered.

232657 of 257235 relevant lines covered (90.45%)

6351359.67 hits per line

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

93.74
/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
    {
383,479✔
65
        ++m_count;
383,479✔
66
    }
383,479✔
67
    ~CountGuard()
68
    {
383,476✔
69
        --m_count;
383,476✔
70
    }
383,476✔
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
{
57,390✔
83
    if (version) {
57,390✔
84
        m_auto_refresh = false;
997✔
85
        REALM_ASSERT(*version != VersionID());
997✔
86
    }
997✔
87
    else if (!coordinator->get_cached_schema(m_schema, m_schema_version, m_schema_transaction_version)) {
56,393✔
88
        m_transaction = coordinator->begin_read();
41,566✔
89
        read_schema_from_group_if_needed();
41,566✔
90
        coordinator->cache_schema(m_schema, m_schema_version, m_schema_transaction_version);
41,566✔
91
        m_transaction = nullptr;
41,566✔
92
    }
41,566✔
93
    m_coordinator = std::move(coordinator);
57,390✔
94
}
57,390✔
95

96
Realm::~Realm()
97
{
57,390✔
98
    if (m_transaction) {
57,390✔
99
        // Wait for potential syncing to finish
25,359✔
100
        m_transaction->prepare_for_close();
51,508✔
101
        call_completion_callbacks();
51,508✔
102
    }
51,508✔
103

28,280✔
104
    if (m_coordinator) {
57,390✔
105
        m_coordinator->unregister_realm(this);
53,373✔
106
    }
53,373✔
107
}
57,390✔
108

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

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

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

129
std::shared_ptr<Transaction> Realm::transaction_ref()
130
{
114,415✔
131
    return m_transaction;
114,415✔
132
}
114,415✔
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,506✔
142
    return realm.m_coordinator->m_db;
6,506✔
143
}
6,506✔
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
{
110,261✔
152
    REALM_ASSERT(!m_transaction);
110,261✔
153
    m_transaction = m_coordinator->begin_read(version_id, bool(m_frozen_version));
110,261✔
154
    add_schema_change_handler();
110,261✔
155
    read_schema_from_group_if_needed();
110,261✔
156
}
110,261✔
157

158
SharedRealm Realm::get_shared_realm(Config config)
159
{
55,599✔
160
    auto coordinator = RealmCoordinator::get_coordinator(config.path);
55,599✔
161
    return coordinator->get_realm(std::move(config), util::none);
55,599✔
162
}
55,599✔
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
{
138✔
172
    if (!scheduler)
138✔
173
        scheduler = util::Scheduler::make_default();
66✔
174
    SharedRealm realm = ref.resolve<std::shared_ptr<Realm>>(nullptr);
138✔
175
    REALM_ASSERT(realm);
138✔
176
    auto& config = realm->config();
138✔
177
    auto coordinator = RealmCoordinator::get_coordinator(config.path);
138✔
178
    if (auto realm = coordinator->get_cached_realm(config, scheduler))
138✔
179
        return realm;
2✔
180
    realm->m_scheduler = scheduler;
136✔
181
    coordinator->bind_to_context(*realm);
136✔
182
    return realm;
136✔
183
}
136✔
184

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

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

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

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

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

228
void Realm::read_schema_from_group_if_needed()
229
{
151,819✔
230
    if (m_config.immutable()) {
151,819✔
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

74,754✔
240
    Group& group = read_group();
151,701✔
241
    auto current_version = transaction().get_version_of_current_transaction().version;
151,701✔
242
    if (m_schema_transaction_version == current_version)
151,701✔
243
        return;
109,137✔
244

21,004✔
245
    m_schema_transaction_version = current_version;
42,564✔
246
    m_schema_version = ObjectStore::get_schema_version(group);
42,564✔
247
    auto schema = ObjectStore::schema_from_group(group);
42,564✔
248

21,004✔
249
    if (m_coordinator)
42,564✔
250
        m_coordinator->cache_schema(schema, m_schema_version, m_schema_transaction_version);
1,060✔
251

21,004✔
252
    if (m_dynamic_schema) {
42,564✔
253
        if (m_schema == schema) {
42,094✔
254
            // The structure of the schema hasn't changed. Bring the table column indices up to date.
10,702✔
255
            m_schema.copy_keys_from(schema, SchemaSubsetMode::Strict);
21,639✔
256
        }
21,639✔
257
        else {
20,455✔
258
            // The structure of the schema has changed, so replace our copy of the schema.
10,071✔
259
            m_schema = std::move(schema);
20,455✔
260
        }
20,455✔
261
    }
42,094✔
262
    else {
470✔
263
        ObjectStore::verify_valid_external_changes(m_schema.compare(schema, m_config.schema_mode));
470✔
264
        m_schema.copy_keys_from(schema, m_config.schema_subset_mode);
470✔
265
    }
470✔
266
    notify_schema_changed();
42,564✔
267
}
42,564✔
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
{
55,078✔
288
    if (version == m_schema_version && changes.empty())
55,078✔
289
        return false;
33,180✔
290

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

6,870✔
297
        case SchemaMode::Immutable:
6,876✔
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,912✔
316
        case SchemaMode::AdditiveExplicit: {
7,868✔
317
            bool will_apply_index_changes = version > m_schema_version;
7,868✔
318
            if (ObjectStore::verify_valid_additive_changes(changes, will_apply_index_changes))
7,868✔
319
                return true;
7,740✔
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
{
55,308✔
337
    if (!m_config.immutable())
55,308✔
338
        do_refresh();
55,242✔
339

27,242✔
340
    // If the user hasn't specified a schema previously then m_schema is always
27,242✔
341
    // the full schema if it's been read
27,242✔
342
    if (m_dynamic_schema && !m_schema.empty())
55,308✔
343
        return m_schema;
33,260✔
344

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

356
bool Realm::is_empty()
357
{
4✔
358
    return ObjectStore::is_empty(read_group());
4✔
359
}
4✔
360

361
Class Realm::get_class(StringData object_type)
362
{
6✔
363
    auto it = m_schema.find(object_type);
6✔
364
    if (it == m_schema.end()) {
6✔
365
        throw LogicError(ErrorCodes::NoSuchTable, util::format("No type '%1'", object_type));
×
366
    }
×
367
    return {shared_from_this(), &*it};
6✔
368
}
6✔
369

370
std::vector<Class> Realm::get_classes()
371
{
×
372
    std::vector<Class> ret;
×
373
    ret.reserve(m_schema.size());
×
374
    auto r = shared_from_this();
×
375
    for (auto& os : m_schema) {
×
376
        ret.emplace_back(r, &os);
×
377
    }
×
378
    return ret;
×
379
}
×
380

381
void Realm::set_schema_subset(Schema schema)
382
{
12✔
383
    verify_thread();
12✔
384
    verify_open();
12✔
385
    REALM_ASSERT(m_dynamic_schema);
12✔
386
    REALM_ASSERT(m_schema_version != ObjectStore::NotVersioned);
12✔
387

6✔
388
    std::vector<SchemaChange> changes = m_schema.compare(schema, m_config.schema_mode);
12✔
389
    switch (m_config.schema_mode) {
12✔
390
        case SchemaMode::Automatic:
2✔
391
        case SchemaMode::SoftResetFile:
2✔
392
        case SchemaMode::HardResetFile:
2✔
393
            ObjectStore::verify_no_migration_required(changes);
2✔
394
            break;
2✔
395

1✔
396
        case SchemaMode::Immutable:
1✔
397
        case SchemaMode::ReadOnly:
✔
398
            ObjectStore::verify_compatible_for_immutable_and_readonly(changes);
×
399
            break;
×
400

401
        case SchemaMode::AdditiveDiscovered:
4✔
402
        case SchemaMode::AdditiveExplicit:
8✔
403
            ObjectStore::verify_valid_additive_changes(changes);
8✔
404
            break;
8✔
405

4✔
406
        case SchemaMode::Manual:
4✔
407
            ObjectStore::verify_no_changes_required(changes);
×
408
            break;
×
409
    }
10✔
410

5✔
411
    set_schema(m_schema, std::move(schema));
10✔
412
}
10✔
413

414
void Realm::update_schema(Schema schema, uint64_t version, MigrationFunction migration_function,
415
                          DataInitializationFunction initialization_function, bool in_transaction)
416
{
55,316✔
417
    uint64_t validation_mode = SchemaValidationMode::Basic;
55,316✔
418
#if REALM_ENABLE_SYNC
55,316✔
419
    if (auto sync_config = m_config.sync_config) {
55,316✔
420
        validation_mode |=
1,444✔
421
            sync_config->flx_sync_requested ? SchemaValidationMode::SyncFLX : SchemaValidationMode::SyncPBS;
1,216✔
422
    }
1,444✔
423
#endif
55,316✔
424
    if (m_config.schema_mode == SchemaMode::AdditiveExplicit) {
55,316✔
425
        validation_mode |= SchemaValidationMode::RejectEmbeddedOrphans;
8,506✔
426
    }
8,506✔
427

27,246✔
428
    schema.validate(static_cast<SchemaValidationMode>(validation_mode));
55,316✔
429

27,246✔
430
    bool was_in_read_transaction = is_in_read_transaction();
55,316✔
431
    Schema actual_schema = get_full_schema();
55,316✔
432

27,246✔
433
    // Frozen Realms never modify the schema on disk and we just need to verify
27,246✔
434
    // that the requested schema is compatible with what actually exists on disk
27,246✔
435
    // at that frozen version. Tables are allowed to be missing as those can be
27,246✔
436
    // represented by empty Results, but tables which exist must have all of the
27,246✔
437
    // requested properties with the correct type.
27,246✔
438
    if (m_frozen_version) {
55,316✔
439
        ObjectStore::verify_compatible_for_immutable_and_readonly(
240✔
440
            actual_schema.compare(schema, m_config.schema_mode, true));
240✔
441
        set_schema(actual_schema, std::move(schema));
240✔
442
        return;
240✔
443
    }
240✔
444

27,126✔
445
    std::vector<SchemaChange> required_changes = actual_schema.compare(schema, m_config.schema_mode);
55,076✔
446
    if (!schema_change_needs_write_transaction(schema, required_changes, version)) {
55,076✔
447
        if (!was_in_read_transaction)
33,252✔
448
            m_transaction = nullptr;
33,222✔
449
        set_schema(actual_schema, std::move(schema));
33,252✔
450
        return;
33,252✔
451
    }
33,252✔
452
    // Either the schema version has changed or we need to do non-migration changes
10,799✔
453

10,799✔
454
    // Cancel the write transaction if we exit this function before committing it
10,799✔
455
    auto cleanup = util::make_scope_exit([&]() noexcept {
21,824✔
456
        // When in_transaction is true, caller is responsible to cancel the transaction.
10,753✔
457
        if (!in_transaction && is_in_transaction())
21,732✔
458
            cancel_transaction();
110✔
459
        if (!was_in_read_transaction)
21,732✔
460
            m_transaction = nullptr;
21,380✔
461
    });
21,732✔
462

10,799✔
463
    if (!in_transaction) {
21,824✔
464
        transaction().promote_to_write();
21,706✔
465

10,740✔
466
        // Beginning the write transaction may have advanced the version and left
10,740✔
467
        // us with nothing to do if someone else initialized the schema on disk
10,740✔
468
        if (m_new_schema) {
21,706✔
469
            actual_schema = *m_new_schema;
12✔
470
            required_changes = actual_schema.compare(schema, m_config.schema_mode);
12✔
471
            if (!schema_change_needs_write_transaction(schema, required_changes, version)) {
12✔
472
                cancel_transaction();
2✔
473
                cache_new_schema();
2✔
474
                set_schema(actual_schema, std::move(schema));
2✔
475
                return;
2✔
476
            }
2✔
477
        }
21,704✔
478
        cache_new_schema();
21,704✔
479
    }
21,704✔
480

10,799✔
481
    schema.copy_keys_from(actual_schema, m_config.schema_subset_mode);
21,823✔
482

10,798✔
483
    uint64_t old_schema_version = m_schema_version;
21,822✔
484
    bool additive = m_config.schema_mode == SchemaMode::AdditiveDiscovered ||
21,822✔
485
                    m_config.schema_mode == SchemaMode::AdditiveExplicit ||
21,735✔
486
                    m_config.schema_mode == SchemaMode::ReadOnly;
17,817✔
487
    if (migration_function && !additive) {
21,822✔
488
        auto wrapper = [&] {
2,248✔
489
            auto config = m_config;
210✔
490
            config.schema_mode = SchemaMode::ReadOnly;
210✔
491
            config.schema = util::none;
210✔
492
            // Don't go through the normal codepath for opening a Realm because
105✔
493
            // we're using a mismatched config
105✔
494
            auto old_realm = std::make_shared<Realm>(std::move(config), none, m_coordinator, MakeSharedTag{});
210✔
495
            // block autorefresh for the old realm
105✔
496
            old_realm->m_auto_refresh = false;
210✔
497
            migration_function(old_realm, shared_from_this(), m_schema);
210✔
498
        };
210✔
499

2,143✔
500
        // migration function needs to see the target schema on the "new" Realm
2,143✔
501
        std::swap(m_schema, schema);
4,357✔
502
        std::swap(m_schema_version, version);
4,357✔
503
        m_in_migration = true;
4,357✔
504
        auto restore = util::make_scope_exit([&]() noexcept {
4,357✔
505
            std::swap(m_schema, schema);
4,357✔
506
            std::swap(m_schema_version, version);
4,357✔
507
            m_in_migration = false;
4,357✔
508
        });
4,357✔
509

2,143✔
510
        ObjectStore::apply_schema_changes(transaction(), version, m_schema, m_schema_version, m_config.schema_mode,
4,357✔
511
                                          required_changes, m_config.automatically_handle_backlinks_in_migrations,
4,357✔
512
                                          wrapper);
4,357✔
513
    }
4,357✔
514
    else {
17,465✔
515
        ObjectStore::apply_schema_changes(transaction(), m_schema_version, schema, version, m_config.schema_mode,
17,465✔
516
                                          required_changes, m_config.automatically_handle_backlinks_in_migrations);
17,465✔
517
        REALM_ASSERT_DEBUG(additive ||
17,465✔
518
                           (required_changes = ObjectStore::schema_from_group(read_group()).compare(schema)).empty());
17,465✔
519
    }
17,465✔
520

10,798✔
521
    if (initialization_function && old_schema_version == ObjectStore::NotVersioned) {
21,822✔
522
        // Initialization function needs to see the latest schema
15✔
523
        uint64_t temp_version = ObjectStore::get_schema_version(read_group());
30✔
524
        std::swap(m_schema, schema);
30✔
525
        std::swap(m_schema_version, temp_version);
30✔
526
        auto restore = util::make_scope_exit([&]() noexcept {
30✔
527
            std::swap(m_schema, schema);
30✔
528
            std::swap(m_schema_version, temp_version);
30✔
529
        });
30✔
530
        initialization_function(shared_from_this());
30✔
531
    }
30✔
532

10,798✔
533
    m_schema = std::move(schema);
21,822✔
534
    m_new_schema = ObjectStore::schema_from_group(read_group());
21,822✔
535
    m_schema_version = ObjectStore::get_schema_version(read_group());
21,822✔
536
    m_dynamic_schema = false;
21,822✔
537
    m_coordinator->clear_schema_cache_and_set_schema_version(version);
21,822✔
538

10,798✔
539
    if (!in_transaction) {
21,822✔
540
        m_coordinator->commit_write(*this);
21,594✔
541
        cache_new_schema();
21,594✔
542
    }
21,594✔
543

10,798✔
544
    notify_schema_changed();
21,822✔
545
}
21,822✔
546

547
void Realm::rename_property(Schema schema, StringData object_type, StringData old_name, StringData new_name)
548
{
4✔
549
    ObjectStore::rename_property(read_group(), schema, object_type, old_name, new_name);
4✔
550
}
4✔
551

552
void Realm::add_schema_change_handler()
553
{
110,253✔
554
    if (m_config.immutable())
110,253✔
555
        return;
56✔
556
    m_transaction->set_schema_change_notification_handler([&] {
110,197✔
557
        m_new_schema = ObjectStore::schema_from_group(read_group());
3,457✔
558
        m_schema_version = ObjectStore::get_schema_version(read_group());
3,457✔
559
        if (m_dynamic_schema) {
3,457✔
560
            m_schema = *m_new_schema;
32✔
561
        }
32✔
562
        else {
3,425✔
563
            m_schema.copy_keys_from(*m_new_schema, m_config.schema_subset_mode);
3,425✔
564
        }
3,425✔
565
        notify_schema_changed();
3,457✔
566
    });
3,457✔
567
}
110,197✔
568

569
void Realm::cache_new_schema()
570
{
203,555✔
571
    if (is_closed()) {
203,555✔
572
        return;
×
573
    }
×
574

99,422✔
575
    auto new_version = transaction().get_version_of_current_transaction().version;
203,555✔
576
    if (m_new_schema)
203,555✔
577
        m_coordinator->cache_schema(std::move(*m_new_schema), m_schema_version, new_version);
25,077✔
578
    else
178,478✔
579
        m_coordinator->advance_schema_cache(m_schema_transaction_version, new_version);
178,478✔
580
    m_schema_transaction_version = new_version;
203,555✔
581
    m_new_schema = util::none;
203,555✔
582
}
203,555✔
583

584
void Realm::translate_schema_error()
585
{
4✔
586
    // Read the new (incompatible) schema without changing our read transaction
2✔
587
    auto new_schema = ObjectStore::schema_from_group(*m_coordinator->begin_read());
4✔
588

2✔
589
    // Should always throw
2✔
590
    ObjectStore::verify_valid_external_changes(m_schema.compare(new_schema, m_config.schema_mode, true));
4✔
591

2✔
592
    // Something strange happened so just rethrow the old exception
2✔
593
    throw;
4✔
594
}
4✔
595

596
void Realm::notify_schema_changed()
597
{
101,119✔
598
    if (m_binding_context) {
101,119✔
599
        m_binding_context->schema_did_change(m_schema);
28✔
600
    }
28✔
601
}
101,119✔
602

603
static void check_can_create_write_transaction(const Realm* realm)
604
{
126,278✔
605
    realm->verify_thread();
126,278✔
606
    realm->verify_open();
126,278✔
607
    if (realm->config().immutable() || realm->config().read_only()) {
126,278✔
608
        throw WrongTransactionState("Can't perform transactions on read-only Realms.");
6✔
609
    }
6✔
610
    if (realm->is_frozen()) {
126,272✔
611
        throw WrongTransactionState("Can't perform transactions on a frozen Realm");
2✔
612
    }
2✔
613
    if (!realm->is_closed() && realm->get_number_of_versions() > realm->config().max_number_of_active_versions) {
126,270✔
614
        throw WrongTransactionState(
×
615
            util::format("Number of active versions (%1) in the Realm exceeded the limit of %2",
×
616
                         realm->get_number_of_versions(), realm->config().max_number_of_active_versions));
×
617
    }
×
618
}
126,270✔
619

620
void Realm::verify_thread() const
621
{
597,760✔
622
    if (m_scheduler && !m_scheduler->is_on_thread())
597,760✔
623
        throw LogicError(ErrorCodes::WrongThread, "Realm accessed from incorrect thread.");
8✔
624
}
597,760✔
625

626
void Realm::verify_in_write() const
627
{
11,968✔
628
    if (!is_in_transaction()) {
11,968✔
629
        throw WrongTransactionState("Cannot modify managed objects outside of a write transaction.");
4✔
630
    }
4✔
631
}
11,968✔
632

633
void Realm::verify_open() const
634
{
2,197,849✔
635
    if (is_closed()) {
2,197,849✔
636
        throw LogicError(ErrorCodes::ClosedRealm, "Cannot access realm that has been closed.");
44✔
637
    }
44✔
638
}
2,197,849✔
639

640
bool Realm::verify_notifications_available(bool throw_on_error) const
641
{
42,850✔
642
    if (is_frozen()) {
42,850✔
643
        if (throw_on_error)
56✔
644
            throw WrongTransactionState(
12✔
645
                "Notifications are not available on frozen collections since they do not change.");
12✔
646
        return false;
44✔
647
    }
44✔
648
    if (config().immutable()) {
42,794✔
649
        if (throw_on_error)
2✔
650
            throw WrongTransactionState("Cannot create asynchronous query for immutable Realms");
×
651
        return false;
2✔
652
    }
2✔
653
    if (throw_on_error) {
42,792✔
654
        if (m_transaction && m_transaction->get_commit_size() > 0)
9,967✔
655
            throw WrongTransactionState(
2✔
656
                "Cannot create asynchronous query after making changes in a write transaction.");
2✔
657
    }
32,825✔
658
    else {
32,825✔
659
        // Don't create implicit notifiers inside write transactions even if
16,322✔
660
        // we could as it wouldn't actually be used
16,322✔
661
        if (is_in_transaction())
32,825✔
662
            return false;
20,616✔
663
    }
22,174✔
664

11,033✔
665
    return true;
22,174✔
666
}
22,174✔
667

668
VersionID Realm::read_transaction_version() const
669
{
5,084✔
670
    verify_thread();
5,084✔
671
    verify_open();
5,084✔
672
    REALM_ASSERT(m_transaction);
5,084✔
673
    return m_transaction->get_version_of_current_transaction();
5,084✔
674
}
5,084✔
675

676
uint_fast64_t Realm::get_number_of_versions() const
677
{
126,263✔
678
    verify_open();
126,263✔
679
    return m_coordinator->get_number_of_versions();
126,263✔
680
}
126,263✔
681

682
bool Realm::is_in_transaction() const noexcept
683
{
695,547✔
684
    return !m_config.immutable() && !is_closed() && m_transaction &&
695,547✔
685
           transaction().get_transact_stage() == DB::transact_Writing;
647,346✔
686
}
695,547✔
687

688
bool Realm::is_in_async_transaction() const noexcept
689
{
886✔
690
    return !m_config.immutable() && !is_closed() && m_transaction && m_transaction->is_async();
886✔
691
}
886✔
692

693
util::Optional<VersionID> Realm::current_transaction_version() const
694
{
104,819✔
695
    util::Optional<VersionID> ret;
104,819✔
696
    if (m_transaction) {
104,819✔
697
        ret = m_transaction->get_version_of_current_transaction();
104,549✔
698
    }
104,549✔
699
    else if (m_frozen_version) {
270✔
700
        ret = m_frozen_version;
×
701
    }
×
702
    return ret;
104,819✔
703
}
104,819✔
704

705
// Get the version of the latest snapshot
706
util::Optional<DB::version_type> Realm::latest_snapshot_version() const
707
{
62✔
708
    util::Optional<DB::version_type> ret;
62✔
709
    if (m_transaction) {
62✔
710
        ret = m_transaction->get_version_of_latest_snapshot();
60✔
711
    }
60✔
712
    return ret;
62✔
713
}
62✔
714

715
void Realm::enable_wait_for_change()
716
{
2✔
717
    verify_open();
2✔
718
    m_coordinator->enable_wait_for_change();
2✔
719
}
2✔
720

721
bool Realm::wait_for_change()
722
{
6✔
723
    verify_open();
6✔
724
    if (m_frozen_version || m_config.schema_mode == SchemaMode::Immutable) {
6✔
725
        return false;
4✔
726
    }
4✔
727
    return m_transaction && m_coordinator->wait_for_change(m_transaction);
2!
728
}
2✔
729

730
void Realm::wait_for_change_release()
731
{
2✔
732
    verify_open();
2✔
733
    m_coordinator->wait_for_change_release();
2✔
734
}
2✔
735

736
bool Realm::has_pending_async_work() const
737
{
996✔
738
    verify_thread();
996✔
739
    return !m_async_commit_q.empty() || !m_async_write_q.empty() || (m_transaction && m_transaction->is_async());
996✔
740
}
996✔
741

742
void Realm::run_writes_on_proper_thread()
743
{
7✔
744
    m_scheduler->invoke([self = shared_from_this()] {
7✔
745
        self->run_writes();
7✔
746
    });
7✔
747
}
7✔
748

749
void Realm::call_completion_callbacks()
750
{
174,883✔
751
    if (m_is_running_async_commit_completions) {
174,883✔
752
        return;
4✔
753
    }
4✔
754

85,724✔
755
    CountGuard sending_completions(m_is_running_async_commit_completions);
174,879✔
756
    auto error = m_transaction->get_commit_exception();
174,879✔
757
    auto completions = std::move(m_async_commit_q);
174,879✔
758
    m_async_commit_q.clear();
174,879✔
759
    for (auto& cb : completions) {
87,103✔
760
        if (!cb.when_completed)
2,510✔
761
            continue;
16✔
762
        if (m_async_exception_handler) {
2,494✔
763
            try {
2✔
764
                cb.when_completed(error);
2✔
765
            }
2✔
766
            catch (...) {
2✔
767
                m_async_exception_handler(cb.handle, std::current_exception());
2✔
768
            }
2✔
769
        }
2✔
770
        else {
2,492✔
771
            cb.when_completed(error);
2,492✔
772
        }
2,492✔
773
    }
2,494✔
774
}
174,879✔
775

776
void Realm::run_async_completions()
777
{
330✔
778
    call_completion_callbacks();
330✔
779
    check_pending_write_requests();
330✔
780
}
330✔
781

782
void Realm::check_pending_write_requests()
783
{
59,493✔
784
    if (!m_async_write_q.empty()) {
59,493✔
785
        if (m_transaction->is_async()) {
420✔
786
            run_writes_on_proper_thread();
7✔
787
        }
7✔
788
        else {
413✔
789
            m_coordinator->async_request_write_mutex(*this);
413✔
790
        }
413✔
791
    }
420✔
792
}
59,493✔
793

794
void Realm::end_current_write(bool check_pending)
795
{
3,300✔
796
    if (!m_transaction) {
3,300✔
797
        return;
2✔
798
    }
2✔
799
    m_transaction->async_complete_writes([self = shared_from_this(), this]() mutable {
3,298✔
800
        m_scheduler->invoke([self = std::move(self), this]() mutable {
330✔
801
            run_async_completions();
330✔
802
            self.reset();
330✔
803
        });
330✔
804
    });
330✔
805
    if (check_pending && m_async_commit_q.empty()) {
3,298✔
806
        check_pending_write_requests();
2,842✔
807
    }
2,842✔
808
}
3,298✔
809

810
void Realm::run_writes()
811
{
502✔
812
    if (!m_transaction) {
502✔
813
        // Realm might have been closed
814
        return;
×
815
    }
×
816
    if (m_transaction->is_synchronizing()) {
502✔
817
        // Wait for the synchronization complete callback before we run more
818
        // writes as we can't add commits while in that state
UNCOV
819
        return;
×
UNCOV
820
    }
×
821
    if (is_in_transaction()) {
502✔
822
        // This is scheduled asynchronously after acquiring the write lock, so
1✔
823
        // in that time a synchronous transaction may have been started. If so,
1✔
824
        // we'll be re-invoked when that transaction ends.
1✔
825
        return;
2✔
826
    }
2✔
827

125✔
828
    CountGuard running_writes(m_is_running_async_writes);
500✔
829
    int run_limit = 20; // max number of commits without full sync to disk
500✔
830
    // this is tricky
125✔
831
    //  - each pending call may itself add other async writes
125✔
832
    //  - the 'run' will terminate as soon as a commit without grouping is requested
125✔
833
    while (!m_async_write_q.empty() && m_transaction) {
2,710✔
834
        // We might have made a sync commit and thereby given up the write lock
1,150✔
835
        if (!m_transaction->holds_write_mutex()) {
2,661✔
836
            return;
115✔
837
        }
115✔
838

1,149✔
839
        do_begin_transaction();
2,546✔
840

1,149✔
841
        auto write_desc = std::move(m_async_write_q.front());
2,546✔
842
        m_async_write_q.pop_front();
2,546✔
843

1,149✔
844
        // prevent any calls to commit/cancel during a simple notification
1,149✔
845
        m_notify_only = write_desc.notify_only;
2,546✔
846
        m_async_commit_barrier_requested = false;
2,546✔
847
        auto prev_version = m_transaction->get_version();
2,546✔
848
        try {
2,546✔
849
            write_desc.writer();
2,546✔
850
        }
2,546✔
851
        catch (const std::exception&) {
1,152✔
852
            if (m_transaction) {
6✔
853
                transaction::cancel(*m_transaction, m_binding_context.get());
4✔
854
            }
4✔
855
            m_notify_only = false;
6✔
856

3✔
857
            if (m_async_exception_handler) {
6✔
858
                m_async_exception_handler(write_desc.handle, std::current_exception());
2✔
859
                continue;
2✔
860
            }
2✔
861
            end_current_write();
4✔
862
            throw;
4✔
863
        }
4✔
864

1,146✔
865
        // if we've merely delivered a notification, the full transaction will follow later
1,146✔
866
        // and terminate with a call to async commit or async cancel
1,146✔
867
        if (m_notify_only) {
2,540✔
868
            m_notify_only = false;
12✔
869
            return;
12✔
870
        }
12✔
871

1,140✔
872
        // Realm may have been closed in the write function
1,140✔
873
        if (!m_transaction) {
2,528✔
874
            return;
20✔
875
        }
20✔
876

1,130✔
877
        auto new_version = m_transaction->get_version();
2,508✔
878
        // if we've run the full transaction, there is follow up work to do:
1,130✔
879
        if (new_version > prev_version) {
2,508✔
880
            // A commit was done during callback
1,125✔
881
            --run_limit;
2,498✔
882
            if (run_limit <= 0)
2,498✔
883
                break;
102✔
884
        }
10✔
885
        else {
10✔
886
            if (m_transaction->get_transact_stage() == DB::transact_Writing) {
10✔
887
                // Still in writing stage - we make a rollback
4✔
888
                transaction::cancel(transaction(), m_binding_context.get());
8✔
889
            }
8✔
890
        }
10✔
891
        if (m_async_commit_barrier_requested)
2,457✔
892
            break;
198✔
893
    }
2,406✔
894

125✔
895
    end_current_write();
368✔
896
}
349✔
897

898
auto Realm::async_begin_transaction(util::UniqueFunction<void()>&& the_write_block, bool notify_only) -> AsyncHandle
899
{
2,560✔
900
    check_can_create_write_transaction(this);
2,560✔
901
    if (m_is_running_async_commit_completions) {
2,560✔
902
        throw WrongTransactionState("Can't begin a write transaction from inside a commit completion callback.");
×
903
    }
×
904
    if (!m_scheduler->can_invoke()) {
2,560✔
905
        throw WrongTransactionState(
×
906
            "Cannot schedule async transaction. Make sure you are running from inside a run loop.");
×
907
    }
×
908
    REALM_ASSERT(the_write_block);
2,560✔
909

1,156✔
910
    // make sure we have a (at least a) read transaction
1,156✔
911
    transaction();
2,560✔
912
    auto handle = m_async_commit_handle++;
2,560✔
913
    m_async_write_q.push_back({std::move(the_write_block), notify_only, handle});
2,560✔
914

1,156✔
915
    if (!m_is_running_async_writes && !m_transaction->is_async() &&
2,560✔
916
        m_transaction->get_transact_stage() != DB::transact_Writing) {
1,236✔
917
        m_coordinator->async_request_write_mutex(*this);
125✔
918
    }
125✔
919
    return handle;
2,560✔
920
}
2,560✔
921

922
auto Realm::async_commit_transaction(util::UniqueFunction<void(std::exception_ptr)>&& completion, bool allow_grouping)
923
    -> AsyncHandle
924
{
2,514✔
925
    check_can_create_write_transaction(this);
2,514✔
926
    if (m_is_running_async_commit_completions) {
2,514✔
927
        throw WrongTransactionState("Can't commit a write transaction from inside a commit completion callback.");
×
928
    }
×
929
    if (!is_in_transaction()) {
2,514✔
930
        throw WrongTransactionState("Can't commit a non-existing write transaction");
×
931
    }
×
932

1,133✔
933
    m_transaction->promote_to_async();
2,514✔
934
    REALM_ASSERT(m_transaction->holds_write_mutex());
2,514✔
935
    REALM_ASSERT(!m_notify_only);
2,514✔
936
    // auditing is not supported
1,133✔
937
    REALM_ASSERT(!audit_context());
2,514✔
938
    // grab a version lock on current version, push it along with the done block
1,133✔
939
    // do in-buffer-cache commit_transaction();
1,133✔
940
    auto handle = m_async_commit_handle++;
2,514✔
941
    m_async_commit_q.push_back({std::move(completion), handle});
2,514✔
942
    try {
2,514✔
943
        m_coordinator->commit_write(*this, /* commit_to_disk: */ false);
2,514✔
944
    }
2,514✔
945
    catch (...) {
1,135✔
946
        // If the exception happened before the commit, we need to roll back the
2✔
947
        // transaction and remove the completion handler from the queue
2✔
948
        if (is_in_transaction()) {
4✔
949
            // Exception happened before the commit, so roll back the transaction
1✔
950
            // and remove the completion handler from the queue
1✔
951
            cancel_transaction();
2✔
952
            auto it = std::find_if(m_async_commit_q.begin(), m_async_commit_q.end(), [=](auto& e) {
2✔
953
                return e.handle == handle;
2✔
954
            });
2✔
955
            if (it != m_async_commit_q.end()) {
2✔
956
                m_async_commit_q.erase(it);
2✔
957
            }
2✔
958
        }
2✔
959
        else if (m_transaction) {
2✔
960
            end_current_write(false);
2✔
961
        }
2✔
962
        throw;
4✔
963
    }
4✔
964

1,130✔
965
    if (m_is_running_async_writes) {
2,508✔
966
        // we're called from with the callback loop and it will take care of releasing lock
1,122✔
967
        // if applicable, and of triggering followup runs of callbacks
1,122✔
968
        if (!allow_grouping) {
2,368✔
969
            m_async_commit_barrier_requested = true;
206✔
970
        }
206✔
971
    }
2,368✔
972
    else {
140✔
973
        // we're called from outside the callback loop so we have to take care of
8✔
974
        // releasing any lock and of keeping callbacks coming.
8✔
975
        if (allow_grouping) {
140✔
976
            run_writes();
×
977
        }
×
978
        else {
140✔
979
            end_current_write(false);
140✔
980
        }
140✔
981
    }
140✔
982
    return handle;
2,508✔
983
}
2,508✔
984

985
bool Realm::async_cancel_transaction(AsyncHandle handle)
986
{
6✔
987
    verify_thread();
6✔
988
    verify_open();
6✔
989
    auto compare = [handle](auto& elem) {
5✔
990
        return elem.handle == handle;
4✔
991
    };
4✔
992

3✔
993
    auto it1 = std::find_if(m_async_write_q.begin(), m_async_write_q.end(), compare);
6✔
994
    if (it1 != m_async_write_q.end()) {
6✔
995
        m_async_write_q.erase(it1);
2✔
996
        return true;
2✔
997
    }
2✔
998
    auto it2 = std::find_if(m_async_commit_q.begin(), m_async_commit_q.end(), compare);
4✔
999
    if (it2 != m_async_commit_q.end()) {
4✔
1000
        // Just delete the callback. It is important that we know
1✔
1001
        // that there are still commits pending.
1✔
1002
        it2->when_completed = nullptr;
2✔
1003
        return true;
2✔
1004
    }
2✔
1005
    return false;
2✔
1006
}
2✔
1007

1008
void Realm::begin_transaction()
1009
{
62,066✔
1010
    check_can_create_write_transaction(this);
62,066✔
1011

30,480✔
1012
    if (is_in_transaction()) {
62,066✔
1013
        throw WrongTransactionState("The Realm is already in a write transaction");
×
1014
    }
×
1015

30,480✔
1016
    // Any of the callbacks to user code below could drop the last remaining
30,480✔
1017
    // strong reference to `this`
30,480✔
1018
    auto retain_self = shared_from_this();
62,066✔
1019

30,480✔
1020
    // make sure we have a read transaction
30,480✔
1021
    read_group();
62,066✔
1022

30,480✔
1023
    do_begin_transaction();
62,066✔
1024
}
62,066✔
1025

1026
void Realm::do_begin_transaction()
1027
{
64,604✔
1028
    CountGuard sending_notifications(m_is_sending_notifications);
64,604✔
1029
    try {
64,604✔
1030
        m_coordinator->promote_to_write(*this);
64,604✔
1031
    }
64,604✔
1032
    catch (_impl::UnsupportedSchemaChange const&) {
31,625✔
1033
        translate_schema_error();
×
1034
    }
×
1035
    cache_new_schema();
64,604✔
1036

31,625✔
1037
    if (m_transaction && !m_transaction->has_unsynced_commits()) {
64,604✔
1038
        call_completion_callbacks();
62,554✔
1039
    }
62,554✔
1040
}
64,604✔
1041

1042
void Realm::commit_transaction()
1043
{
56,317✔
1044
    check_can_create_write_transaction(this);
56,317✔
1045

27,601✔
1046
    if (!is_in_transaction()) {
56,317✔
1047
        throw WrongTransactionState("Can't commit a non-existing write transaction");
×
1048
    }
×
1049

27,601✔
1050
    DB::VersionID prev_version = transaction().get_version_of_current_transaction();
56,317✔
1051
    if (auto audit = audit_context()) {
56,317✔
1052
        audit->prepare_for_write(prev_version);
91✔
1053
    }
91✔
1054

27,601✔
1055
    m_coordinator->commit_write(*this, /* commit_to_disk */ true);
56,317✔
1056
    cache_new_schema();
56,317✔
1057

27,601✔
1058
    // Realm might have been closed
27,601✔
1059
    if (m_transaction) {
56,317✔
1060
        // Any previous async commits got flushed along with the sync commit
27,600✔
1061
        call_completion_callbacks();
56,315✔
1062
        // If we have pending async writes we need to rerequest the write mutex
27,600✔
1063
        check_pending_write_requests();
56,315✔
1064
    }
56,315✔
1065
    if (auto audit = audit_context()) {
56,317✔
1066
        audit->record_write(prev_version, transaction().get_version_of_current_transaction());
91✔
1067
    }
91✔
1068
}
56,317✔
1069

1070
void Realm::cancel_transaction()
1071
{
2,821✔
1072
    check_can_create_write_transaction(this);
2,821✔
1073

1,415✔
1074
    if (m_is_running_async_commit_completions) {
2,821✔
1075
        throw WrongTransactionState("Can't cancel a write transaction from inside a commit completion callback.");
×
1076
    }
×
1077
    if (!is_in_transaction()) {
2,821✔
1078
        throw WrongTransactionState("Can't cancel a non-existing write transaction");
×
1079
    }
×
1080

1,415✔
1081
    transaction::cancel(transaction(), m_binding_context.get());
2,821✔
1082

1,415✔
1083
    if (m_transaction && !m_is_running_async_writes) {
2,821✔
1084
        if (m_async_write_q.empty()) {
2,813✔
1085
            end_current_write();
2,805✔
1086
        }
2,805✔
1087
        else {
8✔
1088
            check_pending_write_requests();
8✔
1089
        }
8✔
1090
    }
2,813✔
1091
}
2,821✔
1092

1093
void Realm::invalidate()
1094
{
246✔
1095
    verify_thread();
246✔
1096
    verify_open();
246✔
1097

123✔
1098
    if (m_is_sending_notifications) {
246✔
1099
        // This was originally because closing the Realm during notification
3✔
1100
        // sending would break things, but we now support that. However, it's a
3✔
1101
        // breaking change so we keep the old behavior for now.
3✔
1102
        return;
6✔
1103
    }
6✔
1104

120✔
1105
    if (is_in_transaction()) {
240✔
1106
        cancel_transaction();
172✔
1107
    }
172✔
1108

120✔
1109
    do_invalidate();
240✔
1110
}
240✔
1111

1112
void Realm::do_invalidate()
1113
{
4,255✔
1114
    if (!m_config.immutable() && m_transaction) {
4,255✔
1115
        m_transaction->prepare_for_close();
4,177✔
1116
        call_completion_callbacks();
4,177✔
1117
        transaction().close();
4,177✔
1118
    }
4,177✔
1119

2,112✔
1120
    m_transaction = nullptr;
4,255✔
1121
    m_async_write_q.clear();
4,255✔
1122
    m_async_commit_q.clear();
4,255✔
1123
}
4,255✔
1124

1125
bool Realm::compact()
1126
{
8✔
1127
    verify_thread();
8✔
1128
    verify_open();
8✔
1129

4✔
1130
    if (m_config.immutable() || m_config.read_only()) {
8✔
1131
        throw WrongTransactionState("Can't compact a read-only Realm");
4✔
1132
    }
4✔
1133
    if (is_in_transaction()) {
4✔
1134
        throw WrongTransactionState("Can't compact a Realm within a write transaction");
×
1135
    }
×
1136

2✔
1137
    verify_open();
4✔
1138
    m_transaction = nullptr;
4✔
1139
    return m_coordinator->compact();
4✔
1140
}
4✔
1141

1142
void Realm::convert(const Config& config, bool merge_into_existing)
1143
{
60✔
1144
    verify_thread();
60✔
1145
    verify_open();
60✔
1146

30✔
1147
#if REALM_ENABLE_SYNC
60✔
1148
    auto src_is_flx_sync = m_config.sync_config && m_config.sync_config->flx_sync_requested;
60✔
1149
    auto dst_is_flx_sync = config.sync_config && config.sync_config->flx_sync_requested;
60✔
1150
    auto dst_is_pbs_sync = config.sync_config && !config.sync_config->flx_sync_requested;
60✔
1151

30✔
1152
    if (dst_is_flx_sync && !src_is_flx_sync) {
60✔
1153
        throw IllegalOperation(
4✔
1154
            "Realm cannot be converted to a flexible sync realm unless flexible sync is already enabled");
4✔
1155
    }
4✔
1156
    if (dst_is_pbs_sync && src_is_flx_sync) {
56✔
1157
        throw IllegalOperation(
2✔
1158
            "Realm cannot be converted from a flexible sync realm to a partition based sync realm");
2✔
1159
    }
2✔
1160

27✔
1161
#endif
54✔
1162

27✔
1163
    if (merge_into_existing && util::File::exists(config.path)) {
54✔
1164
        auto destination_realm = Realm::get_shared_realm(config);
6✔
1165
        destination_realm->begin_transaction();
6✔
1166
        auto destination = destination_realm->transaction_ref();
6✔
1167
        m_transaction->copy_to(destination);
6✔
1168
        destination_realm->commit_transaction();
6✔
1169
        return;
6✔
1170
    }
6✔
1171

24✔
1172
    if (config.encryption_key.size() && config.encryption_key.size() != 64) {
48!
1173
        throw InvalidEncryptionKey();
×
1174
    }
×
1175

24✔
1176
    auto& tr = transaction();
48✔
1177
    auto repl = tr.get_replication();
48✔
1178
    bool src_is_sync = repl && repl->get_history_type() == Replication::hist_SyncClient;
48✔
1179
    bool dst_is_sync = config.sync_config || config.force_sync_history;
48✔
1180

24✔
1181
    if (dst_is_sync) {
48✔
1182
        m_coordinator->write_copy(config.path, config.encryption_key.data());
20✔
1183
        if (!src_is_sync) {
20✔
1184
#if REALM_ENABLE_SYNC
8✔
1185
            DBOptions options;
8✔
1186
            if (config.encryption_key.size()) {
8✔
1187
                options.encryption_key = config.encryption_key.data();
×
1188
            }
×
1189
            auto db = DB::create(make_in_realm_history(), config.path, options);
8✔
1190
            db->create_new_history(sync::make_client_replication());
8✔
1191
#endif
8✔
1192
        }
8✔
1193
    }
20✔
1194
    else {
28✔
1195
        tr.write(config.path, config.encryption_key.data());
28✔
1196
    }
28✔
1197
}
48✔
1198

1199
OwnedBinaryData Realm::write_copy()
1200
{
6✔
1201
    verify_thread();
6✔
1202
    BinaryData buffer = read_group().write_to_mem();
6✔
1203

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

1209
void Realm::notify()
1210
{
24,691✔
1211
    if (is_closed() || is_in_transaction() || is_frozen()) {
24,691✔
1212
        return;
330✔
1213
    }
330✔
1214

10,672✔
1215
    verify_thread();
24,361✔
1216

10,672✔
1217
    // Any of the callbacks to user code below could drop the last remaining
10,672✔
1218
    // strong reference to `this`
10,672✔
1219
    auto retain_self = shared_from_this();
24,361✔
1220

10,672✔
1221
    if (m_binding_context) {
24,361✔
1222
        m_binding_context->before_notify();
558✔
1223
        if (is_closed() || is_in_transaction()) {
558✔
1224
            return;
×
1225
        }
×
1226
    }
24,361✔
1227

10,672✔
1228
    if (!m_coordinator->can_advance(*this)) {
24,361✔
1229
        CountGuard sending_notifications(m_is_sending_notifications);
17,326✔
1230
        m_coordinator->process_available_async(*this);
17,326✔
1231
        return;
17,326✔
1232
    }
17,326✔
1233

2,848✔
1234
    if (m_binding_context) {
7,035✔
1235
        m_binding_context->changes_available();
74✔
1236

37✔
1237
        // changes_available() may have advanced the read version, and if
37✔
1238
        // so we don't need to do anything further
37✔
1239
        if (!m_coordinator->can_advance(*this))
74✔
1240
            return;
4✔
1241
    }
7,031✔
1242

2,846✔
1243
    CountGuard sending_notifications(m_is_sending_notifications);
7,031✔
1244
    if (m_auto_refresh) {
7,031✔
1245
        if (m_transaction) {
7,028✔
1246
            try {
6,958✔
1247
                m_coordinator->advance_to_ready(*this);
6,958✔
1248
            }
6,958✔
1249
            catch (_impl::UnsupportedSchemaChange const&) {
2,808✔
1250
                translate_schema_error();
×
1251
            }
×
1252
            if (!is_closed())
6,958✔
1253
                cache_new_schema();
6,956✔
1254
        }
6,958✔
1255
        else {
70✔
1256
            if (m_binding_context) {
70✔
1257
                m_binding_context->did_change({}, {});
4✔
1258
            }
4✔
1259
            if (!is_closed()) {
70✔
1260
                m_coordinator->process_available_async(*this);
68✔
1261
            }
68✔
1262
        }
70✔
1263
    }
7,028✔
1264
}
7,031✔
1265

1266
bool Realm::refresh()
1267
{
64,183✔
1268
    verify_thread();
64,183✔
1269
    return do_refresh();
64,183✔
1270
}
64,183✔
1271

1272
bool Realm::do_refresh()
1273
{
119,425✔
1274
    // Frozen Realms never change.
58,818✔
1275
    if (is_frozen()) {
119,425✔
1276
        return false;
244✔
1277
    }
244✔
1278

58,696✔
1279
    if (m_config.immutable()) {
119,181✔
1280
        throw WrongTransactionState("Can't refresh an immutable Realm.");
4✔
1281
    }
4✔
1282

58,694✔
1283
    // can't be any new changes if we're in a write transaction
58,694✔
1284
    if (is_in_transaction()) {
119,177✔
1285
        return false;
30✔
1286
    }
30✔
1287
    // don't advance if we're already in the process of advancing as that just
58,679✔
1288
    // makes things needlessly complicated
58,679✔
1289
    if (m_is_sending_notifications) {
119,147✔
1290
        return false;
8✔
1291
    }
8✔
1292

58,675✔
1293
    // Any of the callbacks to user code below could drop the last remaining
58,675✔
1294
    // strong reference to `this`
58,675✔
1295
    auto retain_self = shared_from_this();
119,139✔
1296

58,675✔
1297
    CountGuard sending_notifications(m_is_sending_notifications);
119,139✔
1298
    if (m_binding_context) {
119,139✔
1299
        m_binding_context->before_notify();
138✔
1300
    }
138✔
1301
    if (m_transaction) {
119,139✔
1302
        try {
32,390✔
1303
            bool version_changed = m_coordinator->advance_to_latest(*this);
32,390✔
1304
            if (is_closed())
32,390✔
1305
                return false;
6✔
1306
            cache_new_schema();
32,384✔
1307
            return version_changed;
32,384✔
1308
        }
32,384✔
1309
        catch (_impl::UnsupportedSchemaChange const&) {
4✔
1310
            translate_schema_error();
4✔
1311
        }
4✔
1312
    }
32,390✔
1313

58,675✔
1314
    // No current read transaction, so just create a new one
58,675✔
1315
    read_group();
102,720✔
1316
    m_coordinator->process_available_async(*this);
86,749✔
1317
    return true;
86,749✔
1318
}
119,139✔
1319

1320
void Realm::set_auto_refresh(bool auto_refresh)
1321
{
16✔
1322
    if (is_frozen() && auto_refresh) {
16✔
1323
        throw WrongTransactionState("Auto-refresh cannot be enabled for frozen Realms.");
2✔
1324
    }
2✔
1325
    m_auto_refresh = auto_refresh;
14✔
1326
}
14✔
1327

1328

1329
bool Realm::can_deliver_notifications() const noexcept
1330
{
12,209✔
1331
    if (m_config.immutable() || !m_config.automatic_change_notifications) {
12,209✔
1332
        return false;
12,153✔
1333
    }
12,153✔
1334

28✔
1335
    if (!m_scheduler || !m_scheduler->can_invoke()) {
56✔
1336
        return false;
×
1337
    }
×
1338

28✔
1339
    return true;
56✔
1340
}
56✔
1341

1342
uint64_t Realm::get_schema_version(const Realm::Config& config)
1343
{
×
1344
    auto coordinator = RealmCoordinator::get_coordinator(config.path);
×
1345
    auto version = coordinator->get_schema_version();
×
1346
    if (version == ObjectStore::NotVersioned)
×
1347
        version = ObjectStore::get_schema_version(coordinator->get_realm(config, util::none)->read_group());
×
1348
    return version;
×
1349
}
×
1350

1351

1352
bool Realm::is_frozen() const
1353
{
377,902✔
1354
    bool result = bool(m_frozen_version);
377,902✔
1355
    REALM_ASSERT_DEBUG(!result || !m_transaction || m_transaction->is_frozen());
377,902✔
1356
    return result;
377,902✔
1357
}
377,902✔
1358

1359
SharedRealm Realm::freeze()
1360
{
635✔
1361
    read_group(); // Freezing requires a read transaction
635✔
1362
    return m_coordinator->freeze_realm(*this);
635✔
1363
}
635✔
1364

1365
void Realm::copy_schema_from(const Realm& source)
1366
{
631✔
1367
    REALM_ASSERT(is_frozen());
631✔
1368
    REALM_ASSERT(m_frozen_version == source.read_transaction_version());
631✔
1369
    m_schema = source.m_schema;
631✔
1370
    m_schema_version = source.m_schema_version;
631✔
1371
    m_schema_transaction_version = m_frozen_version->version;
631✔
1372
    m_dynamic_schema = false;
631✔
1373
}
631✔
1374

1375
void Realm::close()
1376
{
4,037✔
1377
    if (is_closed()) {
4,037✔
1378
        return;
20✔
1379
    }
20✔
1380
    if (m_coordinator) {
4,017✔
1381
        m_coordinator->unregister_realm(this);
4,017✔
1382
    }
4,017✔
1383

1,993✔
1384
    do_invalidate();
4,017✔
1385

1,993✔
1386
    m_binding_context = nullptr;
4,017✔
1387
    m_coordinator = nullptr;
4,017✔
1388
    m_scheduler = nullptr;
4,017✔
1389
    m_config = {};
4,017✔
1390
}
4,017✔
1391

1392
void Realm::delete_files(const std::string& realm_file_path, bool* did_delete_realm)
1393
{
16✔
1394
    bool lock_successful = false;
16✔
1395
    try {
16✔
1396
        lock_successful = DB::call_with_lock(realm_file_path, [=](auto const& path) {
14✔
1397
            DB::delete_files(path, did_delete_realm);
12✔
1398
        });
12✔
1399
    }
16✔
1400
    catch (const FileAccessError& e) {
9✔
1401
        if (e.code() != ErrorCodes::FileNotFound) {
2✔
1402
            throw;
×
1403
        }
×
1404
        // Thrown only if the parent directory of the lock file does not exist,
1✔
1405
        // which obviously indicates that we didn't need to delete anything
1✔
1406
        if (did_delete_realm) {
2✔
1407
            *did_delete_realm = false;
2✔
1408
        }
2✔
1409
        return;
2✔
1410
    }
2✔
1411
    if (!lock_successful) {
14✔
1412
        throw FileAccessError(
2✔
1413
            ErrorCodes::DeleteOnOpenRealm,
2✔
1414
            util::format("Cannot delete files of an open Realm: '%1' is still in use.", realm_file_path),
2✔
1415
            realm_file_path);
2✔
1416
    }
2✔
1417
}
14✔
1418

1419
AuditInterface* Realm::audit_context() const noexcept
1420
{
180,693✔
1421
    return m_coordinator ? m_coordinator->audit_context() : nullptr;
180,693✔
1422
}
180,693✔
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

© 2025 Coveralls, Inc