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

realm / realm-core / jonathan.reams_3390

31 Jul 2024 07:00PM UTC coverage: 91.105% (-0.01%) from 91.116%
jonathan.reams_3390

Pull #7938

Evergreen

jbreams
RCORE-2212 Make apply-to-state tool handle all batch states properly
Pull Request #7938: RCORE-2212 Make apply-to-state tool handle all batch states properly

102768 of 181570 branches covered (56.6%)

216827 of 237996 relevant lines covered (91.11%)

5898131.93 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,340✔
67
    {
118,157✔
68
        ++m_count;
118,157✔
69
    }
118,157✔
70
    ~CountGuard()
71
    {
118,156✔
72
        --m_count;
118,156✔
73
    }
118,156✔
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,783✔
93
        REALM_ASSERT(*version != VersionID());
1,783✔
94
    }
1,783✔
95
    else if (!coordinator->get_cached_schema(m_schema, m_schema_version, m_schema_transaction_version)) {
33,381✔
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,752,395✔
119
    return transaction();
1,752,395✔
120
}
1,752,395✔
121

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

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

137
std::shared_ptr<Transaction> Realm::transaction_ref()
138
{
77,707✔
139
    return m_transaction;
77,707✔
140
}
77,707✔
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,880✔
160
    REALM_ASSERT(!m_transaction);
64,880✔
161
    m_transaction = m_coordinator->begin_read(version_id, bool(m_frozen_version));
64,880✔
162
    add_schema_change_handler();
64,880✔
163
    read_schema_from_group_if_needed();
64,880✔
164
}
64,880✔
165

166
SharedRealm Realm::get_shared_realm(Config config)
167
{
25,410✔
168
    auto coordinator = RealmCoordinator::get_coordinator(config.path);
25,410✔
169
    return coordinator->get_realm(std::move(config), util::none);
25,410✔
170
}
25,410✔
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,828✔
230
    m_dynamic_schema = false;
12,828✔
231
    schema.copy_keys_from(reference, m_config.schema_subset_mode);
12,828✔
232
    m_schema = std::move(schema);
12,828✔
233
    notify_schema_changed();
12,828✔
234
}
12,828✔
235

236
void Realm::read_schema_from_group_if_needed()
237
{
84,512✔
238
    if (m_config.immutable()) {
84,512✔
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,394✔
249
    auto current_version = transaction().get_version_of_current_transaction().version;
84,394✔
250
    if (m_schema_transaction_version == current_version)
84,394✔
251
        return;
63,517✔
252

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

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

260
    if (m_dynamic_schema) {
20,877✔
261
        if (m_schema == schema) {
20,274✔
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,270✔
264
        }
19,270✔
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,274✔
270
    else {
603✔
271
        ObjectStore::verify_valid_external_changes(m_schema.compare(schema, m_config.schema_mode));
603✔
272
        m_schema.copy_keys_from(schema, m_config.schema_subset_mode);
603✔
273
    }
603✔
274
    notify_schema_changed();
20,877✔
275
}
20,877✔
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,065✔
296
    if (version == m_schema_version && changes.empty())
32,065✔
297
        return false;
12,490✔
298

299
    switch (m_config.schema_mode) {
19,575✔
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,980✔
324
            bool will_apply_index_changes = version > m_schema_version;
8,980✔
325
            if (ObjectStore::verify_valid_additive_changes(changes, will_apply_index_changes))
8,980✔
326
                return true;
8,788✔
327
            return version != m_schema_version;
192✔
328
        }
8,980✔
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,575✔
338
    REALM_COMPILER_HINT_UNREACHABLE();
×
339
}
19,575✔
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,330✔
354
    if (!m_config.immutable())
32,330✔
355
        do_refresh();
32,264✔
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,330✔
360
        return m_schema;
12,636✔
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,338✔
434
    uint64_t validation_mode = SchemaValidationMode::Basic;
32,338✔
435
#if REALM_ENABLE_SYNC
32,338✔
436
    if (auto sync_config = m_config.sync_config) {
32,338✔
437
        validation_mode |=
1,873✔
438
            sync_config->flx_sync_requested ? SchemaValidationMode::SyncFLX : SchemaValidationMode::SyncPBS;
1,873✔
439
    }
1,873✔
440
#endif
32,338✔
441
    if (m_config.schema_mode == SchemaMode::AdditiveExplicit) {
32,338✔
442
        validation_mode |= SchemaValidationMode::RejectEmbeddedOrphans;
9,793✔
443
    }
9,793✔
444

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

447
    bool was_in_read_transaction = is_in_read_transaction();
32,338✔
448
    Schema actual_schema = get_full_schema();
32,338✔
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,338✔
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,066✔
463
    if (!schema_change_needs_write_transaction(schema, required_changes, version)) {
32,066✔
464
        if (!was_in_read_transaction)
12,564✔
465
            m_transaction = nullptr;
12,534✔
466
        set_schema(actual_schema, std::move(schema));
12,564✔
467
        return;
12,564✔
468
    }
12,564✔
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;
9✔
487
            required_changes = actual_schema.compare(schema, m_config.schema_mode);
9✔
488
            if (!schema_change_needs_write_transaction(schema, required_changes, version)) {
9✔
489
                cancel_transaction();
2✔
490
                cache_new_schema();
2✔
491
                set_schema(actual_schema, std::move(schema));
2✔
492
                return;
2✔
493
            }
2✔
494
        }
