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

realm / realm-core / thomas.goyne_478

02 Aug 2024 05:19PM UTC coverage: 91.089% (-0.01%) from 91.1%
thomas.goyne_478

Pull #7944

Evergreen

tgoyne
Only track pending client resets done by the same core version

If the previous attempt at performing a client reset was done with a different
core version then we should retry the client reset as the new version may have
fixed a bug that made the previous attempt fail (or may be a downgrade to a
version before when the bug was introduced). This also simplifies the tracking
as it means that we don't need to be able to read trackers created by different
versions.

This also means that we can freely change the schema of the table, which this
takes advantage of to drop the unused primary key and make the error required,
as we never actually stored null and the code reading it would have crashed if
it encountered a null error.
Pull Request #7944: Only track pending client resets done by the same core version

102704 of 181534 branches covered (56.58%)

138 of 153 new or added lines in 10 files covered. (90.2%)

85 existing lines in 16 files now uncovered.

216717 of 237917 relevant lines covered (91.09%)

5947762.1 hits per line

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

91.89
/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)
56,418✔
67
    {
116,252✔
68
        ++m_count;
116,252✔
69
    }
116,252✔
70
    ~CountGuard()
71
    {
116,252✔
72
        --m_count;
116,252✔
73
    }
116,252✔
74

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

80
bool RealmConfig::needs_file_format_upgrade() const
81
{
12✔
82
    return DB::needs_file_format_upgrade(path, encryption_key);
12✔
83
}
12✔
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,335✔
88
    , m_frozen_version(version)
17,335✔
89
    , m_scheduler(m_config.scheduler)
17,335✔
90
{
35,164✔
91
    if (version) {
35,164✔
92
        m_auto_refresh = false;
1,773✔
93
        REALM_ASSERT(*version != VersionID());
1,773✔
94
    }
1,773✔
95
    else if (!coordinator->get_cached_schema(m_schema, m_schema_version, m_schema_transaction_version)) {
33,391✔
96
        m_transaction = coordinator->begin_read();
19,639✔
97
        read_schema_from_group_if_needed();
19,639✔
98
        coordinator->cache_schema(m_schema, m_schema_version, m_schema_transaction_version);
19,639✔
99
        m_transaction = nullptr;
19,639✔
100
    }
19,639✔
101
    m_coordinator = std::move(coordinator);
35,164✔
102
}
35,164✔
103

104
Realm::~Realm()
105
{
35,164✔
106
    if (m_transaction) {
35,164✔
107
        // Wait for potential syncing to finish
108
        m_transaction->prepare_for_close();
28,632✔
109
        call_completion_callbacks();
28,632✔
110
    }
28,632✔
111

112
    if (m_coordinator) {
35,164✔
113
        m_coordinator->unregister_realm(this);
30,681✔
114
    }
30,681✔
115
}
35,164✔
116

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

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

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

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

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

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

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

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

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

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

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

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

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

205
sync::SubscriptionSet Realm::get_latest_subscription_set()
206
{
871✔
207
    if (!m_config.sync_config || !m_config.sync_config->flx_sync_requested) {
871✔
208
        throw IllegalOperation("Flexible sync is not enabled");
4✔
209
    }
4✔
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();
867✔
212
    REALM_ASSERT(flx_sub_store);
867✔
213
    return flx_sub_store->get_latest();
867✔
214
}
871✔
215

216
sync::SubscriptionSet Realm::get_active_subscription_set()
217
{
92✔
218
    if (!m_config.sync_config || !m_config.sync_config->flx_sync_requested) {
92✔
219
        throw IllegalOperation("Flexible sync is not enabled");
6✔
220
    }
6✔
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();
86✔
223
    REALM_ASSERT(flx_sub_store);
86✔
224
    return flx_sub_store->get_active();
86✔
225
}
92✔
226
#endif
227

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

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

248
    Group& group = read_group();
84,404✔
249
    auto current_version = transaction().get_version_of_current_transaction().version;
84,404✔
250
    if (m_schema_transaction_version == current_version)
84,404✔
251
        return;
63,534✔
252

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

257
    if (m_coordinator)
20,870✔
258
        m_coordinator->cache_schema(schema, m_schema_version, m_schema_transaction_version);
1,293✔
259

260
    if (m_dynamic_schema) {
20,870✔
261
        if (m_schema == schema) {
20,268✔
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);
19,264✔
264
        }
