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

realm / realm-core / 1863

24 Nov 2023 10:28AM UTC coverage: 91.688% (-0.02%) from 91.708%
1863

push

Evergreen

web-flow
Add missing install headers (#7165)

* add missing install headers

* validate installed headers on PR triggers

92392 of 169288 branches covered (0.0%)

231720 of 252727 relevant lines covered (91.69%)

6390825.2 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
    {
398,239✔
65
        ++m_count;
398,239✔
66
    }
398,239✔
67
    ~CountGuard()
68
    {
398,242✔
69
        --m_count;
398,242✔
70
    }
398,242✔
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,752✔
83
    if (version) {
59,752✔
84
        m_auto_refresh = false;
1,006✔
85
        REALM_ASSERT(*version != VersionID());
1,006✔
86
    }
1,006✔
87
    else if (!coordinator->get_cached_schema(m_schema, m_schema_version, m_schema_transaction_version)) {
58,746✔
88
        m_transaction = coordinator->begin_read();
43,420✔
89
        read_schema_from_group_if_needed();
43,420✔
90
        coordinator->cache_schema(m_schema, m_schema_version, m_schema_transaction_version);
43,420✔
91
        m_transaction = nullptr;
43,420✔
92
    }
43,420✔
93
    m_coordinator = std::move(coordinator);
59,752✔
94
}
59,752✔
95

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

29,461✔
104
    if (m_coordinator) {
59,752✔
105
        m_coordinator->unregister_realm(this);
55,475✔
106
    }
55,475✔
107
}
59,752✔
108

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

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

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

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

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

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

192
std::shared_ptr<SyncSession> Realm::sync_session() const
193
{
30,775,312✔
194
    return m_coordinator ? m_coordinator->sync_session() : nullptr;
30,775,311✔
195
}
30,775,312✔
196

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

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

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

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

78,034✔
240
    Group& group = read_group();
158,250✔
241
    auto current_version = transaction().get_version_of_current_transaction().version;
158,250✔
242
    if (m_schema_transaction_version == current_version)
158,250✔
243
        return;
113,811✔
244

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

21,940✔
249
    if (m_coordinator)
44,439✔
250
        m_coordinator->cache_schema(schema, m_schema_version, m_schema_transaction_version);
1,081✔
251

21,940✔
252
    if (m_dynamic_schema) {
44,439✔
253
        if (m_schema == schema) {
43,952✔
254
            // The structure of the schema hasn't changed. Bring the table column indices up to date.
11,077✔
255
            m_schema.copy_keys_from(schema, SchemaSubsetMode::Strict);
22,389✔
256
        }
22,389✔
257
        else {
21,563✔
258
            // The structure of the schema has changed, so replace our copy of the schema.
10,625✔
259
            m_schema = std::move(schema);
21,563✔
260
        }
21,563✔
261
    }
43,952✔
262
    else {
487✔
263
        ObjectStore::verify_valid_external_changes(m_schema.compare(schema, m_config.schema_mode));
487✔
264
        m_schema.copy_keys_from(schema, m_config.schema_subset_mode);
487✔
265
    }
487✔
266
    notify_schema_changed();
44,439✔
267
}
44,439✔
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
{
57,439✔
288
    if (version == m_schema_version && changes.empty())
57,439✔
289
        return false;
34,791✔
290

11,205✔
291
    switch (m_config.schema_mode) {
22,648✔
292
        case SchemaMode::Automatic:
14,038✔
293
            if (version < m_schema_version && m_schema_version != ObjectStore::NotVersioned)
14,038✔
294
                throw InvalidSchemaVersionException(m_schema_version, version, false);
2✔
295
            return true;
14,036✔
296

6,980✔
297
        case SchemaMode::Immutable:
6,986✔
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:
4,176✔
316
        case SchemaMode::AdditiveExplicit: {
8,398✔
317
            bool will_apply_index_changes = version > m_schema_version;
8,398✔
318
            if (ObjectStore::verify_valid_additive_changes(changes, will_apply_index_changes))
8,398✔
319
                return true;
8,270✔
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
329
            }
28✔
330
            return true;
126✔
331
    }
×
332
    REALM_COMPILER_HINT_UNREACHABLE();
×
333
}
×
334