9✔
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,873✔
571
    if (m_config.immutable())
64,873✔
572
        return;
56✔
573
    m_transaction->set_schema_change_notification_handler([&] {
64,817✔
574
        m_new_schema = ObjectStore::schema_from_group(read_group());
3,985✔
575
        m_schema_version = ObjectStore::get_schema_version(read_group());
3,985✔
576
        if (m_dynamic_schema) {
3,985✔
577
            m_schema = *m_new_schema;
29✔
578
        }
29✔
579
        else {
3,956✔
580
            m_schema.copy_keys_from(*m_new_schema, m_config.schema_subset_mode);
3,956✔
581
        }
3,956✔
582
        notify_schema_changed();
3,985✔
583
    });
3,985✔
584
}
64,817✔
585

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

592
    auto new_version = transaction().get_version_of_current_transaction().version;
147,141✔
593
    if (m_new_schema)
147,141✔
594
        m_coordinator->cache_schema(std::move(*m_new_schema), m_schema_version, new_version);
23,277✔
595
    else
123,864✔
596
        m_coordinator->advance_schema_cache(m_schema_transaction_version, new_version);
123,864✔
597
    m_schema_transaction_version = new_version;
147,141✔
598
    m_new_schema = util::none;
147,141✔
599
}
147,141✔
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,976✔
615
    if (m_binding_context) {
56,976✔
616
        m_binding_context->schema_did_change(m_schema);
86✔
617
    }
86✔
618
}
56,976✔
619