19,264✔
265
        else {
1,004✔
266
            // The structure of the schema has changed, so replace our copy of the schema.
267
            m_schema = std::move(schema);
1,004✔
268
        }
1,004✔
269
    }
20,268✔
270
    else {
602✔
271
        ObjectStore::verify_valid_external_changes(m_schema.compare(schema, m_config.schema_mode));
602✔
272
        m_schema.copy_keys_from(schema, m_config.schema_subset_mode);
602✔
273
    }
602✔
274
    notify_schema_changed();
20,870✔
275
}
20,870✔
276

277
bool Realm::reset_file(Schema& schema, std::vector<SchemaChange>& required_changes)
278
{
20✔
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;
20✔
284
    m_coordinator->delete_and_reopen();
20✔
285

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

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

299
    switch (m_config.schema_mode) {
19,583✔
300
        case SchemaMode::Automatic:
10,383✔
301
            verify_schema_version_not_decreasing(version);
10,383✔
302
            return true;
10,383✔
303

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

312
        case SchemaMode::SoftResetFile:
32✔
313
            if (m_schema_version == ObjectStore::NotVersioned)
32✔
314
                return true;
18✔
315
            if (m_schema_version == version && !ObjectStore::needs_migration(changes))
14✔
316
                return true;
8✔
317
            REALM_FALLTHROUGH;
14✔
318
        case SchemaMode::HardResetFile:
20✔
319
            reset_file(schema, changes);
20✔
320
            return true;
20✔
321

322
        case SchemaMode::AdditiveDiscovered:
116✔
323
        case SchemaMode::AdditiveExplicit: {
8,988✔
324
            bool will_apply_index_changes = version > m_schema_version;
8,988✔
325
            if (ObjectStore::verify_valid_additive_changes(changes, will_apply_index_changes))
8,988✔
326
                return true;
8,796✔
327
            return version != m_schema_version;
192✔
328
        }
8,988✔
329

330
        case SchemaMode::Manual:
128✔
331
            verify_schema_version_not_decreasing(version);
128✔
332
            if (version == m_schema_version) {
128✔
333
                ObjectStore::verify_no_changes_required(changes);
28✔
334
                REALM_UNREACHABLE(); // changes is non-empty so above line always throws
335
            }
28✔
336
            return true;
128✔
337
    }
19,583✔
338
    REALM_COMPILER_HINT_UNREACHABLE();
×
339
}
19,583✔
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
{
10,511✔
344
#if REALM_ENABLE_SYNC
10,511✔
345
    if (m_config.sync_config && m_config.sync_config->flx_sync_requested)
10,511✔
346
        return;
×
347
#endif
10,511✔
348
    if (version < m_schema_version && m_schema_version != ObjectStore::NotVersioned)
10,511✔
349
        throw InvalidSchemaVersionException(m_schema_version, version, false);
4✔
350
}
10,511✔
351

352
Schema Realm::get_full_schema()
353
{
32,340✔
354
    if (!m_config.immutable())
32,340✔
355
        do_refresh();
32,274✔
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())
32,340✔
360
        return m_schema;
12,646✔
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;
19,694✔
365
    uint64_t actual_version;
19,694✔
366
    uint64_t version = -1;
19,694✔
367
    bool got_cached = m_coordinator->get_cached_schema(actual_schema, actual_version, version);
19,694✔
368
    if (!got_cached || version != transaction().get_version_of_current_transaction().version)
19,694✔
369
        return ObjectStore::schema_from_group(read_group());
19,040✔
370
    return actual_schema;
654✔
371
}
19,694✔
372

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