335
Schema Realm::get_full_schema()
336
{
57,669✔
337
    if (!m_config.immutable())
57,669✔
338
        do_refresh();
57,603✔
339

28,428✔
340
    // If the user hasn't specified a schema previously then m_schema is always
28,428✔
341
    // the full schema if it's been read
28,428✔
342
    if (m_dynamic_schema && !m_schema.empty())
57,669✔
343
        return m_schema;
34,873✔
344

11,285✔
345
    // Otherwise we may have a subset of the file's schema, so we need to get
11,285✔
346
    // the complete thing to calculate what changes to make
11,285✔
347
    Schema actual_schema;
22,796✔
348
    uint64_t actual_version;
22,796✔
349
    uint64_t version = -1;
22,796✔
350
    bool got_cached = m_coordinator->get_cached_schema(actual_schema, actual_version, version);
22,796✔
351
    if (!got_cached || version != transaction().get_version_of_current_transaction().version)
22,796✔
352
        return ObjectStore::schema_from_group(read_group());
22,142✔
353
    return actual_schema;
654✔
354
}
654✔
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
{
20✔
363
    auto it = m_schema.find(object_type);
20✔
364
    if (it == m_schema.end()) {
20✔
365
        throw LogicError(ErrorCodes::NoSuchTable, util::format("No type '%1'", object_type));
×
366
    }
×
367
    return {shared_from_this(), &*it};
20✔
368
}
20✔
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
{
57,677✔
417
    uint64_t validation_mode = SchemaValidationMode::Basic;
57,677✔
418
#if REALM_ENABLE_SYNC
57,677✔
419
    if (auto sync_config = m_config.sync_config) {
57,677✔
420
        validation_mode |=
1,482✔
421
            sync_config->flx_sync_requested ? SchemaValidationMode::SyncFLX : SchemaValidationMode::SyncPBS;
1,243✔
422
    }
1,482✔
423
#endif
57,677✔
424
    if (m_config.schema_mode == SchemaMode::AdditiveExplicit) {
57,677✔
425
        validation_mode |= SchemaValidationMode::RejectEmbeddedOrphans;
9,042✔
426
    }
9,042✔
427

28,432✔
428
    schema.validate(static_cast<SchemaValidationMode>(validation_mode));
57,677✔
429

28,432✔
430
    bool was_in_read_transaction = is_in_read_transaction();
57,677✔
431
    Schema actual_schema = get_full_schema();
57,677✔
432

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

28,311✔
445
    std::vector<SchemaChange> required_changes = actual_schema.compare(schema, m_config.schema_mode);
57,435✔
446
    if (!schema_change_needs_write_transaction(schema, required_changes, version)) {
57,435✔
447
        if (!was_in_read_transaction)
34,863✔
448
            m_transaction = nullptr;
34,833✔
449
        set_schema(actual_schema, std::move(schema));
34,863✔
450
        return;
34,863✔
451
    }
34,863✔
452
    // Either the schema version has changed or we need to do non-migration changes
11,173✔
453

11,173✔
454
    // Cancel the write transaction if we exit this function before committing it
11,173✔
455
    auto cleanup = util::make_scope_exit([&]() noexcept {
22,572✔
456
        // When in_transaction is true, caller is responsible to cancel the transaction.
11,127✔
457
        if (!in_transaction && is_in_transaction())
22,480✔
458
            cancel_transaction();
110✔
459
        if (!was_in_read_transaction)
22,480✔
460
            m_transaction = nullptr;
22,126✔
461
    });
22,480✔
462

11,173✔
463
    if (!in_transaction) {
22,572✔
464
        transaction().promote_to_write();
22,454✔
465

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

11,173✔
481
    schema.copy_keys_from(actual_schema, m_config.schema_subset_mode);
22,571✔
482

11,172✔
483
    uint64_t old_schema_version = m_schema_version;
22,570✔
484
    bool additive = m_config.schema_mode == SchemaMode::AdditiveDiscovered ||
22,570✔
485
                    m_config.schema_mode == SchemaMode::AdditiveExplicit ||
22,483✔
486
                    m_config.schema_mode == SchemaMode::ReadOnly;
18,301✔
487
    if (migration_function && !additive) {
22,570✔
488
        auto wrapper = [&] {
2,375✔
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,270✔
500
        // migration function needs to see the target schema on the "new" Realm
2,270✔
501
        std::swap(m_schema, schema);
4,611✔
502
        std::swap(m_schema_version, version);
4,611✔
503
        m_in_migration = true;
4,611✔
504
        auto restore = util::make_scope_exit([&]() noexcept {
4,611✔
505
            std::swap(m_schema, schema);
4,611✔
506
            std::swap(m_schema_version, version);
4,611✔
507
            m_in_migration = false;
4,611✔
508
        });
4,611✔
509

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

11,172✔
521
    if (initialization_function && old_schema_version == ObjectStore::NotVersioned) {
22,570✔
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

11,172✔
533
    m_schema = std::move(schema);
22,570✔
534
    m_new_schema = ObjectStore::schema_from_group(read_group());
22,570✔
535
    m_schema_version = ObjectStore::get_schema_version(read_group());
22,570✔
536
    m_dynamic_schema = false;
22,570✔
537
    m_coordinator->clear_schema_cache_and_set_schema_version(version);
22,570✔
538

11,172✔
539
    if (!in_transaction) {
22,570✔
540
        m_coordinator->commit_write(*this);
22,342✔
541
        cache_new_schema();
22,342✔
542
    }
22,342✔
543

11,172✔
544
    notify_schema_changed();
22,570✔
545
}
22,570✔
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
{
114,948✔
554
    if (m_config.immutable())
114,948✔
555
        return;
56✔
556
    m_transaction->set_schema_change_notification_handler([&] {
114,892✔
557
        m_new_schema = ObjectStore::schema_from_group(read_group());
3,737✔
558
        m_schema_version = ObjectStore::get_schema_version(read_group());
3,737✔
559
        if (m_dynamic_schema) {
3,737✔
560
            m_schema = *m_new_schema;
34✔
561
        }
34✔
562
        else {
3,703✔
563
            m_schema.copy_keys_from(*m_new_schema, m_config.schema_subset_mode);
3,703✔
564
        }
3,703✔
565
        notify_schema_changed();
3,737✔
566
    });
3,737✔
567
}
114,892✔
568

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

103,577✔
575
    auto new_version = transaction().get_version_of_current_transaction().version;
211,272✔
576
    if (m_new_schema)
211,272✔
577
        m_coordinator->cache_schema(std::move(*m_new_schema), m_schema_version, new_version);
26,105✔
578
    else
185,167✔
579
        m_coordinator->advance_schema_cache(m_schema_transaction_version, new_version);
185,167✔
580
    m_schema_transaction_version = new_version;
211,272✔
581
    m_new_schema = util::none;
211,272✔
582
}
211,272✔
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
{
105,635✔
598
    if (m_binding_context) {
105,635✔
599
        m_binding_context->schema_did_change(m_schema);
28✔
600
    }
28✔
601
}
105,635✔
602

603
static void check_can_create_write_transaction(const Realm* realm)
604
{
130,096✔
605
    realm->verify_thread();
130,096✔
606
    realm->verify_open();
130,096✔
607
    if (realm->config().immutable() || realm->config().read_only()) {
130,096✔
608
        throw WrongTransactionState("Can't perform transactions on read-only Realms.");
6✔
609
    }
6✔
610
    if (realm->is_frozen()) {
130,090✔
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) {
130,088✔
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
}
130,088✔
619

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

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

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

640
bool Realm::verify_notifications_available(bool throw_on_error) const
641
{
44,333✔
642
    if (is_frozen()) {
44,333✔
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()) {
44,277✔
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) {
44,275✔
654
        if (m_transaction && m_transaction->get_commit_size() > 0)
10,368✔
655
            throw WrongTransactionState(
2✔
656
                "Cannot create asynchronous query after making changes in a write transaction.");
2✔
657
    }
33,907✔
658
    else {
33,907✔
659
        // Don't create implicit notifiers inside write transactions even if
16,863✔
660
        // we could as it wouldn't actually be used
16,863✔
661
        if (is_in_transaction())
33,907✔
662
            return false;
20,894✔
663
    }
23,379✔
664

11,641✔
665
    return true;
23,379✔
666
}
23,379✔
667

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

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

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

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

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

705
// Get the version of the latest snapshot
706
util::Optional<DB::version_type> Realm::latest_snapshot_version() const
707
{
66✔
708
    util::Optional<DB::version_type> ret;
66✔
709
    if (m_transaction) {
66✔
710
        ret = m_transaction->get_version_of_latest_snapshot();
64✔
711
    }
64✔
712
    return ret;
66✔
713
}
66✔
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
{
1,092✔
738
    verify_thread();
1,092✔
739
    return !m_async_commit_q.empty() || !m_async_write_q.empty() || (m_transaction && m_transaction->is_async());
1,092✔
740
}
1,092✔
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
{
181,039✔
751
    if (m_is_running_async_commit_completions) {
181,039✔
752
        return;
4✔
753
    }
4✔
754

88,821✔
755
    CountGuard sending_completions(m_is_running_async_commit_completions);
181,035✔
756
    auto error = m_transaction->get_commit_exception();
181,035✔
757
    auto completions = std::move(m_async_commit_q);
181,035✔
758
    m_async_commit_q.clear();
181,035✔
759
    for (auto& cb : completions) {
90,200✔
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
}
181,035✔
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
{
61,408✔
784
    if (!m_async_write_q.empty()) {
61,408✔
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
}
61,408✔
793

794
void Realm::end_current_write(bool check_pending)
795
{
3,303✔
796
    if (!m_transaction) {
3,303✔
797
        return;
2✔
798
    }
2✔
799
    m_transaction->async_complete_writes([self = shared_from_this(), this]() mutable {
3,301✔
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,301✔
806
        check_pending_write_requests();
2,845✔
807
    }
2,845✔
808
}
3,301✔
809

810
void Realm::run_writes()
811
{
501✔
812
    if (!m_transaction) {
501✔
813
        // Realm might have been closed
814
        return;
×
815
    }
×
816
    if (m_transaction->is_synchronizing()) {
501✔
817
        // Wait for the synchronization complete callback before we run more
818
        // writes as we can't add commits while in that state
819
        return;
×
820
    }
×
821
    if (is_in_transaction()) {
501✔
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);
499✔
829
    int run_limit = 20; // max number of commits without full sync to disk
499✔
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,709✔
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,660✔
836
            return;
114✔
837
        }
114✔
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
{
63,969✔
1010
    check_can_create_write_transaction(this);
63,969✔
1011

31,441✔
1012
    if (is_in_transaction()) {
63,969✔
1013
        throw WrongTransactionState("The Realm is already in a write transaction");
×
1014
    }
×
1015

31,441✔
1016
    // Any of the callbacks to user code below could drop the last remaining
31,441✔
1017
    // strong reference to `this`
31,441✔
1018
    auto retain_self = shared_from_this();
63,969✔
1019

31,441✔
1020
    // make sure we have a read transaction
31,441✔
1021
    read_group();
63,969✔
1022

31,441✔
1023
    do_begin_transaction();
63,969✔
1024
}
63,969✔
1025

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

32,586✔
1037
    if (m_transaction && !m_transaction->has_unsynced_commits()) {
66,507✔
1038
        call_completion_callbacks();
64,457✔
1039
    }
64,457✔
1040
}
66,507✔
1041

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

28,567✔
1046
    if (!is_in_transaction()) {
58,229✔
1047
        throw WrongTransactionState("Can't commit a non-existing write transaction");
×
1048
    }
×
1049

28,567✔
1050
    DB::VersionID prev_version = transaction().get_version_of_current_transaction();
58,229✔
1051
    if (auto audit = audit_context()) {
58,229✔
1052
        audit->prepare_for_write(prev_version);
91✔
1053
    }
91✔
1054

28,567✔
1055
    m_coordinator->commit_write(*this, /* commit_to_disk */ true);
58,229✔
1056
    cache_new_schema();
58,229✔
1057

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

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

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

1,416✔
1081
    transaction::cancel(transaction(), m_binding_context.get());
2,824✔
1082

1,416✔
1083
    if (m_transaction && !m_is_running_async_writes) {
2,824✔
1084
        if (m_async_write_q.empty()) {
2,816✔
1085
            end_current_write();
2,808✔
1086
        }
2,808✔
1087
        else {
8✔
1088
            check_pending_write_requests();
8✔
1089
        }
8✔
1090
    }
2,816✔
1091
}
2,824✔
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,515✔
1114
    if (!m_config.immutable() && m_transaction) {
4,515✔
1115
        m_transaction->prepare_for_close();
4,409✔
1116
        call_completion_callbacks();
4,409✔
1117
        transaction().close();
4,409✔
1118
    }
4,409✔
1119

2,242✔
1120
    m_transaction = nullptr;
4,515✔
1121
    m_async_write_q.clear();
4,515✔
1122
    m_async_commit_q.clear();
4,515✔
1123
}
4,515✔
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,963✔
1211
    if (is_closed() || is_in_transaction() || is_frozen()) {
24,963✔
1212
        return;
330✔
1213
    }
330✔
1214

10,806✔
1215
    verify_thread();
24,633✔
1216

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

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

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

2,960✔
1234
    if (m_binding_context) {
7,233✔
1235
        m_binding_context->changes_available();
78✔
1236

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

2,958✔
1243
    CountGuard sending_notifications(m_is_sending_notifications);
7,229✔
1244
    if (m_auto_refresh) {
7,234✔
1245
        if (m_transaction) {
7,231✔
1246
            try {
7,161✔
1247
                m_coordinator->advance_to_ready(*this);
7,161✔
1248
            }
7,161✔
1249
            catch (_impl::UnsupportedSchemaChange const&) {
2,920✔
1250
                translate_schema_error();
×
1251
            }
×
1252
            if (!is_closed())
7,161✔
1253
                cache_new_schema();
7,159✔
1254
        }
7,161✔
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,231✔
1264
}
7,229✔
1265

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

1272
bool Realm::do_refresh()
1273
{
125,855✔
1274
    // Frozen Realms never change.
62,305✔
1275
    if (is_frozen()) {
125,855✔
1276
        return false;
246✔
1277
    }
246✔
1278

62,182✔
1279
    if (m_config.immutable()) {
125,609✔
1280
        throw WrongTransactionState("Can't refresh an immutable Realm.");
4✔
1281
    }
4✔
1282

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

62,161✔
1293
    // Any of the callbacks to user code below could drop the last remaining
62,161✔
1294
    // strong reference to `this`
62,161✔
1295
    auto retain_self = shared_from_this();
125,567✔
1296

62,161✔
1297
    CountGuard sending_notifications(m_is_sending_notifications);
125,567✔
1298
    if (m_binding_context) {
125,567✔
1299
        m_binding_context->before_notify();
138✔
1300
    }
138✔
1301
    if (m_transaction) {
125,567✔
1302
        try {
34,593✔
1303
            bool version_changed = m_coordinator->advance_to_latest(*this);
34,593✔
1304
            if (is_closed())
34,593✔
1305
                return false;
6✔
1306
            cache_new_schema();
34,587✔
1307
            return version_changed;
34,587✔
1308
        }
34,587✔
1309
        catch (_impl::UnsupportedSchemaChange const&) {
4✔
1310
            translate_schema_error();
4✔
1311
        }
4✔
1312
    }
34,593✔
1313

62,161✔
1314
    // No current read transaction, so just create a new one
62,161✔
1315
    read_group();
108,313✔
1316
    m_coordinator->process_available_async(*this);
90,974✔
1317
    return true;
90,974✔
1318
}
125,567✔
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
{
13,013✔
1331
    if (m_config.immutable() || !m_config.automatic_change_notifications) {
13,013✔
1332
        return false;
12,957✔
1333
    }
12,957✔
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
{
391,983✔
1354
    bool result = bool(m_frozen_version);
391,983✔
1355
    REALM_ASSERT_DEBUG(!result || !m_transaction || m_transaction->is_frozen());
391,983✔
1356
    return result;
391,983✔
1357
}
391,983✔
1358

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

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

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

2,123✔
1384
    do_invalidate();
4,277✔
1385

2,123✔
1386
    m_binding_context = nullptr;
4,277✔
1387
    m_coordinator = nullptr;
4,277✔
1388
    m_scheduler = nullptr;
4,277✔
1389
    m_config = {};
4,277✔
1390
}
4,277✔
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
{
189,205✔
1421
    return m_coordinator ? m_coordinator->audit_context() : nullptr;
189,205✔
1422
}
189,205✔
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