620
static void check_can_create_write_transaction(const Realm* realm)
621
{
97,463✔
622
    realm->verify_thread();
97,463✔
623
    realm->verify_open();
97,463✔
624
    if (realm->config().immutable() || realm->config().read_only()) {
97,463✔
625
        throw WrongTransactionState("Can't perform transactions on read-only Realms.");
6✔
626
    }
6✔
627
    if (realm->is_frozen()) {
97,457✔
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,455✔
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,455✔
636

637
void Realm::verify_thread() const
638
{
608,650✔
639
    if (m_scheduler && !m_scheduler->is_on_thread())
608,650✔
640
        throw LogicError(ErrorCodes::WrongThread, "Realm accessed from incorrect thread.");
8✔
641
}
608,650✔
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,821,106✔
652
    if (is_closed()) {
2,821,106✔
653
        throw LogicError(ErrorCodes::ClosedRealm, "Cannot access realm that has been closed.");
44✔
654
    }
44✔
655
}
2,821,106✔
656

657
bool Realm::verify_notifications_available(bool throw_on_error) const
658
{
29,974✔
659
    if (is_frozen()) {
29,974✔
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,918✔
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,916✔
671
        if (m_transaction && m_transaction->get_commit_size() > 0)
10,530✔
672
            throw WrongTransactionState(
2✔
673
                "Cannot create asynchronous query after making changes in a write transaction.");
2✔
674
    }
10,530✔
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,808✔
683
}
29,916✔
684

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

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

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

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

710
util::Optional<VersionID> Realm::current_transaction_version() const
711
{
51,276✔
712
    util::Optional<VersionID> ret;
51,276✔
713
    if (m_transaction) {
51,276✔
714
        ret = m_transaction->get_version_of_current_transaction();
50,922✔
715
    }
50,922✔
716
    else if (m_frozen_version) {
354✔
717
        ret = m_frozen_version;
×
718
    }
×
719
    return ret;
51,276✔
720
}
51,276✔
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,870✔
755
    verify_thread();
2,870✔
756
    return !m_async_commit_q.empty() || !m_async_write_q.empty() || (m_transaction && m_transaction->is_async());
2,870✔
757
}
2,870✔
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,636✔
768
    if (m_is_running_async_commit_completions || m_async_commit_q.empty()) {
123,636✔
769
        return;
123,170✔
770
    }
123,170✔
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,077✔
801
    if (!m_async_write_q.empty()) {
45,077✔
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,077✔
810

811
void Realm::end_current_write(bool check_pending)
812
{
3,371✔
813
    if (!m_transaction) {
3,371✔
814
        return;
2✔
815
    }
2✔
816
    m_transaction->async_complete_writes([self = shared_from_this(), this]() mutable {
3,369✔
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,369✔
823
        check_pending_write_requests();
2,911✔
824
    }
2,911✔
825
}
3,369✔
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
836
        return;
×
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,667✔
1027
    check_can_create_write_transaction(this);
47,667✔
1028

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

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

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

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

1054
    if (m_transaction && !m_transaction->has_unsynced_commits()) {
50,205✔
1055
        call_completion_callbacks();
48,155✔
1056
    }
48,155✔
1057
}
50,205✔
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,890✔
1089
    check_can_create_write_transaction(this);
2,890✔
1090

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

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

1100
    if (m_transaction && !m_is_running_async_writes) {
2,890✔
1101
        if (m_async_write_q.empty()) {
2,882✔
1102
            end_current_write();
2,874✔
1103
        }
2,874✔
1104
        else {
8✔
1105
            check_pending_write_requests();
8✔
1106
        }
8✔
1107
    }
2,882✔
1108
}
2,890✔
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,822✔
1228
    if (is_closed() || is_in_transaction() || is_frozen()) {
26,822✔
1229
        return;
662✔
1230
    }
662✔
1231

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

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

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

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

1260
    CountGuard sending_notifications(m_is_sending_notifications);
7,322✔
1261
    if (m_auto_refresh) {
7,330✔
1262
        if (m_transaction) {
7,327✔
1263
            try {
7,265✔
1264
                m_coordinator->advance_to_ready(*this);
7,265✔
1265
            }
7,265✔
1266
            catch (_impl::UnsupportedSchemaChange const&) {
7,265✔
1267
                translate_schema_error();
×
1268
            }
×
1269
            if (!is_closed())
7,265✔
1270
                cache_new_schema();
7,263✔
1271
        }
7,265✔
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,327✔
1281
}
7,322✔
1282

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

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

1296
    if (m_config.immutable()) {
40,865✔
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()) {
40,861✔
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) {
40,831✔
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();
40,823✔
1313

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

1331
    // No current read transaction, so just create a new one
1332
    read_group();
31,612✔
1333
    m_coordinator->process_available_async(*this);
31,612✔
1334
    return true;
31,612✔
1335
}
40,823✔
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
{
240,036✔
1371
    bool result = bool(m_frozen_version);
240,036✔
1372
    REALM_ASSERT_DEBUG(!result || !m_transaction || m_transaction->is_frozen());
240,036✔
1373
    return result;
240,036✔
1374
}
240,036✔
1375

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

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