378
Class Realm::get_class(StringData object_type)
379
{
20✔
380
    auto it = m_schema.find(object_type);
20✔
381
    if (it == m_schema.end()) {
20✔
382
        throw LogicError(ErrorCodes::NoSuchTable, util::format("No type '%1'", object_type));
×
383
    }
×
384
    return {shared_from_this(), &*it};
20✔
385
}
20✔
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
{
12✔
400
    verify_thread();
12✔
401
    verify_open();
12✔
402
    REALM_ASSERT(m_dynamic_schema);
12✔
403
    REALM_ASSERT(m_schema_version != ObjectStore::NotVersioned);
12✔
404

405
    std::vector<SchemaChange> changes = m_schema.compare(schema, m_config.schema_mode);
12✔
406
    switch (m_config.schema_mode) {
12✔
407
        case SchemaMode::Automatic:
2✔
408
        case SchemaMode::SoftResetFile:
2✔
409
        case SchemaMode::HardResetFile:
2✔
410
            ObjectStore::verify_no_migration_required(changes);
2✔
411
            break;
2✔
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:
8✔
420
            ObjectStore::verify_valid_additive_changes(changes);
8✔
421
            break;
8✔
422

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

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

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

445
    schema.validate(static_cast<SchemaValidationMode>(validation_mode));
32,348✔
446

447
    bool was_in_read_transaction = is_in_read_transaction();
32,348✔
448
    Schema actual_schema = get_full_schema();
32,348✔
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) {
32,348✔
456
        ObjectStore::verify_compatible_for_immutable_and_readonly(
272✔
457
            actual_schema.compare(schema, m_config.schema_mode, true));
272✔
458
        set_schema(actual_schema, std::move(schema));
272✔
459
        return;
272✔
460
    }
272✔
461

462
    std::vector<SchemaChange> required_changes = actual_schema.compare(schema, m_config.schema_mode);
32,076✔
463
    if (!schema_change_needs_write_transaction(schema, required_changes, version)) {
32,076✔
464
        if (!was_in_read_transaction)
12,574✔
465
            m_transaction = nullptr;
12,544✔
466
        set_schema(actual_schema, std::move(schema));
12,574✔
467
        return;
12,574✔
468
    }
12,574✔
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 {
19,502✔
473
        // When in_transaction is true, caller is responsible to cancel the transaction.
474
        if (!in_transaction && is_in_transaction())
19,404✔
475
            cancel_transaction();
110✔
476
        if (!was_in_read_transaction)
19,404✔
477
            m_transaction = nullptr;
19,054✔
478
    });
19,404✔
479

480
    if (!in_transaction) {
19,502✔
481
        transaction().promote_to_write();
19,378✔
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) {
19,378✔
486
            actual_schema = *m_new_schema;
17✔
487
            required_changes = actual_schema.compare(schema, m_config.schema_mode);
17✔
488
            if (!schema_change_needs_write_transaction(schema, required_changes, version)) {
17✔
489
                cancel_transaction();
2✔
490
                cache_new_schema();
2✔
491
                set_schema(actual_schema, std::move(schema));
2✔
492
                return;
2✔
493
            }
2✔
494
        }
17✔
495
        cache_new_schema();
19,376✔
496
    }
19,376✔
497

498
    schema.copy_keys_from(actual_schema, m_config.schema_subset_mode);
19,500✔
499

500
    uint64_t old_schema_version = m_schema_version;
19,500✔
501
    bool additive = m_config.schema_mode == SchemaMode::AdditiveDiscovered ||
19,500✔
502
                    m_config.schema_mode == SchemaMode::AdditiveExplicit ||
19,500✔
503
                    m_config.schema_mode == SchemaMode::ReadOnly;
19,500✔
504
    if (migration_function && !additive) {
19,500✔
505
        auto wrapper = [&] {
863✔
506
            auto config = m_config;
210✔
507
            config.schema_mode = SchemaMode::ReadOnly;
210✔
508
            config.schema = util::none;
210✔
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());
210✔
512
            // block autorefresh for the old realm
513
            old_realm->m_auto_refresh = false;
210✔
514
            migration_function(old_realm, shared_from_this(), m_schema);
210✔
515
        };
210✔
516

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

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

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

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

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

561
    notify_schema_changed();
19,500✔
562
}
19,500✔
563

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

569
void Realm::add_schema_change_handler()
570
{
64,883✔
571
    if (m_config.immutable())
64,883✔
572
        return;
56✔
573
    m_transaction->set_schema_change_notification_handler([&] {
64,827✔
574
        m_new_schema = ObjectStore::schema_from_group(read_group());
3,991✔
575
        m_schema_version = ObjectStore::get_schema_version(read_group());
3,991✔
576
        if (m_dynamic_schema) {
3,991✔
577
            m_schema = *m_new_schema;
37✔
578
        }
37✔
579
        else {
3,954✔
580
            m_schema.copy_keys_from(*m_new_schema, m_config.schema_subset_mode);
3,954✔
581
        }
3,954✔
582
        notify_schema_changed();
3,991✔
583
    });
3,991✔
584
}
64,827✔
585

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

592
    auto new_version = transaction().get_version_of_current_transaction().version;
