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

realm / realm-core / github_pull_request_312964

19 Feb 2025 07:31PM UTC coverage: 90.814% (-0.3%) from 91.119%
github_pull_request_312964

Pull #8071

Evergreen

web-flow
Bump serialize-javascript and mocha

Bumps [serialize-javascript](https://github.com/yahoo/serialize-javascript) to 6.0.2 and updates ancestor dependency [mocha](https://github.com/mochajs/mocha). These dependencies need to be updated together.


Updates `serialize-javascript` from 6.0.0 to 6.0.2
- [Release notes](https://github.com/yahoo/serialize-javascript/releases)
- [Commits](https://github.com/yahoo/serialize-javascript/compare/v6.0.0...v6.0.2)

Updates `mocha` from 10.2.0 to 10.8.2
- [Release notes](https://github.com/mochajs/mocha/releases)
- [Changelog](https://github.com/mochajs/mocha/blob/main/CHANGELOG.md)
- [Commits](https://github.com/mochajs/mocha/compare/v10.2.0...v10.8.2)

---
updated-dependencies:
- dependency-name: serialize-javascript
  dependency-type: indirect
- dependency-name: mocha
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Pull Request #8071: Bump serialize-javascript and mocha

96552 of 179126 branches covered (53.9%)

212672 of 234185 relevant lines covered (90.81%)

3115802.0 hits per line

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

91.56
/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
#ifdef REALM_DEBUG
54
#include <iostream>
55
#endif
56

57
#include <thread>
58

59
using namespace realm;
60
using namespace realm::_impl;
61

62
namespace {
63
class CountGuard {
64
public:
65
    CountGuard(size_t& count)
66
        : m_count(count)
55,727✔
67
    {
55,727✔
68
        ++m_count;
55,727✔
69
    }
55,727✔
70
    ~CountGuard()
71
    {
55,726✔
72
        --m_count;
55,726✔
73
    }
55,726✔
74

75
private:
76
    size_t& m_count;
77
};
78
} // namespace
79

80
bool RealmConfig::needs_file_format_upgrade() const
81
{
6✔
82
    return DB::needs_file_format_upgrade(path, encryption_key);
6✔
83
}
6✔
84

85
Realm::Realm(Config config, util::Optional<VersionID> version, std::shared_ptr<_impl::RealmCoordinator> coordinator,
86
             Private)
87
    : m_config(std::move(config))
17,365✔
88
    , m_frozen_version(version)
17,365✔
89
    , m_scheduler(m_config.scheduler)
17,365✔
90
{
17,365✔
91
    if (version) {
17,365✔
92
        m_auto_refresh = false;
870✔
93
        REALM_ASSERT(*version != VersionID());
870✔
94
    }
870✔
95
    else if (!coordinator->get_cached_schema(m_schema, m_schema_version, m_schema_transaction_version)) {
16,495✔
96
        m_transaction = coordinator->begin_read();
9,735✔
97
        read_schema_from_group_if_needed();
9,735✔
98
        coordinator->cache_schema(m_schema, m_schema_version, m_schema_transaction_version);
9,735✔
99
        m_transaction = nullptr;
9,735✔
100
    }
9,735✔
101
    m_coordinator = std::move(coordinator);
17,365✔
102
}
17,365✔
103

104
Realm::~Realm()
105
{
17,365✔
106
    if (m_transaction) {
17,365✔
107
        // Wait for potential syncing to finish
108
        m_transaction->prepare_for_close();
14,113✔
109
        call_completion_callbacks();
14,113✔
110
    }
14,113✔
111

112
    if (m_coordinator) {
17,365✔
113
        m_coordinator->unregister_realm(this);
15,138✔
114
    }
15,138✔
115
}
17,365✔
116

117
Group& Realm::read_group()
118
{
1,629,292✔
119
    return transaction();
1,629,292✔
120
}
1,629,292✔
121

122
Transaction& Realm::transaction()
123
{
2,052,517✔
124
    verify_open();
2,052,517✔
125
    if (!m_transaction)
2,052,517✔
126
        begin_read(m_frozen_version.value_or(VersionID{}));
32,008✔
127
    return *m_transaction;
2,052,517✔
128
}
2,052,517✔
129

130
Transaction& Realm::transaction() const
131
{
231,220✔
132
    // one day we should change the way we use constness
133
    Realm* nc_realm = const_cast<Realm*>(this);
231,220✔
134
    return nc_realm->transaction();
231,220✔
135
}
231,220✔
136

137
std::shared_ptr<Transaction> Realm::transaction_ref()
138
{
36,746✔
139
    return m_transaction;
36,746✔
140
}
36,746✔
141

142
std::shared_ptr<Transaction> Realm::duplicate() const
143
{
72✔
144
    auto version = read_transaction_version(); // does the validity check first
72✔
145
    return m_coordinator->begin_read(version, is_frozen());
72✔
146
}
72✔
147

148
std::shared_ptr<DB>& Realm::Internal::get_db(Realm& realm)
149
{
3,682✔
150
    return realm.m_coordinator->m_db;
3,682✔
151
}
3,682✔
152

153
void Realm::Internal::begin_read(Realm& realm, VersionID version_id)
154
{
23✔
155
    realm.begin_read(version_id);
23✔
156
}
23✔
157

158
void Realm::begin_read(VersionID version_id)
159
{
32,031✔
160
    REALM_ASSERT(!m_transaction);
32,031✔
161
    m_transaction = m_coordinator->begin_read(version_id, bool(m_frozen_version));
32,031✔
162
    add_schema_change_handler();
32,031✔
163
    read_schema_from_group_if_needed();
32,031✔
164
}
32,031✔
165

166
SharedRealm Realm::get_shared_realm(Config config)
167
{
12,259✔
168
    auto coordinator = RealmCoordinator::get_coordinator(config.path);
12,259✔
169
    return coordinator->get_realm(std::move(config), util::none);
12,259✔
170
}
12,259✔
171

172
SharedRealm Realm::get_frozen_realm(Config config, VersionID version)
173
{
137✔
174
    auto coordinator = RealmCoordinator::get_coordinator(config.path);
137✔
175
    return coordinator->get_realm(std::move(config), version);
137✔
176
}
137✔
177

178
SharedRealm Realm::get_shared_realm(ThreadSafeReference ref, std::shared_ptr<util::Scheduler> scheduler)
179
{
101✔
180
    if (!scheduler)
101✔
181
        scheduler = util::Scheduler::make_default();
52✔
182
    SharedRealm realm = ref.resolve<std::shared_ptr<Realm>>(nullptr);
101✔
183
    REALM_ASSERT(realm);
101✔
184
    auto& config = realm->config();
101✔
185
    auto coordinator = RealmCoordinator::get_coordinator(config.path);
101✔
186
    if (auto realm = coordinator->get_cached_realm(config, scheduler))
101✔
187
        return realm;
1✔
188
    realm->m_scheduler = scheduler;
100✔
189
    coordinator->bind_to_context(*realm);
100✔
190
    return realm;
100✔
191
}
101✔
192

193
#if REALM_ENABLE_SYNC
194
std::shared_ptr<AsyncOpenTask> Realm::get_synchronized_realm(Config config)
195
{
94✔
196
    auto coordinator = RealmCoordinator::get_coordinator(config.path);
94✔
197
    return coordinator->get_synchronized_realm(std::move(config));
94✔
198
}
94✔
199

200
std::shared_ptr<SyncSession> Realm::sync_session() const
201
{
1,251✔
202
    return m_coordinator ? m_coordinator->sync_session() : nullptr;
1,251✔
203
}
1,251✔
204

205
sync::SubscriptionSet Realm::get_latest_subscription_set()
206
{
435✔
207
    if (!m_config.sync_config || !m_config.sync_config->flx_sync_requested) {
435✔
208
        throw IllegalOperation("Flexible sync is not enabled");
2✔
209
    }
2✔
210
    // If there is a subscription store, then return the active set
211
    auto flx_sub_store = m_coordinator->sync_session()->get_flx_subscription_store();
433✔
212
    REALM_ASSERT(flx_sub_store);
433✔
213
    return flx_sub_store->get_latest();
433✔
214
}
435✔
215

216
sync::SubscriptionSet Realm::get_active_subscription_set()
217
{
46✔
218
    if (!m_config.sync_config || !m_config.sync_config->flx_sync_requested) {
46✔
219
        throw IllegalOperation("Flexible sync is not enabled");
3✔
220
    }
3✔
221
    // If there is a subscription store, then return the active set
222
    auto flx_sub_store = m_coordinator->sync_session()->get_flx_subscription_store();
43✔
223
    REALM_ASSERT(flx_sub_store);
43✔
224
    return flx_sub_store->get_active();
43✔
225
}
46✔
226
#endif
227

228
void Realm::set_schema(Schema const& reference, Schema schema)
229
{
6,298✔
230
    m_dynamic_schema = false;
6,298✔
231
    schema.copy_keys_from(reference, m_config.schema_subset_mode);
6,298✔
232
    m_schema = std::move(schema);
6,298✔
233
    notify_schema_changed();
6,298✔
234
}
6,298✔
235

236
void Realm::read_schema_from_group_if_needed()
237
{
41,762✔
238
    if (m_config.immutable()) {
41,762✔
239
        REALM_ASSERT(m_transaction);
59✔
240
        if (m_schema.empty()) {
59✔
241
            m_schema_version = ObjectStore::get_schema_version(*m_transaction);
32✔
242
            m_schema = ObjectStore::schema_from_group(*m_transaction);
32✔
243
            m_schema_transaction_version = m_transaction->get_version_of_current_transaction().version;
32✔
244
        }
32✔
245
        return;
59✔
246
    }
59✔
247

248
    Group& group = read_group();
41,703✔
249
    auto current_version = transaction().get_version_of_current_transaction().version;
41,703✔
250
    if (m_schema_transaction_version == current_version)
41,703✔
251
        return;
31,358✔
252

253
    m_schema_transaction_version = current_version;
10,345✔
254
    m_schema_version = ObjectStore::get_schema_version(group);
10,345✔
255
    auto schema = ObjectStore::schema_from_group(group);
10,345✔
256

257
    if (m_coordinator)
10,345✔
258
        m_coordinator->cache_schema(schema, m_schema_version, m_schema_transaction_version);
641✔
259

260
    if (m_dynamic_schema) {
10,345✔
261
        if (m_schema == schema) {
10,048✔
262
            // The structure of the schema hasn't changed. Bring the table column indices up to date.
263
            m_schema.copy_keys_from(schema, SchemaSubsetMode::Strict);
9,547✔
264
        }
9,547✔
265
        else {
501✔
266
            // The structure of the schema has changed, so replace our copy of the schema.
267
            m_schema = std::move(schema);
501✔
268
        }
501✔
269
    }
10,048✔
270
    else {
297✔
271
        ObjectStore::verify_valid_external_changes(m_schema.compare(schema, m_config.schema_mode));
297✔
272
        m_schema.copy_keys_from(schema, m_config.schema_subset_mode);
297✔
273
    }
297✔
274
    notify_schema_changed();
10,345✔
275
}
10,345✔
276

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

286
    m_schema = ObjectStore::schema_from_group(read_group());
10✔
287
    m_schema_version = ObjectStore::get_schema_version(read_group());
10✔
288
    required_changes = m_schema.compare(schema, m_config.schema_mode);
10✔
289
    m_coordinator->clear_schema_cache_and_set_schema_version(m_schema_version);
10✔
290
    return false;
10✔
291
}
10✔
292

293
bool Realm::schema_change_needs_write_transaction(Schema& schema, std::vector<SchemaChange>& changes,
294
                                                  uint64_t version)
295
{
15,838✔
296
    if (version == m_schema_version && changes.empty())
15,838✔
297
        return false;
6,127✔
298

299
    switch (m_config.schema_mode) {
9,711✔
300
        case SchemaMode::Automatic:
5,185✔
301
            verify_schema_version_not_decreasing(version);
5,185✔
302
            return true;
5,185✔
303

304
        case SchemaMode::Immutable:
6✔
305
            if (version != m_schema_version)
6✔
306
                throw InvalidSchemaVersionException(m_schema_version, version, true);
1✔
307
            REALM_FALLTHROUGH;
6✔
308
        case SchemaMode::ReadOnly:
18✔
309
            ObjectStore::verify_compatible_for_immutable_and_readonly(changes);
18✔
310
            return m_schema_version == ObjectStore::NotVersioned;
18✔
311

312
        case SchemaMode::SoftResetFile:
16✔
313
            if (m_schema_version == ObjectStore::NotVersioned)
16✔
314
                return true;
9✔
315
            if (m_schema_version == version && !ObjectStore::needs_migration(changes))
7✔
316
                return true;
4✔
317
            REALM_FALLTHROUGH;
7✔
318
        case SchemaMode::HardResetFile:
10✔
319
            reset_file(schema, changes);
10✔
320
            return true;
10✔
321

322
        case SchemaMode::AdditiveDiscovered:
58✔
323
        case SchemaMode::AdditiveExplicit: {
4,420✔
324
            bool will_apply_index_changes = version > m_schema_version;
4,420✔
325
            if (ObjectStore::verify_valid_additive_changes(changes, will_apply_index_changes))
4,420✔
326
                return true;
4,321✔
327
            return version != m_schema_version;
99✔
328
        }
4,420✔
329

330
        case SchemaMode::Manual:
64✔
331
            verify_schema_version_not_decreasing(version);
64✔
332
            if (version == m_schema_version) {
64✔
333
                ObjectStore::verify_no_changes_required(changes);
14✔
334
                REALM_UNREACHABLE(); // changes is non-empty so above line always throws
335
            }
14✔
336
            return true;
64✔
337
    }
9,711✔
338
    REALM_COMPILER_HINT_UNREACHABLE();
×
339
}
9,711✔
340

341
// Schema version is not allowed to decrease for local and pbs realms.
342
void Realm::verify_schema_version_not_decreasing(uint64_t version)
343
{
5,249✔
344
#if REALM_ENABLE_SYNC
5,249✔
345
    if (m_config.sync_config && m_config.sync_config->flx_sync_requested)
5,249✔
346
        return;
×
347
#endif
5,249✔
348
    if (version < m_schema_version && m_schema_version != ObjectStore::NotVersioned)
5,249✔
349
        throw InvalidSchemaVersionException(m_schema_version, version, false);
2✔
350
}
5,249✔
351

352
Schema Realm::get_full_schema()
353
{
15,973✔
354
    if (!m_config.immutable())
15,973✔
355
        do_refresh();
15,940✔
356

357
    // If the user hasn't specified a schema previously then m_schema is always
358
    // the full schema if it's been read
359
    if (m_dynamic_schema && !m_schema.empty())
15,973✔
360
        return m_schema;
6,204✔
361

362
    // Otherwise we may have a subset of the file's schema, so we need to get
363
    // the complete thing to calculate what changes to make
364
    Schema actual_schema;
9,769✔
365
    uint64_t actual_version;
9,769✔
366
    uint64_t version = -1;
9,769✔
367
    bool got_cached = m_coordinator->get_cached_schema(actual_schema, actual_version, version);
9,769✔
368
    if (!got_cached || version != transaction().get_version_of_current_transaction().version)
9,769✔
369
        return ObjectStore::schema_from_group(read_group());
9,441✔
370
    return actual_schema;
328✔
371
}
9,769✔
372

373
bool Realm::is_empty()
374
{
2✔
375
    return ObjectStore::is_empty(read_group());
2✔
376
}
2✔
377

378
Class Realm::get_class(StringData object_type)
379
{
10✔
380
    auto it = m_schema.find(object_type);
10✔
381
    if (it == m_schema.end()) {
10✔
382
        throw LogicError(ErrorCodes::NoSuchTable, util::format("No type '%1'", object_type));
×
383
    }
×
384
    return {shared_from_this(), &*it};
10✔
385
}
10✔
386

387
std::vector<Class> Realm::get_classes()
388
{
×
389
    std::vector<Class> ret;
×
390
    ret.reserve(m_schema.size());
×
391
    auto r = shared_from_this();
×
392
    for (auto& os : m_schema) {
×
393
        ret.emplace_back(r, &os);
×
394
    }
×
395
    return ret;
×
396
}
×
397

398
void Realm::set_schema_subset(Schema schema)
399
{
6✔
400
    verify_thread();
6✔
401
    verify_open();
6✔
402
    REALM_ASSERT(m_dynamic_schema);
6✔
403
    REALM_ASSERT(m_schema_version != ObjectStore::NotVersioned);
6✔
404

405
    std::vector<SchemaChange> changes = m_schema.compare(schema, m_config.schema_mode);
6✔
406
    switch (m_config.schema_mode) {
6✔
407
        case SchemaMode::Automatic:
1✔
408
        case SchemaMode::SoftResetFile:
1✔
409
        case SchemaMode::HardResetFile:
1✔
410
            ObjectStore::verify_no_migration_required(changes);
1✔
411
            break;
1✔
412

413
        case SchemaMode::Immutable:
✔
414
        case SchemaMode::ReadOnly:
✔
415
            ObjectStore::verify_compatible_for_immutable_and_readonly(changes);
×
416
            break;
×
417

418
        case SchemaMode::AdditiveDiscovered:
✔
419
        case SchemaMode::AdditiveExplicit:
4✔
420
            ObjectStore::verify_valid_additive_changes(changes);
4✔
421
            break;
4✔
422

423
        case SchemaMode::Manual:
✔
424
            ObjectStore::verify_no_changes_required(changes);
×
425
            break;
×
426
    }
6✔
427

428
    set_schema(m_schema, std::move(schema));
5✔
429
}
5✔
430

431
void Realm::update_schema(Schema schema, uint64_t version, MigrationFunction migration_function,
432
                          DataInitializationFunction initialization_function, bool in_transaction)
433
{
15,977✔
434
    uint64_t validation_mode = SchemaValidationMode::Basic;
15,977✔
435
#if REALM_ENABLE_SYNC
15,977✔
436
    if (auto sync_config = m_config.sync_config) {
15,977✔
437
        validation_mode |=
866✔
438
            sync_config->flx_sync_requested ? SchemaValidationMode::SyncFLX : SchemaValidationMode::SyncPBS;
866✔
439
    }
866✔
440
#endif
15,977✔
441
    if (m_config.schema_mode == SchemaMode::AdditiveExplicit) {
15,977✔
442
        validation_mode |= SchemaValidationMode::RejectEmbeddedOrphans;
4,830✔
443
    }
4,830✔
444

445
    schema.validate(static_cast<SchemaValidationMode>(validation_mode));
15,977✔
446

447
    bool was_in_read_transaction = is_in_read_transaction();
15,977✔
448
    Schema actual_schema = get_full_schema();
15,977✔
449

450
    // Frozen Realms never modify the schema on disk and we just need to verify
451
    // that the requested schema is compatible with what actually exists on disk
452
    // at that frozen version. Tables are allowed to be missing as those can be
453
    // represented by empty Results, but tables which exist must have all of the
454
    // requested properties with the correct type.
455
    if (m_frozen_version) {
15,977✔
456
        ObjectStore::verify_compatible_for_immutable_and_readonly(
136✔
457
            actual_schema.compare(schema, m_config.schema_mode, true));
136✔
458
        set_schema(actual_schema, std::move(schema));
136✔
459
        return;
136✔
460
    }
136✔
461

462
    std::vector<SchemaChange> required_changes = actual_schema.compare(schema, m_config.schema_mode);
15,841✔
463
    if (!schema_change_needs_write_transaction(schema, required_changes, version)) {
15,841✔
464
        if (!was_in_read_transaction)
6,166✔
465
            m_transaction = nullptr;
6,151✔
466
        set_schema(actual_schema, std::move(schema));
6,166✔
467
        return;
6,166✔
468
    }
6,166✔
469
    // Either the schema version has changed or we need to do non-migration changes
470

471
    // Cancel the write transaction if we exit this function before committing it
472
    auto cleanup = util::make_scope_exit([&]() noexcept {
9,675✔
473
        // When in_transaction is true, caller is responsible to cancel the transaction.
474
        if (!in_transaction && is_in_transaction())
9,626✔
475
            cancel_transaction();
55✔
476
        if (!was_in_read_transaction)
9,626✔
477
            m_transaction = nullptr;
9,451✔
478
    });
9,626✔
479

480
    if (!in_transaction) {
9,675✔
481
        transaction().promote_to_write();
9,613✔
482

483
        // Beginning the write transaction may have advanced the version and left
484
        // us with nothing to do if someone else initialized the schema on disk
485
        if (m_new_schema) {
9,613✔
486
            actual_schema = *m_new_schema;
2✔
487
            required_changes = actual_schema.compare(schema, m_config.schema_mode);
2✔
488
            if (!schema_change_needs_write_transaction(schema, required_changes, version)) {
2✔
489
                cancel_transaction();
1✔
490
                cache_new_schema();
1✔
491
                set_schema(actual_schema, std::move(schema));
1✔
492
                return;
1✔
493
            }
1✔
494
        }
2✔
495
        cache_new_schema();
9,612✔
496
    }
9,612✔
497

498
    schema.copy_keys_from(actual_schema, m_config.schema_subset_mode);
9,674✔
499

500
    uint64_t old_schema_version = m_schema_version;
9,674✔
501
    bool additive = m_config.schema_mode == SchemaMode::AdditiveDiscovered ||
9,674✔
502
                    m_config.schema_mode == SchemaMode::AdditiveExplicit ||
9,674✔
503
                    m_config.schema_mode == SchemaMode::ReadOnly;
9,674✔
504
    if (migration_function && !additive) {
9,674✔
505
        auto wrapper = [&] {
424✔
506
            auto config = m_config;
106✔
507
            config.schema_mode = SchemaMode::ReadOnly;
106✔
508
            config.schema = util::none;
106✔
509
            // Don't go through the normal codepath for opening a Realm because
510
            // we're using a mismatched config
511
            auto old_realm = std::make_shared<Realm>(std::move(config), none, m_coordinator, Private());
106✔
512
            // block autorefresh for the old realm
513
            old_realm->m_auto_refresh = false;
106✔
514
            migration_function(old_realm, shared_from_this(), m_schema);
106✔
515
        };
106✔
516

517
        // migration function needs to see the target schema on the "new" Realm
518
        std::swap(m_schema, schema);
424✔
519
        std::swap(m_schema_version, version);
424✔
520
        m_in_migration = true;
424✔
521
        auto restore = util::make_scope_exit([&]() noexcept {
424✔
522
            std::swap(m_schema, schema);
424✔
523
            std::swap(m_schema_version, version);
424✔
524
            m_in_migration = false;
424✔
525
        });
424✔
526

527
        ObjectStore::apply_schema_changes(transaction(), version, m_schema, m_schema_version, m_config.schema_mode,
424✔
528
                                          required_changes, m_config.automatically_handle_backlinks_in_migrations,
424✔
529
                                          wrapper);
424✔
530
    }
424✔
531
    else {
9,250✔
532
        ObjectStore::apply_schema_changes(transaction(), m_schema_version, schema, version, m_config.schema_mode,
9,250✔
533
                                          required_changes, m_config.automatically_handle_backlinks_in_migrations);
9,250✔
534
        REALM_ASSERT_DEBUG(additive ||
9,250✔
535
                           (required_changes = ObjectStore::schema_from_group(read_group()).compare(schema)).empty());
9,250✔
536
    }
9,250✔
537

538
    if (initialization_function && old_schema_version == ObjectStore::NotVersioned) {
9,674✔
539
        // Initialization function needs to see the latest schema
540
        uint64_t temp_version = ObjectStore::get_schema_version(read_group());
15✔
541
        std::swap(m_schema, schema);
15✔
542
        std::swap(m_schema_version, temp_version);
15✔
543
        auto restore = util::make_scope_exit([&]() noexcept {
15✔
544
            std::swap(m_schema, schema);
15✔
545
            std::swap(m_schema_version, temp_version);
15✔
546
        });
15✔
547
        initialization_function(shared_from_this());
15✔
548
    }
15✔
549

550
    m_schema = std::move(schema);
9,674✔
551
    m_new_schema = ObjectStore::schema_from_group(read_group());
9,674✔
552
    m_schema_version = ObjectStore::get_schema_version(read_group());
9,674✔
553
    m_dynamic_schema = false;
9,674✔
554
    m_coordinator->clear_schema_cache_and_set_schema_version(version);
9,674✔
555

556
    if (!in_transaction) {
9,674✔
557
        m_coordinator->commit_write(*this);
9,557✔
558
        cache_new_schema();
9,557✔
559
    }
9,557✔
560

561
    notify_schema_changed();
9,674✔
562
}
9,674✔
563

564
void Realm::rename_property(Schema schema, StringData object_type, StringData old_name, StringData new_name)
565
{
2✔
566
    ObjectStore::rename_property(read_group(), schema, object_type, old_name, new_name);
2✔
567
}
2✔
568

569
void Realm::add_schema_change_handler()
570
{
32,027✔
571
    if (m_config.immutable())
32,027✔
572
        return;
28✔
573
    m_transaction->set_schema_change_notification_handler([&] {
31,999✔
574
        m_new_schema = ObjectStore::schema_from_group(read_group());
1,988✔
575
        m_schema_version = ObjectStore::get_schema_version(read_group());
1,988✔
576
        if (m_dynamic_schema) {
1,988✔
577
            m_schema = *m_new_schema;
12✔
578
        }
12✔
579
        else {
1,976✔
580
            m_schema.copy_keys_from(*m_new_schema, m_config.schema_subset_mode);
1,976✔
581
        }
1,976✔
582
        notify_schema_changed();
1,988✔
583
    });
1,988✔
584
}
31,999✔
585

586
void Realm::cache_new_schema()
587
{
71,015✔
588
    if (is_closed()) {
71,015✔
589
        return;
×
590
    }
×
591

592
    auto new_version = transaction().get_version_of_current_transaction().version;
71,015✔
593
    if (m_new_schema)
71,015✔
594
        m_coordinator->cache_schema(std::move(*m_new_schema), m_schema_version, new_version);
11,558✔
595
    else
59,457✔
596
        m_coordinator->advance_schema_cache(m_schema_transaction_version, new_version);
59,457✔
597
    m_schema_transaction_version = new_version;
71,015✔
598
    m_new_schema = util::none;
71,015✔
599
}
71,015✔
600

601
void Realm::translate_schema_error()
602
{
2✔
603
    // Read the new (incompatible) schema without changing our read transaction
604
    auto new_schema = ObjectStore::schema_from_group(*m_coordinator->begin_read());
2✔
605

606
    // Should always throw
607
    ObjectStore::verify_valid_external_changes(m_schema.compare(new_schema, m_config.schema_mode, true));
2✔
608

609
    // Something strange happened so just rethrow the old exception
610
    throw;
2✔
611
}
2✔
612

613
void Realm::notify_schema_changed()
614
{
28,198✔
615
    if (m_binding_context) {
28,198✔
616
        m_binding_context->schema_did_change(m_schema);
43✔
617
    }
43✔
618
}
28,198✔
619

620
static void check_can_create_write_transaction(const Realm* realm)
621
{
47,699✔
622
    realm->verify_thread();
47,699✔
623
    realm->verify_open();
47,699✔
624
    if (realm->config().immutable() || realm->config().read_only()) {
47,699✔
625
        throw WrongTransactionState("Can't perform transactions on read-only Realms.");
3✔
626
    }
3✔
627
    if (realm->is_frozen()) {
47,696✔
628
        throw WrongTransactionState("Can't perform transactions on a frozen Realm");
1✔
629
    }
1✔
630
    if (!realm->is_closed() && realm->get_number_of_versions() > realm->config().max_number_of_active_versions) {
47,695✔
631
        throw WrongTransactionState(
×
632
            util::format("Number of active versions (%1) in the Realm exceeded the limit of %2",
×
633
                         realm->get_number_of_versions(), realm->config().max_number_of_active_versions));
×
634
    }
×
635
}
47,695✔
636

637
void Realm::verify_thread() const
638
{
302,258✔
639
    if (m_scheduler && !m_scheduler->is_on_thread())
302,258✔
640
        throw LogicError(ErrorCodes::WrongThread, "Realm accessed from incorrect thread.");
4✔
641
}
302,258✔
642

643
void Realm::verify_in_write() const
644
{
8,181✔
645
    if (!is_in_transaction()) {
8,181✔
646
        throw WrongTransactionState("Cannot modify managed objects outside of a write transaction.");
2✔
647
    }
2✔
648
}
8,181✔
649

650
void Realm::verify_open() const
651
{
2,151,268✔
652
    if (is_closed()) {
2,151,268✔
653
        throw LogicError(ErrorCodes::ClosedRealm, "Cannot access realm that has been closed.");
22✔
654
    }
22✔
655
}
2,151,268✔
656

657
bool Realm::verify_notifications_available(bool throw_on_error) const
658
{
15,002✔
659
    if (is_frozen()) {
15,002✔
660
        if (throw_on_error)
28✔
661
            throw WrongTransactionState(
6✔
662
                "Notifications are not available on frozen collections since they do not change.");
6✔
663
        return false;
22✔
664
    }
28✔
665
    if (config().immutable()) {
14,974✔
666
        if (throw_on_error)
1✔
667
            throw WrongTransactionState("Cannot create asynchronous query for immutable Realms");
×
668
        return false;
1✔
669
    }
1✔
670
    if (throw_on_error) {
14,973✔
671
        if (m_transaction && m_transaction->get_commit_size() > 0)
5,280✔
672
            throw WrongTransactionState(
1✔
673
                "Cannot create asynchronous query after making changes in a write transaction.");
1✔
674
    }
5,280✔
675
    else {
9,693✔
676
        // Don't create implicit notifiers inside write transactions even if
677
        // we could as it wouldn't actually be used
678
        if (is_in_transaction())
9,693✔
679
            return false;
8,053✔
680
    }
9,693✔
681

682
    return true;
6,919✔
683
}
14,973✔
684

685
VersionID Realm::read_transaction_version() const
686
{
3,163✔
687
    verify_thread();
3,163✔
688
    verify_open();
3,163✔
689
    REALM_ASSERT(m_transaction);
3,163✔
690
    return m_transaction->get_version_of_current_transaction();
3,163✔
691
}
3,163✔
692

693
uint_fast64_t Realm::get_number_of_versions() const
694
{
47,692✔
695
    verify_open();
47,692✔
696
    return m_coordinator->get_number_of_versions();
47,692✔
697
}
47,692✔
698

699
bool Realm::is_in_transaction() const noexcept
700
{
253,099✔
701
    return !m_config.immutable() && !is_closed() && m_transaction &&
253,099✔
702
           transaction().get_transact_stage() == DB::transact_Writing;
253,099✔
703
}
253,099✔
704

705
bool Realm::is_in_async_transaction() const noexcept
706
{
2,957✔
707
    return !m_config.immutable() && !is_closed() && m_transaction && m_transaction->is_async();
2,957✔
708
}
2,957✔
709

710
util::Optional<VersionID> Realm::current_transaction_version() const
711
{
24,541✔
712
    util::Optional<VersionID> ret;
24,541✔
713
    if (m_transaction) {
24,541✔
714
        ret = m_transaction->get_version_of_current_transaction();
24,363✔
715
    }
24,363✔
716
    else if (m_frozen_version) {
178✔
717
        ret = m_frozen_version;
×
718
    }
×
719
    return ret;
24,541✔
720
}
24,541✔
721

722
// Get the version of the latest snapshot
723
util::Optional<DB::version_type> Realm::latest_snapshot_version() const
724
{
83✔
725
    util::Optional<DB::version_type> ret;
83✔
726
    if (m_transaction) {
83✔
727
        ret = m_transaction->get_version_of_latest_snapshot();
82✔
728
    }
82✔
729
    return ret;
83✔
730
}
83✔
731

732
void Realm::enable_wait_for_change()
733
{
1✔
734
    verify_open();
1✔
735
    m_coordinator->enable_wait_for_change();
1✔
736
}
1✔
737

738
bool Realm::wait_for_change()
739
{
3✔
740
    verify_open();
3✔
741
    if (m_frozen_version || m_config.schema_mode == SchemaMode::Immutable) {
3✔
742
        return false;
2✔
743
    }
2✔
744
    return m_transaction && m_coordinator->wait_for_change(m_transaction);
1!
745
}
3✔
746

747
void Realm::wait_for_change_release()
748
{
1✔
749
    verify_open();
1✔
750
    m_coordinator->wait_for_change_release();
1✔
751
}
1✔
752

753
bool Realm::has_pending_async_work() const
754
{
3,088✔
755
    verify_thread();
3,088✔
756
    return !m_async_commit_q.empty() || !m_async_write_q.empty() || (m_transaction && m_transaction->is_async());
3,088✔
757
}
3,088✔
758

759
void Realm::run_writes_on_proper_thread()
760
{
3✔
761
    m_scheduler->invoke([self = shared_from_this()] {
3✔
762
        self->run_writes();
3✔
763
    });
3✔
764
}
3✔
765

766
void Realm::call_completion_callbacks()
767
{
60,594✔
768
    if (m_is_running_async_commit_completions || m_async_commit_q.empty()) {
60,594✔
769
        return;
60,485✔
770
    }
60,485✔
771

772
    CountGuard sending_completions(m_is_running_async_commit_completions);
109✔
773
    auto error = m_transaction->get_commit_exception();
109✔
774
    auto completions = std::move(m_async_commit_q);
109✔
775
    m_async_commit_q.clear();
109✔
776
    for (auto& cb : completions) {
1,132✔
777
        if (!cb.when_completed)
1,132✔
778
            continue;
8✔
779
        if (m_async_exception_handler) {
1,124✔
780
            try {
1✔
781
                cb.when_completed(error);
1✔
782
            }
1✔
783
            catch (...) {
1✔
784
                m_async_exception_handler(cb.handle, std::current_exception());
1✔
785
            }
1✔
786
        }
1✔
787
        else {
1,123✔
788
            cb.when_completed(error);
1,123✔
789
        }
1,123✔
790
    }
1,124✔
791
}
109✔
792

793
void Realm::run_async_completions()
794
{
95✔
795
    call_completion_callbacks();
95✔
796
    check_pending_write_requests();
95✔
797
}
95✔
798

799
void Realm::check_pending_write_requests()
800
{
22,069✔
801
    if (!m_async_write_q.empty()) {
22,069✔
802
        if (m_transaction->is_async()) {
85✔
803
            run_writes_on_proper_thread();
3✔
804
        }
3✔
805
        else {
82✔
806
            m_coordinator->async_request_write_mutex(*this);
82✔
807
        }
82✔
808
    }
85✔
809
}
22,069✔
810

811
void Realm::end_current_write(bool check_pending)
812
{
1,582✔
813
    if (!m_transaction) {
1,582✔
814
        return;
1✔
815
    }
1✔
816
    m_transaction->async_complete_writes([self = shared_from_this(), this]() mutable {
1,581✔
817
        m_scheduler->invoke([self = std::move(self), this]() mutable {
95✔
818
            run_async_completions();
95✔
819
            self.reset();
95✔
820
        });
95✔
821
    });
95✔
822
    if (check_pending && m_async_commit_q.empty()) {
1,581✔
823
        check_pending_write_requests();
1,476✔
824
    }
1,476✔
825
}
1,581✔
826

827
void Realm::run_writes()
828
{
126✔
829
    if (!m_transaction) {
126✔
830
        // Realm might have been closed
831
        return;
×
832
    }
×
833
    if (m_transaction->is_synchronizing()) {
126✔
834
        // Wait for the synchronization complete callback before we run more
835
        // writes as we can't add commits while in that state
836
        return;
×
837
    }
×
838
    if (is_in_transaction()) {
126✔
839
        // This is scheduled asynchronously after acquiring the write lock, so
840
        // in that time a synchronous transaction may have been started. If so,
841
        // we'll be re-invoked when that transaction ends.
842
        return;
1✔
843
    }
1✔
844

845
    CountGuard running_writes(m_is_running_async_writes);
125✔
846
    int run_limit = 20; // max number of commits without full sync to disk
125✔
847
    // this is tricky
848
    //  - each pending call may itself add other async writes
849
    //  - the 'run' will terminate as soon as a commit without grouping is requested
850
    while (!m_async_write_q.empty() && m_transaction) {
1,168✔
851
        // We might have made a sync commit and thereby given up the write lock
852
        if (!m_transaction->holds_write_mutex()) {
1,150✔
853
            return;
1✔
854
        }
1✔
855

856
        do_begin_transaction();
1,149✔
857

858
        auto write_desc = std::move(m_async_write_q.front());
1,149✔
859
        m_async_write_q.pop_front();
1,149✔
860

861
        // prevent any calls to commit/cancel during a simple notification
862
        m_notify_only = write_desc.notify_only;
1,149✔
863
        m_async_commit_barrier_requested = false;
1,149✔
864
        auto prev_version = m_transaction->get_version();
1,149✔
865
        try {
1,149✔
866
            write_desc.writer();
1,149✔
867
        }
1,149✔
868
        catch (const std::exception&) {
1,149✔
869
            if (m_transaction) {
3✔
870
                transaction::cancel(*m_transaction, m_binding_context.get());
2✔
871
            }
2✔
872
            m_notify_only = false;
3✔
873

874
            if (m_async_exception_handler) {
3✔
875
                m_async_exception_handler(write_desc.handle, std::current_exception());
1✔
876
                continue;
1✔
877
            }
1✔
878
            end_current_write();
2✔
879
            throw;
2✔
880
        }
3✔
881

882
        // if we've merely delivered a notification, the full transaction will follow later
883
        // and terminate with a call to async commit or async cancel
884
        if (m_notify_only) {
1,146✔
885
            m_notify_only = false;
6✔
886
            return;
6✔
887
        }
6✔
888

889
        // Realm may have been closed in the write function
890
        if (!m_transaction) {
1,140✔
891
            return;
10✔
892
        }
10✔
893

894
        auto new_version = m_transaction->get_version();
1,130✔
895
        // if we've run the full transaction, there is follow up work to do:
896
        if (new_version > prev_version) {
1,130✔
897
            // A commit was done during callback
898
            --run_limit;
1,125✔
899
            if (run_limit <= 0)
1,125✔
900
                break;
51✔
901
        }
1,125✔
902
        else {
5✔
903
            if (m_transaction->get_transact_stage() == DB::transact_Writing) {
5✔
904
                // Still in writing stage - we make a rollback
905
                transaction::cancel(transaction(), m_binding_context.get());
4✔
906
            }
4✔
907
        }
5✔
908
        if (m_async_commit_barrier_requested)
1,079✔
909
            break;
37✔
910
    }
1,079✔
911

912
    end_current_write();
106✔
913
}
106✔
914

915
auto Realm::async_begin_transaction(util::UniqueFunction<void()>&& the_write_block, bool notify_only) -> AsyncHandle
916
{
1,156✔
917
    check_can_create_write_transaction(this);
1,156✔
918
    if (m_is_running_async_commit_completions) {
1,156✔
919
        throw WrongTransactionState("Can't begin a write transaction from inside a commit completion callback.");
×
920
    }
×
921
    if (!m_scheduler->can_invoke()) {
1,156✔
922
        throw WrongTransactionState(
×
923
            "Cannot schedule async transaction. Make sure you are running from inside a run loop.");
×
924
    }
×
925
    REALM_ASSERT(the_write_block);
1,156✔
926

927
    // make sure we have a (at least a) read transaction
928
    transaction();
1,156✔
929
    auto handle = m_async_commit_handle++;
1,156✔
930
    m_async_write_q.push_back({std::move(the_write_block), notify_only, handle});
1,156✔
931

932
    if (!m_is_running_async_writes && !m_transaction->is_async() &&
1,156✔
933
        m_transaction->get_transact_stage() != DB::transact_Writing) {
1,156✔
934
        m_coordinator->async_request_write_mutex(*this);
49✔
935
    }
49✔
936
    return handle;
1,156✔
937
}
1,156✔
938

939
auto Realm::async_commit_transaction(util::UniqueFunction<void(std::exception_ptr)>&& completion, bool allow_grouping)
940
    -> AsyncHandle
941
{
1,134✔
942
    check_can_create_write_transaction(this);
1,134✔
943
    if (m_is_running_async_commit_completions) {
1,134✔
944
        throw WrongTransactionState("Can't commit a write transaction from inside a commit completion callback.");
×
945
    }
×
946
    if (!is_in_transaction()) {
1,134✔
947
        throw WrongTransactionState("Can't commit a non-existing write transaction");
×
948
    }
×
949

950
    m_transaction->promote_to_async();
1,134✔
951
    REALM_ASSERT(m_transaction->holds_write_mutex());
1,134✔
952
    REALM_ASSERT(!m_notify_only);
1,134✔
953
    // auditing is not supported
954
    REALM_ASSERT(!audit_context());
1,134✔
955
    // grab a version lock on current version, push it along with the done block
956
    // do in-buffer-cache commit_transaction();
957
    auto handle = m_async_commit_handle++;
1,134✔
958
    m_async_commit_q.push_back({std::move(completion), handle});
1,134✔
959
    try {
1,134✔
960
        m_coordinator->commit_write(*this, /* commit_to_disk: */ false);
1,134✔
961
    }
1,134✔
962
    catch (...) {
1,134✔
963
        // If the exception happened before the commit, we need to roll back the
964
        // transaction and remove the completion handler from the queue
965
        if (is_in_transaction()) {
2✔
966
            // Exception happened before the commit, so roll back the transaction
967
            // and remove the completion handler from the queue
968
            cancel_transaction();
1✔
969
            auto it = std::find_if(m_async_commit_q.begin(), m_async_commit_q.end(), [=](auto& e) {
1✔
970
                return e.handle == handle;
1✔
971
            });
1✔
972
            if (it != m_async_commit_q.end()) {
1✔
973
                m_async_commit_q.erase(it);
1✔
974
            }
1✔
975
        }
1✔
976
        else if (m_transaction) {
1✔
977
            end_current_write(false);
1✔
978
        }
1✔
979
        throw;
2✔
980
    }
2✔
981

982
    if (m_is_running_async_writes) {
1,131✔
983
        // we're called from with the callback loop and it will take care of releasing lock
984
        // if applicable, and of triggering followup runs of callbacks
985
        if (!allow_grouping) {
1,122✔
986
            m_async_commit_barrier_requested = true;
41✔
987
        }
41✔
988
    }
1,122✔
989
    else {
9✔
990
        // we're called from outside the callback loop so we have to take care of
991
        // releasing any lock and of keeping callbacks coming.
992
        if (allow_grouping) {
9✔
993
            run_writes();
×
994
        }
×
995
        else {
9✔
996
            end_current_write(false);
9✔
997
        }
9✔
998
    }
9✔
999
    return handle;
1,131✔
1000
}
1,134✔
1001

1002
bool Realm::async_cancel_transaction(AsyncHandle handle)
1003
{
3✔
1004
    verify_thread();
3✔
1005
    verify_open();
3✔
1006
    auto compare = [handle](auto& elem) {
3✔
1007
        return elem.handle == handle;
2✔
1008
    };
2✔
1009

1010
    auto it1 = std::find_if(m_async_write_q.begin(), m_async_write_q.end(), compare);
3✔
1011
    if (it1 != m_async_write_q.end()) {
3✔
1012
        m_async_write_q.erase(it1);
1✔
1013
        return true;
1✔
1014
    }
1✔
1015
    auto it2 = std::find_if(m_async_commit_q.begin(), m_async_commit_q.end(), compare);
2✔
1016
    if (it2 != m_async_commit_q.end()) {
2✔
1017
        // Just delete the callback. It is important that we know
1018
        // that there are still commits pending.
1019
        it2->when_completed = nullptr;
1✔
1020
        return true;
1✔
1021
    }
1✔
1022
    return false;
1✔
1023
}
2✔
1024

1025
void Realm::begin_transaction()
1026
{
23,441✔
1027
    check_can_create_write_transaction(this);
23,441✔
1028

1029
    if (is_in_transaction()) {
23,441✔
1030
        throw WrongTransactionState("The Realm is already in a write transaction");
×
1031
    }
×
1032

1033
    // Any of the callbacks to user code below could drop the last remaining
1034
    // strong reference to `this`
1035
    auto retain_self = shared_from_this();
23,441✔
1036

1037
    // make sure we have a read transaction
1038
    read_group();
23,441✔
1039

1040
    do_begin_transaction();
23,441✔
1041
}
23,441✔
1042

1043
void Realm::do_begin_transaction()
1044
{
24,586✔
1045
    CountGuard sending_notifications(m_is_sending_notifications);
24,586✔
1046
    try {
24,586✔
1047
        m_coordinator->promote_to_write(*this);
24,586✔
1048
    }
24,586✔
1049
    catch (_impl::UnsupportedSchemaChange const&) {
24,586✔
1050
        translate_schema_error();
×
1051
    }
×
1052
    cache_new_schema();
24,586✔
1053

1054
    if (m_transaction && !m_transaction->has_unsynced_commits()) {
24,586✔
1055
        call_completion_callbacks();
23,561✔
1056
    }
23,561✔
1057
}
24,586✔
1058

1059
void Realm::commit_transaction()
1060
{
20,496✔
1061
    check_can_create_write_transaction(this);
20,496✔
1062

1063
    if (!is_in_transaction()) {
20,496✔
1064
        throw WrongTransactionState("Can't commit a non-existing write transaction");
×
1065
    }
×
1066

1067
    DB::VersionID prev_version = transaction().get_version_of_current_transaction();
20,496✔
1068
    if (auto audit = audit_context()) {
20,496✔
1069
        audit->prepare_for_write(prev_version);
×
1070
    }
×
1071

1072
    m_coordinator->commit_write(*this, /* commit_to_disk */ true);
20,496✔
1073
    cache_new_schema();
20,496✔
1074

1075
    // Realm might have been closed
1076
    if (m_transaction) {
20,496✔
1077
        // Any previous async commits got flushed along with the sync commit
1078
        call_completion_callbacks();
20,495✔
1079
        // If we have pending async writes we need to rerequest the write mutex
1080
        check_pending_write_requests();
20,495✔
1081
    }
20,495✔
1082
    if (auto audit = audit_context()) {
20,496✔
1083
        audit->record_write(prev_version, transaction().get_version_of_current_transaction());
×
1084
    }
×
1085
}
20,496✔
1086

1087
void Realm::cancel_transaction()
1088
{
1,472✔
1089
    check_can_create_write_transaction(this);
1,472✔
1090

1091
    if (m_is_running_async_commit_completions) {
1,472✔
1092
        throw WrongTransactionState("Can't cancel a write transaction from inside a commit completion callback.");
×
1093
    }
×
1094
    if (!is_in_transaction()) {
1,472✔
1095
        throw WrongTransactionState("Can't cancel a non-existing write transaction");
×
1096
    }
×
1097

1098
    transaction::cancel(transaction(), m_binding_context.get());
1,472✔
1099

1100
    if (m_transaction && !m_is_running_async_writes) {
1,472✔
1101
        if (m_async_write_q.empty()) {
1,468✔
1102
            end_current_write();
1,464✔
1103
        }
1,464✔
1104
        else {
4✔
1105
            check_pending_write_requests();
4✔
1106
        }
4✔
1107
    }
1,468✔
1108
}
1,472✔
1109

1110
void Realm::invalidate()
1111
{
160✔
1112
    verify_thread();
160✔
1113
    verify_open();
160✔
1114

1115
    if (m_is_sending_notifications) {
160✔
1116
        // This was originally because closing the Realm during notification
1117
        // sending would break things, but we now support that. However, it's a
1118
        // breaking change so we keep the old behavior for now.
1119
        return;
3✔
1120
    }
3✔
1121

1122
    if (is_in_transaction()) {
157✔
1123
        cancel_transaction();
86✔
1124
    }
86✔
1125

1126
    do_invalidate();
157✔
1127
}
157✔
1128

1129
void Realm::do_invalidate()
1130
{
2,383✔
1131
    if (!m_config.immutable() && m_transaction) {
2,383✔
1132
        m_transaction->prepare_for_close();
2,330✔
1133
        call_completion_callbacks();
2,330✔
1134
        transaction().close();
2,330✔
1135
    }
2,330✔
1136

1137
    m_transaction = nullptr;
2,383✔
1138
    m_async_write_q.clear();
2,383✔
1139
    m_async_commit_q.clear();
2,383✔
1140
}
2,383✔
1141

1142
bool Realm::compact()
1143
{
4✔
1144
    verify_thread();
4✔
1145
    verify_open();
4✔
1146

1147
    if (m_config.immutable() || m_config.read_only()) {
4✔
1148
        throw WrongTransactionState("Can't compact a read-only Realm");
2✔
1149
    }
2✔
1150
    if (is_in_transaction()) {
2✔
1151
        throw WrongTransactionState("Can't compact a Realm within a write transaction");
×
1152
    }
×
1153

1154
    verify_open();
2✔
1155
    m_transaction = nullptr;
2✔
1156
    return m_coordinator->compact();
2✔
1157
}
2✔
1158

1159
void Realm::convert(const Config& config, bool merge_into_existing)
1160
{
33✔
1161
    verify_thread();
33✔
1162
    verify_open();
33✔
1163

1164
#if REALM_ENABLE_SYNC
33✔
1165
    auto src_is_flx_sync = m_config.sync_config && m_config.sync_config->flx_sync_requested;
33✔
1166
    auto dst_is_flx_sync = config.sync_config && config.sync_config->flx_sync_requested;
33✔
1167
    auto dst_is_pbs_sync = config.sync_config && !config.sync_config->flx_sync_requested;
33✔
1168

1169
    if (dst_is_flx_sync && !src_is_flx_sync) {
33✔
1170
        throw IllegalOperation(
2✔
1171
            "Realm cannot be converted to a flexible sync realm unless flexible sync is already enabled");
2✔
1172
    }
2✔
1173
    if (dst_is_pbs_sync && src_is_flx_sync) {
31✔
1174
        throw IllegalOperation(
1✔
1175
            "Realm cannot be converted from a flexible sync realm to a partition based sync realm");
1✔
1176
    }
1✔
1177

1178
#endif
30✔
1179

1180
    if (merge_into_existing && util::File::exists(config.path)) {
30✔
1181
        auto destination_realm = Realm::get_shared_realm(config);
3✔
1182
        destination_realm->begin_transaction();
3✔
1183
        auto destination = destination_realm->transaction_ref();
3✔
1184
        m_transaction->copy_to(destination);
3✔
1185
        destination_realm->commit_transaction();
3✔
1186
        return;
3✔
1187
    }
3✔
1188

1189
    if (config.encryption_key.size() && config.encryption_key.size() != 64) {
27!
1190
        throw InvalidEncryptionKey();
×
1191
    }
×
1192

1193
    auto& tr = transaction();
27✔
1194
    auto repl = tr.get_replication();
27✔
1195
    bool src_is_sync = repl && repl->get_history_type() == Replication::hist_SyncClient;
27✔
1196
    bool dst_is_sync = config.sync_config || config.force_sync_history;
27✔
1197

1198
    if (dst_is_sync) {
27✔
1199
        m_coordinator->write_copy(config.path, config.encryption_key.data());
13✔
1200
        if (!src_is_sync) {
13✔
1201
#if REALM_ENABLE_SYNC
4✔
1202
            DBOptions options;
4✔
1203
            if (config.encryption_key.size()) {
4✔
1204
                options.encryption_key = config.encryption_key.data();
×
1205
            }
×
1206
            auto db = DB::create(make_in_realm_history(), config.path, options);
4✔
1207
            db->create_new_history(sync::make_client_replication());
4✔
1208
#endif
4✔
1209
        }
4✔
1210
    }
13✔
1211
    else {
14✔
1212
        tr.write(config.path, config.encryption_key.data());
14✔
1213
    }
14✔
1214
}
27✔
1215

1216
OwnedBinaryData Realm::write_copy()
1217
{
3✔
1218
    verify_thread();
3✔
1219
    BinaryData buffer = read_group().write_to_mem();
3✔
1220

1221
    // Since OwnedBinaryData does not have a constructor directly taking
1222
    // ownership of BinaryData, we have to do this to avoid copying the buffer
1223
    return OwnedBinaryData(std::unique_ptr<char[]>((char*)buffer.data()), buffer.size());
3✔
1224
}
3✔
1225

1226
void Realm::notify()
1227
{
12,053✔
1228
    if (is_closed() || is_in_transaction() || is_frozen()) {
12,053✔
1229
        return;
331✔
1230
    }
331✔
1231

1232
    verify_thread();
11,722✔
1233

1234
    // Any of the callbacks to user code below could drop the last remaining
1235
    // strong reference to `this`
1236
    auto retain_self = shared_from_this();
11,722✔
1237

1238
    if (m_binding_context) {
11,722✔
1239
        m_binding_context->before_notify();
846✔
1240
        if (is_closed() || is_in_transaction()) {
846✔
1241
            return;
×
1242
        }
×
1243
    }
846✔
1244

1245
    if (!m_coordinator->can_advance(*this)) {
11,722✔
1246
        CountGuard sending_notifications(m_is_sending_notifications);
8,489✔
1247
        m_coordinator->process_available_async(*this);
8,489✔
1248
        return;
8,489✔
1249
    }
8,489✔
1250

1251
    if (m_binding_context) {
3,233✔
1252
        m_binding_context->changes_available();
86✔
1253

1254
        // changes_available() may have advanced the read version, and if
1255
        // so we don't need to do anything further
1256
        if (!m_coordinator->can_advance(*this))
86✔
1257
            return;
2✔
1258
    }
86✔
1259

1260
    CountGuard sending_notifications(m_is_sending_notifications);
3,231✔
1261
    if (m_auto_refresh) {
3,231✔
1262
        if (m_transaction) {
3,228✔
1263
            try {
3,197✔
1264
                m_coordinator->advance_to_ready(*this);
3,197✔
1265
            }
3,197✔
1266
            catch (_impl::UnsupportedSchemaChange const&) {
3,197✔
1267
                translate_schema_error();
×
1268
            }
×
1269
            if (!is_closed())
3,197✔
1270
                cache_new_schema();
3,196✔
1271
        }
3,197✔
1272
        else {
31✔
1273
            if (m_binding_context) {
31✔
1274
                m_binding_context->did_change({}, {});
2✔
1275
            }
2✔
1276
            if (!is_closed()) {
31✔
1277
                m_coordinator->process_available_async(*this);
30✔
1278
            }
30✔
1279
        }
31✔
1280
    }
3,228✔
1281
}
3,231✔
1282

1283
bool Realm::refresh()
1284
{
3,406✔
1285
    verify_thread();
3,406✔
1286
    return do_refresh();
3,406✔
1287
}
3,406✔
1288

1289
bool Realm::do_refresh()
1290
{
19,346✔
1291
    // Frozen Realms never change.
1292
    if (is_frozen()) {
19,346✔
1293
        return false;
138✔
1294
    }
138✔
1295

1296
    if (m_config.immutable()) {
19,208✔
1297
        throw WrongTransactionState("Can't refresh an immutable Realm.");
2✔
1298
    }
2✔
1299

1300
    // can't be any new changes if we're in a write transaction
1301
    if (is_in_transaction()) {
19,206✔
1302
        return false;
15✔
1303
    }
15✔
1304
    // don't advance if we're already in the process of advancing as that just
1305
    // makes things needlessly complicated
1306
    if (m_is_sending_notifications) {
19,191✔
1307
        return false;
4✔
1308
    }
4✔
1309

1310
    // Any of the callbacks to user code below could drop the last remaining
1311
    // strong reference to `this`
1312
    auto retain_self = shared_from_this();
19,187✔
1313

1314
    CountGuard sending_notifications(m_is_sending_notifications);
19,187✔
1315
    if (m_binding_context) {
19,187✔
1316
        m_binding_context->before_notify();
69✔
1317
    }
69✔
1318
    if (m_transaction) {
19,187✔
1319
        try {
3,573✔
1320
            bool version_changed = m_coordinator->advance_to_latest(*this);
3,573✔
1321
            if (is_closed())
3,573✔
1322
                return false;
3✔
1323
            cache_new_schema();
3,570✔
1324
            return version_changed;
3,570✔
1325
        }
3,573✔
1326
        catch (_impl::UnsupportedSchemaChange const&) {
3,573✔
1327
            translate_schema_error();
2✔
1328
        }
2✔
1329
    }
3,573✔
1330

1331
    // No current read transaction, so just create a new one
1332
    read_group();
15,614✔
1333
    m_coordinator->process_available_async(*this);
15,614✔
1334
    return true;
15,614✔
1335
}
19,187✔
1336

1337
void Realm::set_auto_refresh(bool auto_refresh)
1338
{
8✔
1339
    if (is_frozen() && auto_refresh) {
8✔
1340
        throw WrongTransactionState("Auto-refresh cannot be enabled for frozen Realms.");
1✔
1341
    }
1✔
1342
    m_auto_refresh = auto_refresh;
7✔
1343
}
7✔
1344

1345

1346
bool Realm::can_deliver_notifications() const noexcept
1347
{
1,640✔
1348
    if (m_config.immutable() || !m_config.automatic_change_notifications) {
1,640✔
1349
        return false;
1,612✔
1350
    }
1,612✔
1351

1352
    if (!m_scheduler || !m_scheduler->can_invoke()) {
28✔
1353
        return false;
×
1354
    }
×
1355

1356
    return true;
28✔
1357
}
28✔
1358

1359
uint64_t Realm::get_schema_version(const Realm::Config& config)
1360
{
×
1361
    auto coordinator = RealmCoordinator::get_coordinator(config.path);
×
1362
    auto version = coordinator->get_schema_version();
×
1363
    if (version == ObjectStore::NotVersioned)
×
1364
        version = ObjectStore::get_schema_version(coordinator->get_realm(config, util::none)->read_group());
×
1365
    return version;
×
1366
}
×
1367

1368

1369
bool Realm::is_frozen() const
1370
{
116,324✔
1371
    bool result = bool(m_frozen_version);
116,324✔
1372
    REALM_ASSERT_DEBUG(!result || !m_transaction || m_transaction->is_frozen());
116,324✔
1373
    return result;
116,324✔
1374
}
116,324✔
1375

1376
SharedRealm Realm::freeze()
1377
{
673✔
1378
    read_group(); // Freezing requires a read transaction
673✔
1379
    return m_coordinator->freeze_realm(*this);
673✔
1380
}
673✔
1381

1382
void Realm::copy_schema_from(const Realm& source)
1383
{
671✔
1384
    REALM_ASSERT(is_frozen());
671✔
1385
    REALM_ASSERT(m_frozen_version == source.read_transaction_version());
671✔
1386
    m_schema = source.m_schema;
671✔
1387
    m_schema_version = source.m_schema_version;
671✔
1388
    m_schema_transaction_version = m_frozen_version->version;
671✔
1389
    m_dynamic_schema = false;
671✔
1390
}
671✔
1391

1392
void Realm::close()
1393
{
2,237✔
1394
    if (is_closed()) {
2,237✔
1395
        return;
10✔
1396
    }
10✔
1397
    if (m_coordinator) {
2,227✔
1398
        m_coordinator->unregister_realm(this);
2,227✔
1399
    }
2,227✔
1400

1401
    do_invalidate();
2,227✔
1402

1403
    m_binding_context = nullptr;
2,227✔
1404
    m_coordinator = nullptr;
2,227✔
1405
    m_scheduler = nullptr;
2,227✔
1406
    m_config = {};
2,227✔
1407
}
2,227✔
1408

1409
void Realm::delete_files(const std::string& realm_file_path, bool* did_delete_realm)
1410
{
8✔
1411
    bool lock_successful = false;
8✔
1412
    try {
8✔
1413
        lock_successful = DB::call_with_lock(realm_file_path, [=](auto const& path) {
8✔
1414
            DB::delete_files(path, did_delete_realm);
6✔
1415
        });
6✔
1416
    }
8✔
1417
    catch (const FileAccessError& e) {
8✔
1418
        if (e.code() != ErrorCodes::FileNotFound) {
1✔
1419
            throw;
×
1420
        }
×
1421
        // Thrown only if the parent directory of the lock file does not exist,
1422
        // which obviously indicates that we didn't need to delete anything
1423
        if (did_delete_realm) {
1✔
1424
            *did_delete_realm = false;
1✔
1425
        }
1✔
1426
        return;
1✔
1427
    }
1✔
1428
    if (!lock_successful) {
7✔
1429
        throw FileAccessError(
1✔
1430
            ErrorCodes::DeleteOnOpenRealm,
1✔
1431
            util::format("Cannot delete files of an open Realm: '%1' is still in use.", realm_file_path),
1✔
1432
            realm_file_path);
1✔
1433
    }
1✔
1434
}
7✔
1435

1436
AuditInterface* Realm::audit_context() const noexcept
1437
{
70,740✔
1438
    return m_coordinator ? m_coordinator->audit_context() : nullptr;
70,740✔
1439
}
70,740✔
1440

1441
namespace {
1442

1443
/*********************************** PropId **********************************/
1444

1445
// The KeyPathResolver will build up a tree of these objects starting with the
1446
// first property. If a wildcard specifier is part of the path, one object can
1447
// have several children.
1448
struct PropId {
1449
    PropId(TableKey tk, ColKey ck, const Property* prop, const ObjectSchema* os, bool b)
1450
        : table_key(tk)
623✔
1451
        , col_key(ck)
623✔
1452
        , origin_prop(prop)
623✔
1453
        , target_schema(os)
623✔
1454
        , mandatory(b)
623✔
1455
    {
623✔
1456
    }
623✔
1457
    void expand(KeyPath& key_path, KeyPathArray& key_path_array) const;
1458

1459
    TableKey table_key;
1460
    ColKey col_key;
1461
    const Property* origin_prop;
1462
    const ObjectSchema* target_schema;
1463
    std::vector<PropId> children;
1464
    bool mandatory;
1465
};
1466

1467
// This function will create one KeyPath entry in key_path_array for every
1468
// branch in the tree,
1469
void PropId::expand(KeyPath& key_path, KeyPathArray& key_path_array) const
1470
{
604✔
1471
    key_path.emplace_back(table_key, col_key);
604✔
1472
    if (children.empty()) {
604✔
1473
        key_path_array.push_back(key_path);
406✔
1474
    }
406✔
1475
    else {
198✔
1476
        for (auto& child : children) {
236✔
1477
            child.expand(key_path, key_path_array);
236✔
1478
        }
236✔
1479
    }
198✔
1480
    key_path.pop_back();
604✔
1481
}
604✔
1482

1483
/****************************** KeyPathResolver ******************************/
1484

1485
class KeyPathResolver {
1486
public:
1487
    KeyPathResolver(Group& g, const Schema& schema)
1488
        : m_group(g)
356✔
1489
        , m_schema(schema)
356✔
1490
    {
356✔
1491
    }
356✔
1492

1493
    void resolve(const ObjectSchema* object_schema, const char* path)
1494
    {
356✔
1495
        m_full_path = path;
356✔
1496
        if (!_resolve(m_root_props, object_schema, path, true)) {
356✔
1497
            throw InvalidArgument(util::format("'%1' does not resolve in any valid key paths.", m_full_path));
1✔
1498
        }
1✔
1499
    }
356✔
1500

1501
    void expand(KeyPathArray& key_path_array) const
1502
    {
352✔
1503
        for (auto& elem : m_root_props) {
368✔
1504
            KeyPath key_path;
368✔
1505
            key_path.reserve(4);
368✔
1506
            elem.expand(key_path, key_path_array);
368✔
1507
        }
368✔
1508
    }
352✔
1509

1510
private:
1511
    std::pair<ColKey, const ObjectSchema*> get_col_key(const Property* prop);
1512
    bool _resolve(std::vector<PropId>& props, const ObjectSchema* object_schema, const char* path, bool mandatory);
1513
    bool _resolve(PropId& current, const char* path);
1514

1515
    Group& m_group;
1516
    const char* m_full_path = nullptr;
1517
    const Schema& m_schema;
1518
    std::vector<PropId> m_root_props;
1519
};
1520

1521
// Get the column key for a specific Property. In case the property is representing a backlink
1522
// we need to look up the backlink column based on the forward link properties.
1523
std::pair<ColKey, const ObjectSchema*> KeyPathResolver::get_col_key(const Property* prop)
1524
{
623✔
1525
    ColKey col_key = prop->column_key;
623✔
1526
    const ObjectSchema* target_schema = nullptr;
623✔
1527
    if (prop->type == PropertyType::Object || prop->type == PropertyType::LinkingObjects) {
623✔
1528
        auto found_schema = m_schema.find(prop->object_type);
337✔
1529
        if (found_schema != m_schema.end()) {
337✔
1530
            target_schema = &*found_schema;
337✔
1531
            if (prop->type == PropertyType::LinkingObjects) {
337✔
1532
                auto origin_prop = target_schema->property_for_name(prop->link_origin_property_name);
129✔
1533
                auto origin_table = ObjectStore::table_for_object_type(m_group, target_schema->name);
129✔
1534
                col_key = origin_table->get_opposite_column(origin_prop->column_key);
129✔
1535
            }
129✔
1536
        }
337✔
1537
    }
337✔
1538
    return {col_key, target_schema};
623✔
1539
}
623✔
1540

1541
// This function will add one or more PropId objects to the props array. This array can either be the root
1542
// array in the KeyPathResolver or it can be the 'children' array in one PropId.
1543
bool KeyPathResolver::_resolve(std::vector<PropId>& props, const ObjectSchema* object_schema, const char* path,
1544
                               bool mandatory)
1545
{
560✔
1546
    if (*path == '*') {
560✔
1547
        path++;
23✔
1548
        // Add all properties
1549
        props.reserve(object_schema->persisted_properties.size() + object_schema->computed_properties.size());
23✔
1550
        for (auto& p : object_schema->persisted_properties) {
75✔
1551
            auto [col_key, target_schema] = get_col_key(&p);
75✔
1552
            props.emplace_back(object_schema->table_key, col_key, &p, target_schema, false);
75✔
1553
        }
75✔
1554
        for (const auto& p : object_schema->computed_properties) {
23✔
1555
            auto [col_key, target_schema] = get_col_key(&p);
17✔
1556
            props.emplace_back(object_schema->table_key, col_key, &p, target_schema, false);
17✔
1557
        }
17✔
1558
    }
23✔
1559
    else {
537✔
1560
        auto p = find_chr(path, '.');
537✔
1561
        StringData property(path, p - path);
537✔
1562
        path = p;
537✔
1563
        auto prop = object_schema->property_for_public_name(property);
537✔
1564
        if (prop) {
537✔
1565
            auto [col_key, target_schema] = get_col_key(prop);
531✔
1566
            props.emplace_back(object_schema->table_key, col_key, prop, target_schema, true);
531✔
1567
        }
531✔
1568
        else {
6✔
1569
            if (mandatory) {
6✔
1570
                throw InvalidArgument(util::format("Property '%1' in KeyPath '%2' is not a valid property in %3.",
2✔
1571
                                                   property, m_full_path, object_schema->name));
2✔
1572
            }
2✔
1573
            else {
4✔
1574
                return false;
4✔
1575
            }
4✔
1576
        }
6✔
1577
    }
537✔
1578

1579
    if (*path++ == '.') {
554✔
1580
        auto it = props.begin();
193✔
1581
        while (it != props.end()) {
419✔
1582
            if (_resolve(*it, path)) {
226✔
1583
                ++it;
207✔
1584
            }
207✔
1585
            else {
19✔
1586
                it = props.erase(it);
19✔
1587
            }
19✔
1588
        }
226✔
1589
    }
193✔
1590
    return props.size();
554✔
1591
}
560✔
1592

1593
bool KeyPathResolver::_resolve(PropId& current, const char* path)
1594
{
226✔
1595
    auto object_schema = current.target_schema;
226✔
1596
    if (!object_schema) {
226✔
1597
        if (current.mandatory) {
22✔
1598
            throw InvalidArgument(
1✔
1599
                util::format("Property '%1' in KeyPath '%2' is not a collection of objects or an object "
1✔
1600
                             "reference, so it cannot be used as an intermediate keypath element.",
1✔
1601
                             current.origin_prop->public_name, m_full_path));
1✔
1602
        }
1✔
1603
        // Check if the rest of the path is stars. If not, we should exclude this property
1604
        auto tmp = path;
21✔
1605
        do {
25✔
1606
            auto p = find_chr(tmp, '.');
25✔
1607
            StringData property(tmp, p - tmp);
25✔
1608
            tmp = p;
25✔
1609
            if (property != "*") {
25✔
1610
                return false;
12✔
1611
            }
12✔
1612
        } while (*tmp++ == '.');
25✔
1613
        return true;
9✔
1614
    }
21✔
1615
    // Target schema exists - proceed
1616
    return _resolve(current.children, object_schema, path, current.mandatory);
204✔
1617
}
226✔
1618

1619
} // namespace
1620

1621
KeyPathArray Realm::create_key_path_array(StringData table_name, const std::vector<std::string>& key_paths)
1622
{
349✔
1623
    std::vector<const char*> vec;
349✔
1624
    vec.reserve(key_paths.size());
349✔
1625
    for (auto& kp : key_paths) {
349✔
1626
        vec.push_back(kp.c_str());
349✔
1627
    }
349✔
1628
    return create_key_path_array(m_schema.find(table_name)->table_key, vec.size(), &vec.front());
349✔
1629
}
349✔
1630

1631
KeyPathArray Realm::create_key_path_array(TableKey table_key, size_t num_key_paths, const char** all_key_paths)
1632
{
357✔
1633
    auto object_schema = m_schema.find(table_key);
357✔
1634
    REALM_ASSERT(object_schema != m_schema.end());
357✔
1635
    KeyPathArray resolved_key_path_array;
357✔
1636
    for (size_t n = 0; n < num_key_paths; n++) {
713✔
1637
        KeyPathResolver resolver(read_group(), m_schema);
356✔
1638
        // Build property tree
1639
        resolver.resolve(&*object_schema, all_key_paths[n]);
356✔
1640
        // Expand tree into separate lines
1641
        resolver.expand(resolved_key_path_array);
356✔
1642
    }
356✔
1643
    return resolved_key_path_array;
357✔
1644
}
357✔
1645

1646
#ifdef REALM_DEBUG
1647
void Realm::print_key_path_array(const KeyPathArray& kpa)
1648
{
×
1649
    auto& g = read_group();
×
1650
    for (auto& kp : kpa) {
×
1651
        for (auto [tk, ck] : kp) {
×
1652
            auto table = g.get_table(tk);
×
1653
            std::cout << '{' << table->get_name() << ':';
×
1654
            if (ck.get_type() == col_type_BackLink) {
×
1655
                auto col_key = table->get_opposite_column(ck);
×
1656
                table = table->get_opposite_table(ck);
×
1657
                std::cout << '{' << table->get_name() << ':' << table->get_column_name(col_key) << "}->";
×
1658
            }
×
1659
            else {
×
1660
                std::cout << table->get_column_name(ck);
×
1661
            }
×
1662
            std::cout << '}';
×
1663
        }
×
1664
        std::cout << std::endl;
×
1665
    }
×
1666
}
×
1667
#endif
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