145,245✔
593
    if (m_new_schema)
145,245✔
594
        m_coordinator->cache_schema(std::move(*m_new_schema), m_schema_version, new_version);
23,283✔
595
    else
121,962✔
596
        m_coordinator->advance_schema_cache(m_schema_transaction_version, new_version);
121,962✔
597
    m_schema_transaction_version = new_version;
145,245✔
598
    m_new_schema = util::none;
145,245✔
599
}
145,245✔
600

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

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

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

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

620
static void check_can_create_write_transaction(const Realm* realm)
621
{
97,513✔
622
    realm->verify_thread();
97,513✔
623
    realm->verify_open();
97,513✔
624
    if (realm->config().immutable() || realm->config().read_only()) {
97,513✔
625
        throw WrongTransactionState("Can't perform transactions on read-only Realms.");
6✔
626
    }
6✔
627
    if (realm->is_frozen()) {
97,507✔
628
        throw WrongTransactionState("Can't perform transactions on a frozen Realm");
2✔
629
    }
2✔
630
    if (!realm->is_closed() && realm->get_number_of_versions() > realm->config().max_number_of_active_versions) {
97,505✔
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
}
97,505✔
636

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

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

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

657
bool Realm::verify_notifications_available(bool throw_on_error) const
658
{
29,984✔
659
    if (is_frozen()) {
29,984✔
660
        if (throw_on_error)
56✔
661
            throw WrongTransactionState(
12✔
662
                "Notifications are not available on frozen collections since they do not change.");
12✔
663
        return false;
44✔
664
    }
56✔
665
    if (config().immutable()) {
29,928✔
666
        if (throw_on_error)
2✔
667
            throw WrongTransactionState("Cannot create asynchronous query for immutable Realms");
×
668
        return false;
2✔
669
    }
2✔
670
    if (throw_on_error) {
29,926✔
671
        if (m_transaction && m_transaction->get_commit_size() > 0)
10,540✔
672
            throw WrongTransactionState(
2✔
673
                "Cannot create asynchronous query after making changes in a write transaction.");
2✔
674
    }
10,540✔
675
    else {
19,386✔
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())
19,386✔
679
            return false;
16,106✔
680
    }
19,386✔
681

682
    return true;
13,818✔
683
}
29,926✔
684

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

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

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

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

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

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

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

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

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

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

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

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

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

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

799
void Realm::check_pending_write_requests()
800
{
45,102✔
801
    if (!m_async_write_q.empty()) {
45,102✔
802
        if (m_transaction->is_async()) {
420✔
803
            run_writes_on_proper_thread();
6✔
804
        }
6✔
805
        else {
414✔
806
            m_coordinator->async_request_write_mutex(*this);
414✔
807
        }
414✔
808
    }
420✔
809
}
45,102✔
810

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

827
void Realm::run_writes()
828
{
500✔
829
    if (!m_transaction) {
500✔
830
        // Realm might have been closed
831
        return;
×
832
    }
×
833
    if (m_transaction->is_synchronizing()) {
500✔
834
        // Wait for the synchronization complete callback before we run more
835
        // writes as we can't add commits while in that state
UNCOV
836
        return;
×
UNCOV
837
    }
×
838
    if (is_in_transaction()) {
500✔
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;
2✔
843
    }
2✔
844

845
    CountGuard running_writes(m_is_running_async_writes);
498✔
846
    int run_limit = 20; // max number of commits without full sync to disk
498✔
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) {
2,708✔
851
        // We might have made a sync commit and thereby given up the write lock
852
        if (!m_transaction->holds_write_mutex()) {
2,659✔
853
            return;
113✔
854
        }
113✔
855

856
        do_begin_transaction();
2,546✔
857

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

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

874
            if (m_async_exception_handler) {
6✔
875
                m_async_exception_handler(write_desc.handle, std::current_exception());
2✔
876
                continue;
2✔
877
            }
2✔
878
            end_current_write();
4✔
879
            throw;
4✔
880
        }
6✔
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) {
2,540✔
885
            m_notify_only = false;
12✔
886
            return;
12✔
887
        }
12✔
888

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

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

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

915
auto Realm::async_begin_transaction(util::UniqueFunction<void()>&& the_write_block, bool notify_only) -> AsyncHandle
916
{
2,560✔
917
    check_can_create_write_transaction(this);
2,560✔
918
    if (m_is_running_async_commit_completions) {
2,560✔
919
        throw WrongTransactionState("Can't begin a write transaction from inside a commit completion callback.");
×
920
    }
×
921
    if (!m_scheduler->can_invoke()) {
2,560✔
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);
2,560✔
926

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

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

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

950
    m_transaction->promote_to_async();
2,516✔
951
    REALM_ASSERT(m_transaction->holds_write_mutex());
2,516✔
952
    REALM_ASSERT(!m_notify_only);
2,516✔
953
    // auditing is not supported
954
    REALM_ASSERT(!audit_context());
2,516✔
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++;
2,516✔
958
    m_async_commit_q.push_back({std::move(completion), handle});
2,516✔
959
    try {
2,516✔
960
        m_coordinator->commit_write(*this, /* commit_to_disk: */ false);
2,516✔
961
    }
2,516✔
962
    catch (...) {
2,516✔
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()) {
4✔
966
            // Exception happened before the commit, so roll back the transaction
967
            // and remove the completion handler from the queue
968
            cancel_transaction();
2✔
969
            auto it = std::find_if(m_async_commit_q.begin(), m_async_commit_q.end(), [=](auto& e) {
2✔
970
                return e.handle == handle;
2✔
971
            });
2✔
972
            if (it != m_async_commit_q.end()) {
2✔
973
                m_async_commit_q.erase(it);
2✔
974
            }
2✔
975
        }
2✔
976
        else if (m_transaction) {
2✔
977
            end_current_write(false);
2✔
978
        }
2✔
979
        throw;
4✔
980
    }
4✔
981

982
    if (m_is_running_async_writes) {
2,510✔
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) {
2,368✔
986
            m_async_commit_barrier_requested = true;
206✔
987
        }
206✔
988
    }
2,368✔
989
    else {
142✔
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) {
142✔
993
            run_writes();
×
994
        }
×
995
        else {
142✔
996
            end_current_write(false);
142✔
997
        }
142✔
998
    }
142✔
999
    return handle;
2,510✔
1000
}
2,516✔
1001

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

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

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

1029
    if (is_in_transaction()) {
47,692✔
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();
47,692✔
1036

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

1040
    do_begin_transaction();
47,692✔
1041
}
47,692✔
1042

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

1054
    if (m_transaction && !m_transaction->has_unsynced_commits()) {
50,230✔
1055
        call_completion_callbacks();
48,180✔
1056
    }
48,180✔
1057
}
50,230✔
1058

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

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

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

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

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

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

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

1098
    transaction::cancel(transaction(), m_binding_context.get());
2,915✔
1099

1100
    if (m_transaction && !m_is_running_async_writes) {
2,915✔
1101
        if (m_async_write_q.empty()) {
2,907✔
1102
            end_current_write();
2,899✔
1103
        }
2,899✔
1104
        else {
8✔
1105
            check_pending_write_requests();
8✔
1106
        }
8✔
1107
    }
2,907✔
1108
}
2,915✔
1109

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

1115
    if (m_is_sending_notifications) {
320✔
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;
6✔
1120
    }
6✔
1121

1122
    if (is_in_transaction()) {
314✔
1123
        cancel_transaction();
172✔
1124
    }
172✔
1125

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

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

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

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

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

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

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

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

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

1178
#endif
54✔
1179

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

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

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

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

1216
OwnedBinaryData Realm::write_copy()
1217
{
6✔
1218
    verify_thread();
6✔
1219
    BinaryData buffer = read_group().write_to_mem();
6✔
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());
6✔
1224
}
6✔
1225

1226
void Realm::notify()
1227
{
26,752✔
1228
    if (is_closed() || is_in_transaction() || is_frozen()) {
26,752✔
1229
        return;
662✔
1230
    }
662✔
1231

1232
    verify_thread();
26,090✔
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();
26,090✔
1237

1238
    if (m_binding_context) {
26,090✔
1239
        m_binding_context->before_notify();
1,697✔
1240
        if (is_closed() || is_in_transaction()) {
1,697✔
1241
            return;
×
1242
        }
×
1243
    }
1,697✔
1244

1245
    if (!m_coordinator->can_advance(*this)) {
26,090✔
1246
        CountGuard sending_notifications(m_is_sending_notifications);
18,814✔
1247
        m_coordinator->process_available_async(*this);
18,814✔
1248
        return;
18,814✔
1249
    }
18,814✔
1250

1251
    if (m_binding_context) {
7,276✔
1252
        m_binding_context->changes_available();
172✔
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))
172✔
1257
            return;
4✔
1258
    }
172✔
1259

1260
    CountGuard sending_notifications(m_is_sending_notifications);
7,272✔
1261
    if (m_auto_refresh) {
7,278✔
1262
        if (m_transaction) {
7,275✔
1263
            try {
7,213✔
1264
                m_coordinator->advance_to_ready(*this);
7,213✔
1265
            }
7,213✔
1266
            catch (_impl::UnsupportedSchemaChange const&) {
7,213✔
1267
                translate_schema_error();
×
1268
            }
×
1269
            if (!is_closed())
7,213✔
1270
                cache_new_schema();
7,211✔
1271
        }
7,213✔
1272
        else {
62✔
1273
            if (m_binding_context) {
62✔
1274
                m_binding_context->did_change({}, {});
4✔
1275
            }
4✔
1276
            if (!is_closed()) {
62✔
1277
                m_coordinator->process_available_async(*this);
60✔
1278
            }
60✔
1279
        }
62✔
1280
    }
7,275✔
1281
}
7,272✔
1282

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

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

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

1300
    // can't be any new changes if we're in a write transaction
1301
    if (is_in_transaction()) {
39,002✔
1302
        return false;
30✔
1303
    }
30✔
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) {
38,972✔
1307
        return false;
8✔
1308
    }
8✔
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();
38,964✔
1313

1314
    CountGuard sending_notifications(m_is_sending_notifications);
38,964✔
1315
    if (m_binding_context) {
38,964✔
1316
        m_binding_context->before_notify();
138✔
1317
    }
138✔
1318
    if (m_transaction) {
38,964✔
1319
        try {
7,342✔
1320
            bool version_changed = m_coordinator->advance_to_latest(*this);
7,342✔
1321
            if (is_closed())
7,342✔
1322
                return false;
6✔
1323
            cache_new_schema();
7,336✔
1324
            return version_changed;
7,336✔
1325
        }
7,342✔
1326
        catch (_impl::UnsupportedSchemaChange const&) {
7,342✔
1327
            translate_schema_error();
4✔
1328
        }
4✔
1329
    }
7,342✔
1330

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

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

1345

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

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

1356
    return true;
56✔
1357
}
56✔
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
{
238,164✔
1371
    bool result = bool(m_frozen_version);
238,164✔
1372
    REALM_ASSERT_DEBUG(!result || !m_transaction || m_transaction->is_frozen());
238,164✔
1373
    return result;
238,164✔
1374
}
238,164✔
1375

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

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

1392
void Realm::close()
1393
{
4,503✔
1394
    if (is_closed()) {
4,503✔
1395
        return;
20✔
1396
    }
20✔
1397
    if (m_coordinator) {
4,483✔
1398
        m_coordinator->unregister_realm(this);
4,483✔
1399
    }
4,483✔
1400

1401
    do_invalidate();
4,483✔
1402

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

1409
void Realm::delete_files(const std::string& realm_file_path, bool* did_delete_realm)
1410
{
16✔
1411
    bool lock_successful = false;
16✔
1412
    try {
16✔
1413
        lock_successful = DB::call_with_lock(realm_file_path, [=](auto const& path) {
16✔
1414
            DB::delete_files(path, did_delete_realm);
12✔
1415
        });
12✔
1416
    }
16✔
1417
    catch (const FileAccessError& e) {
16✔
1418
        if (e.code() != ErrorCodes::FileNotFound) {
2✔
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) {
2✔
1424
            *did_delete_realm = false;
2✔
1425
        }
2✔
1426
        return;
2✔
1427
    }
2✔
1428
    if (!lock_successful) {
14✔
1429
        throw FileAccessError(
2✔
1430
            ErrorCodes::DeleteOnOpenRealm,
2✔
1431
            util::format("Cannot delete files of an open Realm: '%1' is still in use.", realm_file_path),
2✔
1432
            realm_file_path);
2✔
1433
    }
2✔
1434
}
14✔
1435

1436
AuditInterface* Realm::audit_context() const noexcept
1437
{
143,936✔
1438
    return m_coordinator ? m_coordinator->audit_context() : nullptr;
143,936✔
1439
}
143,936✔
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)
529✔
1451
        , col_key(ck)
529✔
1452
        , origin_prop(prop)
529✔
1453
        , target_schema(os)
529✔
1454
        , mandatory(b)
529✔
1455
    {
1,058✔
1456
    }
1,058✔
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
{
1,020✔
1471
    key_path.emplace_back(table_key, col_key);
1,020✔
1472
    if (children.empty()) {
1,020✔
1473
        key_path_array.push_back(key_path);
676✔
1474
    }
676✔
1475
    else {
344✔
1476
        for (auto& child : children) {
354✔
1477
            child.expand(key_path, key_path_array);
354✔
1478
        }
354✔
1479
    }
344✔
1480
    key_path.pop_back();
1,020✔
1481
}
1,020✔
1482

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

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

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

1501
    void expand(KeyPathArray& key_path_array) const
1502
    {
658✔
1503
        for (auto& elem : m_root_props) {
666✔
1504
            KeyPath key_path;
666✔
1505
            key_path.reserve(4);
666✔
1506
            elem.expand(key_path, key_path_array);
666✔
1507
        }
666✔
1508
    }
658✔
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
{
1,058✔
1525
    ColKey col_key = prop->column_key;
1,058✔
1526
    const ObjectSchema* target_schema = nullptr;
1,058✔
1527
    if (prop->type == PropertyType::Object || prop->type == PropertyType::LinkingObjects) {
1,058✔
1528
        auto found_schema = m_schema.find(prop->object_type);
568✔
1529
        if (found_schema != m_schema.end()) {
568✔
1530
            target_schema = &*found_schema;
568✔
1531
            if (prop->type == PropertyType::LinkingObjects) {
568✔
1532
                auto origin_prop = target_schema->property_for_name(prop->link_origin_property_name);
222✔
1533
                auto origin_table = ObjectStore::table_for_object_type(m_group, target_schema->name);
222✔
1534
                col_key = origin_table->get_opposite_column(origin_prop->column_key);
222✔
1535
            }
222✔
1536
        }
568✔
1537
    }
568✔
1538
    return {col_key, target_schema};
1,058✔
1539
}
1,058✔
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
{
1,022✔
1546
    if (*path == '*') {
1,022✔
1547
        path++;
22✔
1548
        // Add all properties
1549
        props.reserve(object_schema->persisted_properties.size() + object_schema->computed_properties.size());
22✔
1550
        for (auto& p : object_schema->persisted_properties) {
60✔
1551
            auto [col_key, target_schema] = get_col_key(&p);
60✔
1552
            props.emplace_back(object_schema->table_key, col_key, &p, target_schema, false);
60✔
1553
        }
60✔
1554
        for (const auto& p : object_schema->computed_properties) {
22✔
1555
            auto [col_key, target_schema] = get_col_key(&p);
10✔
1556
            props.emplace_back(object_schema->table_key, col_key, &p, target_schema, false);
10✔
1557
        }
10✔
1558
    }
22✔
1559
    else {
1,000✔
1560
        auto p = find_chr(path, '.');
1,000✔
1561
        StringData property(path, p - path);
1,000✔
1562
        path = p;
1,000✔
1563
        auto prop = object_schema->property_for_public_name(property);
1,000✔
1564
        if (prop) {
1,000✔
1565
            auto [col_key, target_schema] = get_col_key(prop);
988✔
1566
            props.emplace_back(object_schema->table_key, col_key, prop, target_schema, true);
988✔
1567
        }
988✔
1568
        else {
12✔
1569
            if (mandatory) {
12✔
1570
                throw InvalidArgument(util::format("Property '%1' in KeyPath '%2' is not a valid property in %3.",
4✔
1571
                                                   property, m_full_path, object_schema->name));
4✔
1572
            }
4✔
1573
            else {
8✔
1574
                return false;
8✔
1575
            }
8✔
1576
        }
12✔
1577
    }
1,000✔
1578

1579
    if (*path++ == '.') {
1,010✔
1580
        auto it = props.begin();
346✔
1581
        while (it != props.end()) {
734✔
1582
            if (_resolve(*it, path)) {
388✔
1583
                ++it;
350✔
1584
            }
350✔
1585
            else {
38✔
1586
                it = props.erase(it);
38✔
1587
            }
38✔
1588
        }
388✔
1589
    }
346✔
1590
    return props.size();
1,010✔
1591
}
1,022✔
1592

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

1618
} // namespace
1619

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

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

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

© 2025 Coveralls, Inc