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

realm / realm-core / 1730

04 Oct 2023 01:46PM UTC coverage: 91.575% (-0.04%) from 91.615%
1730

push

Evergreen

web-flow
Initial ObjectStore Class class (#6521)

94264 of 173446 branches covered (0.0%)

30 of 70 new or added lines in 10 files covered. (42.86%)

96 existing lines in 17 files now uncovered.

230350 of 251543 relevant lines covered (91.57%)

6843633.6 hits per line

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

93.73
/src/realm/object-store/shared_realm.cpp
1
////////////////////////////////////////////////////////////////////////////
2
//
3
// Copyright 2015 Realm Inc.
4
//
5
// Licensed under the Apache License, Version 2.0 (the "License");
6
// you may not use this file except in compliance with the License.
7
// You may obtain a copy of the License at
8
//
9
// http://www.apache.org/licenses/LICENSE-2.0
10
//
11
// Unless required by applicable law or agreed to in writing, software
12
// distributed under the License is distributed on an "AS IS" BASIS,
13
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
// See the License for the specific language governing permissions and
15
// limitations under the License.
16
//
17
////////////////////////////////////////////////////////////////////////////
18

19
#include <realm/object-store/shared_realm.hpp>
20

21
#include <realm/object-store/impl/collection_notifier.hpp>
22
#include <realm/object-store/impl/realm_coordinator.hpp>
23
#include <realm/object-store/impl/transact_log_handler.hpp>
24

25
#include <realm/object-store/audit.hpp>
26
#include <realm/object-store/binding_context.hpp>
27
#include <realm/object-store/list.hpp>
28
#include <realm/object-store/object.hpp>
29
#include <realm/object-store/object_schema.hpp>
30
#include <realm/object-store/object_store.hpp>
31
#include <realm/object-store/results.hpp>
32
#include <realm/object-store/schema.hpp>
33
#include <realm/object-store/thread_safe_reference.hpp>
34

35
#include <realm/object-store/util/scheduler.hpp>
36

37
#include <realm/db.hpp>
38
#include <realm/util/fifo_helper.hpp>
39
#include <realm/util/file.hpp>
40
#include <realm/util/scope_exit.hpp>
41

42
#if REALM_ENABLE_SYNC
43
#include <realm/object-store/sync/impl/sync_file.hpp>
44
#include <realm/object-store/sync/sync_manager.hpp>
45
#include <realm/object-store/sync/sync_user.hpp>
46
#include <realm/object-store/sync/sync_session.hpp>
47

48
#include <realm/sync/config.hpp>
49
#include <realm/sync/history.hpp>
50
#include <realm/sync/noinst/client_history_impl.hpp>
51
#include <realm/history.hpp>
52
#endif
53

54
#include <thread>
55

56
using namespace realm;
57
using namespace realm::_impl;
58

59
namespace {
60
class CountGuard {
61
public:
62
    CountGuard(size_t& count)
63
        : m_count(count)
64
    {
381,858✔
65
        ++m_count;
381,858✔
66
    }
381,858✔
67
    ~CountGuard()
68
    {
381,865✔
69
        --m_count;
381,865✔
70
    }
381,865✔
71

72
private:
73
    size_t& m_count;
74
};
75
} // namespace
76

77
Realm::Realm(Config config, util::Optional<VersionID> version, std::shared_ptr<_impl::RealmCoordinator> coordinator,
78
             MakeSharedTag)
79
    : m_config(std::move(config))
80
    , m_frozen_version(version)
81
    , m_scheduler(m_config.scheduler)
82
{
57,300✔
83
    if (version) {
57,300✔
84
        m_auto_refresh = false;
985✔
85
        REALM_ASSERT(*version != VersionID());
985✔
86
    }
985✔
87
    else if (!coordinator->get_cached_schema(m_schema, m_schema_version, m_schema_transaction_version)) {
56,315✔
88
        m_transaction = coordinator->begin_read();
41,500✔
89
        read_schema_from_group_if_needed();
41,500✔
90
        coordinator->cache_schema(m_schema, m_schema_version, m_schema_transaction_version);
41,500✔
91
        m_transaction = nullptr;
41,500✔
92
    }
41,500✔
93
    m_coordinator = std::move(coordinator);
57,300✔
94
}
57,300✔
95

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

28,235✔
104
    if (m_coordinator) {
57,300✔
105
        m_coordinator->unregister_realm(this);
53,283✔
106
    }
53,283✔
107
}
57,300✔
108

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

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

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

129
std::shared_ptr<Transaction> Realm::transaction_ref()
130
{
114,334✔
131
    return m_transaction;
114,334✔
132
}
114,334✔
133

134
std::shared_ptr<Transaction> Realm::duplicate() const
135
{
144✔
136
    auto version = read_transaction_version(); // does the validity check first
144✔
137
    return m_coordinator->begin_read(version, is_frozen());
144✔
138
}
144✔
139

140
std::shared_ptr<DB>& Realm::Internal::get_db(Realm& realm)
141
{
6,504✔
142
    return realm.m_coordinator->m_db;
6,504✔
143
}
6,504✔
144

145
void Realm::Internal::begin_read(Realm& realm, VersionID version_id)
146
{
46✔
147
    realm.begin_read(version_id);
46✔
148
}
46✔
149

150
void Realm::begin_read(VersionID version_id)
151
{
110,119✔
152
    REALM_ASSERT(!m_transaction);
110,119✔
153
    m_transaction = m_coordinator->begin_read(version_id, bool(m_frozen_version));
110,119✔
154
    add_schema_change_handler();
110,119✔
155
    read_schema_from_group_if_needed();
110,119✔
156
}
110,119✔
157

158
SharedRealm Realm::get_shared_realm(Config config)
159
{
55,540✔
160
    auto coordinator = RealmCoordinator::get_coordinator(config.path);
55,540✔
161
    return coordinator->get_realm(std::move(config), util::none);
55,540✔
162
}
55,540✔
163

164
SharedRealm Realm::get_frozen_realm(Config config, VersionID version)
165
{
274✔
166
    auto coordinator = RealmCoordinator::get_coordinator(config.path);
274✔
167
    return coordinator->get_realm(std::move(config), version);
274✔
168
}
274✔
169

170
SharedRealm Realm::get_shared_realm(ThreadSafeReference ref, std::shared_ptr<util::Scheduler> scheduler)
171
{
138✔
172
    if (!scheduler)
138✔
173
        scheduler = util::Scheduler::make_default();
66✔
174
    SharedRealm realm = ref.resolve<std::shared_ptr<Realm>>(nullptr);
138✔
175
    REALM_ASSERT(realm);
138✔
176
    auto& config = realm->config();
138✔
177
    auto coordinator = RealmCoordinator::get_coordinator(config.path);
138✔
178
    if (auto realm = coordinator->get_cached_realm(config, scheduler))
138✔
179
        return realm;
2✔
180
    realm->m_scheduler = scheduler;
136✔
181
    coordinator->bind_to_context(*realm);
136✔
182
    return realm;
136✔
183
}
136✔
184

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

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

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

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

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

228
void Realm::read_schema_from_group_if_needed()
229
{
151,610✔
230
    if (m_config.immutable()) {
151,610✔
231
        REALM_ASSERT(m_transaction);
118✔
232
        if (m_schema.empty()) {
118✔
233
            m_schema_version = ObjectStore::get_schema_version(*m_transaction);
64✔
234
            m_schema = ObjectStore::schema_from_group(*m_transaction);
64✔
235
            m_schema_transaction_version = m_transaction->get_version_of_current_transaction().version;
64✔
236
        }
64✔
237
        return;
118✔
238
    }
118✔
239

74,651✔
240
    Group& group = read_group();
151,492✔
241
    auto current_version = transaction().get_version_of_current_transaction().version;
151,492✔
242
    if (m_schema_transaction_version == current_version)
151,492✔
243
        return;
108,993✔
244

20,970✔
245
    m_schema_transaction_version = current_version;
42,499✔
246
    m_schema_version = ObjectStore::get_schema_version(group);
42,499✔
247
    auto schema = ObjectStore::schema_from_group(group);
42,499✔
248

20,970✔
249
    if (m_coordinator)
42,499✔
250
        m_coordinator->cache_schema(schema, m_schema_version, m_schema_transaction_version);
1,061✔
251

20,970✔
252
    if (m_dynamic_schema) {
42,499✔
253
        if (m_schema == schema) {
42,032✔
254
            // The structure of the schema hasn't changed. Bring the table column indices up to date.
10,674✔
255
            m_schema.copy_keys_from(schema, SchemaSubsetMode::Strict);
21,587✔
256
        }
21,587✔
257
        else {
20,445✔
258
            // The structure of the schema has changed, so replace our copy of the schema.
10,066✔
259
            m_schema = std::move(schema);
20,445✔
260
        }
20,445✔
261
    }
42,032✔
262
    else {
467✔
263
        ObjectStore::verify_valid_external_changes(m_schema.compare(schema, m_config.schema_mode));
467✔
264
        m_schema.copy_keys_from(schema, m_config.schema_subset_mode);
467✔
265
    }
467✔
266
    notify_schema_changed();
42,499✔
267
}
42,499✔
268

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

10✔
278
    m_schema = ObjectStore::schema_from_group(read_group());
20✔
279
    m_schema_version = ObjectStore::get_schema_version(read_group());
20✔
280
    required_changes = m_schema.compare(schema, m_config.schema_mode);
20✔
281
    m_coordinator->clear_schema_cache_and_set_schema_version(m_schema_version);
20✔
282
    return false;
20✔
283
}
20✔
284

285
bool Realm::schema_change_needs_write_transaction(Schema& schema, std::vector<SchemaChange>& changes,
286
                                                  uint64_t version)
287
{
55,016✔
288
    if (version == m_schema_version && changes.empty())
55,016✔
289
        return false;
33,176✔
290

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

6,841✔
297
        case SchemaMode::Immutable:
6,847✔
298
            if (version != m_schema_version)
12✔
299
                throw InvalidSchemaVersionException(m_schema_version, version, true);
2✔
300
            REALM_FALLTHROUGH;
10✔
301
        case SchemaMode::ReadOnly:
36✔
302
            ObjectStore::verify_compatible_for_immutable_and_readonly(changes);
36✔
303
            return m_schema_version == ObjectStore::NotVersioned;
36✔
304

5✔
305
        case SchemaMode::SoftResetFile:
32✔
306
            if (m_schema_version == ObjectStore::NotVersioned)
32✔
307
                return true;
18✔
308
            if (m_schema_version == version && !ObjectStore::needs_migration(changes))
14✔
309
                return true;
8✔
310
            REALM_FALLTHROUGH;
6✔
311
        case SchemaMode::HardResetFile:
20✔
312
            reset_file(schema, changes);
20✔
313
            return true;
20✔
314

3✔
315
        case SchemaMode::AdditiveDiscovered:
3,911✔
316
        case SchemaMode::AdditiveExplicit: {
7,868✔
317
            bool will_apply_index_changes = version > m_schema_version;
7,868✔
318
            if (ObjectStore::verify_valid_additive_changes(changes, will_apply_index_changes))
7,868✔
319
                return true;
7,740✔
320
            return version != m_schema_version;
128✔
321
        }
128✔
322

64✔
323
        case SchemaMode::Manual:
128✔
324
            if (version < m_schema_version && m_schema_version != ObjectStore::NotVersioned)
128✔
325
                throw InvalidSchemaVersionException(m_schema_version, version, false);
2✔
326
            if (version == m_schema_version) {
126✔
327
                ObjectStore::verify_no_changes_required(changes);
28✔
328
                REALM_UNREACHABLE(); // changes is non-empty so above line always throws
28✔
329
            }
28✔
330
            return true;
126✔
331
    }
×
332
    REALM_COMPILER_HINT_UNREACHABLE();
×
333
}
×
334

335
Schema Realm::get_full_schema()
336
{
55,244✔
337
    if (!m_config.immutable())
55,244✔
338
        do_refresh();
55,178✔
339

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

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

356
Class Realm::get_class(StringData object_type)
357
{
6✔
358
    auto it = m_schema.find(object_type);
6✔
359
    if (it == m_schema.end()) {
6✔
NEW
360
        throw LogicError(ErrorCodes::NoSuchTable, util::format("No type '%1'", object_type));
×
NEW
361
    }
×
362
    return {shared_from_this(), &*it};
6✔
363
}
6✔
364

365
std::vector<Class> Realm::get_classes()
NEW
366
{
×
NEW
367
    std::vector<Class> ret;
×
NEW
368
    ret.reserve(m_schema.size());
×
NEW
369
    auto r = shared_from_this();
×
NEW
370
    for (auto& os : m_schema) {
×
NEW
371
        ret.emplace_back(r, &os);
×
NEW
372
    }
×
NEW
373
    return ret;
×
NEW
374
}
×
375

376
void Realm::set_schema_subset(Schema schema)
377
{
12✔
378
    verify_thread();
12✔
379
    verify_open();
12✔
380
    REALM_ASSERT(m_dynamic_schema);
12✔
381
    REALM_ASSERT(m_schema_version != ObjectStore::NotVersioned);
12✔
382

6✔
383
    std::vector<SchemaChange> changes = m_schema.compare(schema, m_config.schema_mode);
12✔
384
    switch (m_config.schema_mode) {
12✔
385
        case SchemaMode::Automatic:
2✔
386
        case SchemaMode::SoftResetFile:
2✔
387
        case SchemaMode::HardResetFile:
2✔
388
            ObjectStore::verify_no_migration_required(changes);
2✔
389
            break;
2✔
390

1✔
391
        case SchemaMode::Immutable:
1✔
392
        case SchemaMode::ReadOnly:
✔
393
            ObjectStore::verify_compatible_for_immutable_and_readonly(changes);
×
394
            break;
×
395

396
        case SchemaMode::AdditiveDiscovered:
4✔
397
        case SchemaMode::AdditiveExplicit:
8✔
398
            ObjectStore::verify_valid_additive_changes(changes);
8✔
399
            break;
8✔
400

4✔
401
        case SchemaMode::Manual:
4✔
402
            ObjectStore::verify_no_changes_required(changes);
×
403
            break;
×
404
    }
10✔
405

5✔
406
    set_schema(m_schema, std::move(schema));
10✔
407
}
10✔
408

409
void Realm::update_schema(Schema schema, uint64_t version, MigrationFunction migration_function,
410
                          DataInitializationFunction initialization_function, bool in_transaction)
411
{
55,252✔
412
    uint64_t validation_mode = SchemaValidationMode::Basic;
55,252✔
413
#if REALM_ENABLE_SYNC
55,252✔
414
    if (auto sync_config = m_config.sync_config) {
55,252✔
415
        validation_mode |=
1,442✔
416
            sync_config->flx_sync_requested ? SchemaValidationMode::SyncFLX : SchemaValidationMode::SyncPBS;
1,215✔
417
    }
1,442✔
418
#endif
55,252✔
419
    if (m_config.schema_mode == SchemaMode::AdditiveExplicit) {
55,252✔
420
        validation_mode |= SchemaValidationMode::RejectEmbeddedOrphans;
8,504✔
421
    }
8,504✔
422

27,215✔
423
    schema.validate(static_cast<SchemaValidationMode>(validation_mode));
55,252✔
424

27,215✔
425
    bool was_in_read_transaction = is_in_read_transaction();
55,252✔
426
    Schema actual_schema = get_full_schema();
55,252✔
427

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

27,095✔
440
    std::vector<SchemaChange> required_changes = actual_schema.compare(schema, m_config.schema_mode);
55,012✔
441
    if (!schema_change_needs_write_transaction(schema, required_changes, version)) {
55,012✔
442
        if (!was_in_read_transaction)
33,248✔
443
            m_transaction = nullptr;
33,218✔
444
        set_schema(actual_schema, std::move(schema));
33,248✔
445
        return;
33,248✔
446
    }
33,248✔
447
    // Either the schema version has changed or we need to do non-migration changes
10,769✔
448

10,769✔
449
    // Cancel the write transaction if we exit this function before committing it
10,769✔
450
    auto cleanup = util::make_scope_exit([&]() noexcept {
21,764✔
451
        // When in_transaction is true, caller is responsible to cancel the transaction.
10,723✔
452
        if (!in_transaction && is_in_transaction())
21,672✔
453
            cancel_transaction();
110✔
454
        if (!was_in_read_transaction)
21,672✔
455
            m_transaction = nullptr;
21,320✔
456
    });
21,672✔
457

10,769✔
458
    if (!in_transaction) {
21,764✔
459
        transaction().promote_to_write();
21,646✔
460

10,710✔
461
        // Beginning the write transaction may have advanced the version and left
10,710✔
462
        // us with nothing to do if someone else initialized the schema on disk
10,710✔
463
        if (m_new_schema) {
21,646✔
464
            actual_schema = *m_new_schema;
14✔
465
            required_changes = actual_schema.compare(schema, m_config.schema_mode);
14✔
466
            if (!schema_change_needs_write_transaction(schema, required_changes, version)) {
14✔
467
                cancel_transaction();
2✔
468
                cache_new_schema();
2✔
469
                set_schema(actual_schema, std::move(schema));
2✔
470
                return;
2✔
471
            }
2✔
472
        }
21,644✔
473
        cache_new_schema();
21,644✔
474
    }
21,644✔
475

10,769✔
476
    schema.copy_keys_from(actual_schema, m_config.schema_subset_mode);
21,763✔
477

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

2,142✔
495
        // migration function needs to see the target schema on the "new" Realm
2,142✔
496
        std::swap(m_schema, schema);
4,355✔
497
        std::swap(m_schema_version, version);
4,355✔
498
        m_in_migration = true;
4,355✔
499
        auto restore = util::make_scope_exit([&]() noexcept {
4,355✔
500
            std::swap(m_schema, schema);
4,355✔
501
            std::swap(m_schema_version, version);
4,355✔
502
            m_in_migration = false;
4,355✔
503
        });
4,355✔
504

2,142✔
505
        ObjectStore::apply_schema_changes(transaction(), version, m_schema, m_schema_version, m_config.schema_mode,
4,355✔
506
                                          required_changes, m_config.automatically_handle_backlinks_in_migrations,
4,355✔
507
                                          wrapper);
4,355✔
508
    }
4,355✔
509
    else {
17,407✔
510
        ObjectStore::apply_schema_changes(transaction(), m_schema_version, schema, version, m_config.schema_mode,
17,407✔
511
                                          required_changes, m_config.automatically_handle_backlinks_in_migrations);
17,407✔
512
        REALM_ASSERT_DEBUG(additive ||
17,407✔
513
                           (required_changes = ObjectStore::schema_from_group(read_group()).compare(schema)).empty());
17,407✔
514
    }
17,407✔
515

10,768✔
516
    if (initialization_function && old_schema_version == ObjectStore::NotVersioned) {
21,762✔
517
        // Initialization function needs to see the latest schema
15✔
518
        uint64_t temp_version = ObjectStore::get_schema_version(read_group());
30✔
519
        std::swap(m_schema, schema);
30✔
520
        std::swap(m_schema_version, temp_version);
30✔
521
        auto restore = util::make_scope_exit([&]() noexcept {
30✔
522
            std::swap(m_schema, schema);
30✔
523
            std::swap(m_schema_version, temp_version);
30✔
524
        });
30✔
525
        initialization_function(shared_from_this());
30✔
526
    }
30✔
527

10,768✔
528
    m_schema = std::move(schema);
21,762✔
529
    m_new_schema = ObjectStore::schema_from_group(read_group());
21,762✔
530
    m_schema_version = ObjectStore::get_schema_version(read_group());
21,762✔
531
    m_dynamic_schema = false;
21,762✔
532
    m_coordinator->clear_schema_cache_and_set_schema_version(version);
21,762✔
533

10,768✔
534
    if (!in_transaction) {
21,762✔
535
        m_coordinator->commit_write(*this);
21,534✔
536
        cache_new_schema();
21,534✔
537
    }
21,534✔
538

10,768✔
539
    notify_schema_changed();
21,762✔
540
}
21,762✔
541

542
void Realm::rename_property(Schema schema, StringData object_type, StringData old_name, StringData new_name)
543
{
4✔
544
    ObjectStore::rename_property(read_group(), schema, object_type, old_name, new_name);
4✔
545
}
4✔
546

547
void Realm::add_schema_change_handler()
548
{
110,111✔
549
    if (m_config.immutable())
110,111✔
550
        return;
56✔
551
    m_transaction->set_schema_change_notification_handler([&] {
110,055✔
552
        m_new_schema = ObjectStore::schema_from_group(read_group());
3,457✔
553
        m_schema_version = ObjectStore::get_schema_version(read_group());
3,457✔
554
        if (m_dynamic_schema) {
3,457✔
555
            m_schema = *m_new_schema;
34✔
556
        }
34✔
557
        else {
3,423✔
558
            m_schema.copy_keys_from(*m_new_schema, m_config.schema_subset_mode);
3,423✔
559
        }
3,423✔
560
        notify_schema_changed();
3,457✔
561
    });
3,457✔
562
}
110,055✔
563

564
void Realm::cache_new_schema()
565
{
203,065✔
566
    if (is_closed()) {
203,065✔
567
        return;
×
568
    }
×
569

99,466✔
570
    auto new_version = transaction().get_version_of_current_transaction().version;
203,065✔
571
    if (m_new_schema)
203,065✔
572
        m_coordinator->cache_schema(std::move(*m_new_schema), m_schema_version, new_version);
25,017✔
573
    else
178,048✔
574
        m_coordinator->advance_schema_cache(m_schema_transaction_version, new_version);
178,048✔
575
    m_schema_transaction_version = new_version;
203,065✔
576
    m_new_schema = util::none;
203,065✔
577
}
203,065✔
578

579
void Realm::translate_schema_error()
580
{
4✔
581
    // Read the new (incompatible) schema without changing our read transaction
2✔
582
    auto new_schema = ObjectStore::schema_from_group(*m_coordinator->begin_read());
4✔
583

2✔
584
    // Should always throw
2✔
585
    ObjectStore::verify_valid_external_changes(m_schema.compare(new_schema, m_config.schema_mode, true));
4✔
586

2✔
587
    // Something strange happened so just rethrow the old exception
2✔
588
    throw;
4✔
589
}
4✔
590

591
void Realm::notify_schema_changed()
592
{
100,990✔
593
    if (m_binding_context) {
100,990✔
594
        m_binding_context->schema_did_change(m_schema);
26✔
595
    }
26✔
596
}
100,990✔
597

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

615
void Realm::verify_thread() const
616
{
595,790✔
617
    if (m_scheduler && !m_scheduler->is_on_thread())
595,790✔
618
        throw LogicError(ErrorCodes::WrongThread, "Realm accessed from incorrect thread.");
8✔
619
}
595,790✔
620

621
void Realm::verify_in_write() const
622
{
11,968✔
623
    if (!is_in_transaction()) {
11,968✔
624
        throw WrongTransactionState("Cannot modify managed objects outside of a write transaction.");
4✔
625
    }
4✔
626
}
11,968✔
627

628
void Realm::verify_open() const
629
{
2,146,865✔
630
    if (is_closed()) {
2,146,865✔
631
        throw LogicError(ErrorCodes::ClosedRealm, "Cannot access realm that has been closed.");
44✔
632
    }
44✔
633
}
2,146,865✔
634

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

10,968✔
660
    return true;
22,042✔
661
}
22,042✔
662

663
VersionID Realm::read_transaction_version() const
664
{
4,843✔
665
    verify_thread();
4,843✔
666
    verify_open();
4,843✔
667
    REALM_ASSERT(m_transaction);
4,843✔
668
    return m_transaction->get_version_of_current_transaction();
4,843✔
669
}
4,843✔
670

671
uint_fast64_t Realm::get_number_of_versions() const
672
{
125,558✔
673
    verify_open();
125,558✔
674
    return m_coordinator->get_number_of_versions();
125,558✔
675
}
125,558✔
676

677
bool Realm::is_in_transaction() const noexcept
678
{
692,092✔
679
    return !m_config.immutable() && !is_closed() && m_transaction &&
692,092✔
680
           transaction().get_transact_stage() == DB::transact_Writing;
643,977✔
681
}
692,092✔
682

683
bool Realm::is_in_async_transaction() const noexcept
684
{
826✔
685
    return !m_config.immutable() && !is_closed() && m_transaction && m_transaction->is_async();
826✔
686
}
826✔
687

688
util::Optional<VersionID> Realm::current_transaction_version() const
689
{
104,031✔
690
    util::Optional<VersionID> ret;
104,031✔
691
    if (m_transaction) {
104,031✔
692
        ret = m_transaction->get_version_of_current_transaction();
103,801✔
693
    }
103,801✔
694
    else if (m_frozen_version) {
230✔
695
        ret = m_frozen_version;
×
696
    }
×
697
    return ret;
104,031✔
698
}
104,031✔
699

700
// Get the version of the latest snapshot
701
util::Optional<DB::version_type> Realm::latest_snapshot_version() const
702
{
62✔
703
    util::Optional<DB::version_type> ret;
62✔
704
    if (m_transaction) {
62✔
705
        ret = m_transaction->get_version_of_latest_snapshot();
60✔
706
    }
60✔
707
    return ret;
62✔
708
}
62✔
709

710
void Realm::enable_wait_for_change()
711
{
2✔
712
    verify_open();
2✔
713
    m_coordinator->enable_wait_for_change();
2✔
714
}
2✔
715

716
bool Realm::wait_for_change()
717
{
6✔
718
    verify_open();
6✔
719
    if (m_frozen_version || m_config.schema_mode == SchemaMode::Immutable) {
6✔
720
        return false;
4✔
721
    }
4✔
722
    return m_transaction && m_coordinator->wait_for_change(m_transaction);
2!
723
}
2✔
724

725
void Realm::wait_for_change_release()
726
{
2✔
727
    verify_open();
2✔
728
    m_coordinator->wait_for_change_release();
2✔
729
}
2✔
730

731
bool Realm::has_pending_async_work() const
732
{
1,143✔
733
    verify_thread();
1,143✔
734
    return !m_async_commit_q.empty() || !m_async_write_q.empty() || (m_transaction && m_transaction->is_async());
1,143✔
735
}
1,143✔
736

737
void Realm::run_writes_on_proper_thread()
738
{
7✔
739
    m_scheduler->invoke([self = shared_from_this()] {
7✔
740
        self->run_writes();
7✔
741
    });
7✔
742
}
7✔
743

744
void Realm::call_completion_callbacks()
745
{
174,109✔
746
    if (m_is_running_async_commit_completions) {
174,109✔
747
        return;
4✔
748
    }
4✔
749

85,354✔
750
    CountGuard sending_completions(m_is_running_async_commit_completions);
174,105✔
751
    auto error = m_transaction->get_commit_exception();
174,105✔
752
    auto completions = std::move(m_async_commit_q);
174,105✔
753
    m_async_commit_q.clear();
174,105✔
754
    for (auto& cb : completions) {
86,733✔
755
        if (!cb.when_completed)
2,510✔
756
            continue;
16✔
757
        if (m_async_exception_handler) {
2,494✔
758
            try {
2✔
759
                cb.when_completed(error);
2✔
760
            }
2✔
761
            catch (...) {
2✔
762
                m_async_exception_handler(cb.handle, std::current_exception());
2✔
763
            }
2✔
764
        }
2✔
765
        else {
2,492✔
766
            cb.when_completed(error);
2,492✔
767
        }
2,492✔
768
    }
2,494✔
769
}
174,105✔
770

771
void Realm::run_async_completions()
772
{
330✔
773
    call_completion_callbacks();
330✔
774
    check_pending_write_requests();
330✔
775
}
330✔
776

777
void Realm::check_pending_write_requests()
778
{
59,146✔
779
    if (!m_async_write_q.empty()) {
59,146✔
780
        if (m_transaction->is_async()) {
420✔
781
            run_writes_on_proper_thread();
7✔
782
        }
7✔
783
        else {
413✔
784
            m_coordinator->async_request_write_mutex(*this);
413✔
785
        }
413✔
786
    }
420✔
787
}
59,146✔
788

789
void Realm::end_current_write(bool check_pending)
790
{
3,291✔
791
    if (!m_transaction) {
3,291✔
792
        return;
2✔
793
    }
2✔
794
    m_transaction->async_complete_writes([self = shared_from_this(), this]() mutable {
3,289✔
795
        m_scheduler->invoke([self = std::move(self), this]() mutable {
330✔
796
            run_async_completions();
330✔
797
            self.reset();
330✔
798
        });
330✔
799
    });
330✔
800
    if (check_pending && m_async_commit_q.empty()) {
3,289✔
801
        check_pending_write_requests();
2,833✔
802
    }
2,833✔
803
}
3,289✔
804

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

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

1,149✔
834
        do_begin_transaction();
2,546✔
835

1,149✔
836
        auto write_desc = std::move(m_async_write_q.front());
2,546✔
837
        m_async_write_q.pop_front();
2,546✔
838

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

3✔
852
            if (m_async_exception_handler) {
6✔
853
                m_async_exception_handler(write_desc.handle, std::current_exception());
2✔
854
                continue;
2✔
855
            }
2✔
856
            end_current_write();
4✔
857
            throw;
4✔
858
        }
4✔
859

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

1,140✔
867
        // Realm may have been closed in the write function
1,140✔
868
        if (!m_transaction) {
2,528✔
869
            return;
20✔
870
        }
20✔
871

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

125✔
890
    end_current_write();
368✔
891
}
349✔
892

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

1,156✔
905
    // make sure we have a (at least a) read transaction
1,156✔
906
    transaction();
2,560✔
907
    auto handle = m_async_commit_handle++;
2,560✔
908
    m_async_write_q.push_back({std::move(the_write_block), notify_only, handle});
2,560✔
909

1,156✔
910
    if (!m_is_running_async_writes && !m_transaction->is_async() &&
2,560✔
911
        m_transaction->get_transact_stage() != DB::transact_Writing) {
1,236✔
912
        m_coordinator->async_request_write_mutex(*this);
125✔
913
    }
125✔
914
    return handle;
2,560✔
915
}
2,560✔
916

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

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

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

980
bool Realm::async_cancel_transaction(AsyncHandle handle)
981
{
6✔
982
    verify_thread();
6✔
983
    verify_open();
6✔
984
    auto compare = [handle](auto& elem) {
5✔
985
        return elem.handle == handle;
4✔
986
    };
4✔
987

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

1003
void Realm::begin_transaction()
1004
{
61,707✔
1005
    check_can_create_write_transaction(this);
61,707✔
1006

30,308✔
1007
    if (is_in_transaction()) {
61,707✔
1008
        throw WrongTransactionState("The Realm is already in a write transaction");
×
1009
    }
×
1010

30,308✔
1011
    // Any of the callbacks to user code below could drop the last remaining
30,308✔
1012
    // strong reference to `this`
30,308✔
1013
    auto retain_self = shared_from_this();
61,707✔
1014

30,308✔
1015
    // make sure we have a read transaction
30,308✔
1016
    read_group();
61,707✔
1017

30,308✔
1018
    do_begin_transaction();
61,707✔
1019
}
61,707✔
1020

1021
void Realm::do_begin_transaction()
1022
{
64,245✔
1023
    CountGuard sending_notifications(m_is_sending_notifications);
64,245✔
1024
    try {
64,245✔
1025
        m_coordinator->promote_to_write(*this);
64,245✔
1026
    }
64,245✔
1027
    catch (_impl::UnsupportedSchemaChange const&) {
31,453✔
1028
        translate_schema_error();
×
1029
    }
×
1030
    cache_new_schema();
64,245✔
1031

31,453✔
1032
    if (m_transaction && !m_transaction->has_unsynced_commits()) {
64,245✔
1033
        call_completion_callbacks();
62,195✔
1034
    }
62,195✔
1035
}
64,245✔
1036

1037
void Realm::commit_transaction()
1038
{
55,979✔
1039
    check_can_create_write_transaction(this);
55,979✔
1040

27,442✔
1041
    if (!is_in_transaction()) {
55,979✔
1042
        throw WrongTransactionState("Can't commit a non-existing write transaction");
×
1043
    }
×
1044

27,442✔
1045
    DB::VersionID prev_version = transaction().get_version_of_current_transaction();
55,979✔
1046
    if (auto audit = audit_context()) {
55,979✔
1047
        audit->prepare_for_write(prev_version);
91✔
1048
    }
91✔
1049

27,442✔
1050
    m_coordinator->commit_write(*this, /* commit_to_disk */ true);
55,979✔
1051
    cache_new_schema();
55,979✔
1052

27,442✔
1053
    // Realm might have been closed
27,442✔
1054
    if (m_transaction) {
55,979✔
1055
        // Any previous async commits got flushed along with the sync commit
27,441✔
1056
        call_completion_callbacks();
55,977✔
1057
        // If we have pending async writes we need to rerequest the write mutex
27,441✔
1058
        check_pending_write_requests();
55,977✔
1059
    }
55,977✔
1060
    if (auto audit = audit_context()) {
55,979✔
1061
        audit->record_write(prev_version, transaction().get_version_of_current_transaction());
91✔
1062
    }
91✔
1063
}
55,979✔
1064

1065
void Realm::cancel_transaction()
1066
{
2,812✔
1067
    check_can_create_write_transaction(this);
2,812✔
1068

1,408✔
1069
    if (m_is_running_async_commit_completions) {
2,812✔
1070
        throw WrongTransactionState("Can't cancel a write transaction from inside a commit completion callback.");
×
1071
    }
×
1072
    if (!is_in_transaction()) {
2,812✔
1073
        throw WrongTransactionState("Can't cancel a non-existing write transaction");
×
1074
    }
×
1075

1,408✔
1076
    transaction::cancel(transaction(), m_binding_context.get());
2,812✔
1077

1,408✔
1078
    if (m_transaction && !m_is_running_async_writes) {
2,812✔
1079
        if (m_async_write_q.empty()) {
2,804✔
1080
            end_current_write();
2,796✔
1081
        }
2,796✔
1082
        else {
8✔
1083
            check_pending_write_requests();
8✔
1084
        }
8✔
1085
    }
2,804✔
1086
}
2,812✔
1087

1088
void Realm::invalidate()
1089
{
246✔
1090
    verify_thread();
246✔
1091
    verify_open();
246✔
1092

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

120✔
1100
    if (is_in_transaction()) {
240✔
1101
        cancel_transaction();
172✔
1102
    }
172✔
1103

120✔
1104
    do_invalidate();
240✔
1105
}
240✔
1106

1107
void Realm::do_invalidate()
1108
{
4,255✔
1109
    if (!m_config.immutable() && m_transaction) {
4,255✔
1110
        m_transaction->prepare_for_close();
4,177✔
1111
        call_completion_callbacks();
4,177✔
1112
        transaction().close();
4,177✔
1113
    }
4,177✔
1114

2,112✔
1115
    m_transaction = nullptr;
4,255✔
1116
    m_async_write_q.clear();
4,255✔
1117
    m_async_commit_q.clear();
4,255✔
1118
}
4,255✔
1119

1120
bool Realm::compact()
1121
{
8✔
1122
    verify_thread();
8✔
1123
    verify_open();
8✔
1124

4✔
1125
    if (m_config.immutable() || m_config.read_only()) {
8✔
1126
        throw WrongTransactionState("Can't compact a read-only Realm");
4✔
1127
    }
4✔
1128
    if (is_in_transaction()) {
4✔
1129
        throw WrongTransactionState("Can't compact a Realm within a write transaction");
×
1130
    }
×
1131

2✔
1132
    verify_open();
4✔
1133
    m_transaction = nullptr;
4✔
1134
    return m_coordinator->compact();
4✔
1135
}
4✔
1136

1137
void Realm::convert(const Config& config, bool merge_into_existing)
1138
{
60✔
1139
    verify_thread();
60✔
1140
    verify_open();
60✔
1141

30✔
1142
#if REALM_ENABLE_SYNC
60✔
1143
    auto src_is_flx_sync = m_config.sync_config && m_config.sync_config->flx_sync_requested;
60✔
1144
    auto dst_is_flx_sync = config.sync_config && config.sync_config->flx_sync_requested;
60✔
1145
    auto dst_is_pbs_sync = config.sync_config && !config.sync_config->flx_sync_requested;
60✔
1146

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

27✔
1156
#endif
54✔
1157

27✔
1158
    if (merge_into_existing && util::File::exists(config.path)) {
54✔
1159
        auto destination_realm = Realm::get_shared_realm(config);
6✔
1160
        destination_realm->begin_transaction();
6✔
1161
        auto destination = destination_realm->transaction_ref();
6✔
1162
        m_transaction->copy_to(destination);
6✔
1163
        destination_realm->commit_transaction();
6✔
1164
        return;
6✔
1165
    }
6✔
1166

24✔
1167
    if (config.encryption_key.size() && config.encryption_key.size() != 64) {
48!
1168
        throw InvalidEncryptionKey();
×
1169
    }
×
1170

24✔
1171
    auto& tr = transaction();
48✔
1172
    auto repl = tr.get_replication();
48✔
1173
    bool src_is_sync = repl && repl->get_history_type() == Replication::hist_SyncClient;
48✔
1174
    bool dst_is_sync = config.sync_config || config.force_sync_history;
48✔
1175

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

1194
OwnedBinaryData Realm::write_copy()
1195
{
6✔
1196
    verify_thread();
6✔
1197
    BinaryData buffer = read_group().write_to_mem();
6✔
1198

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

1204
void Realm::notify()
1205
{
23,956✔
1206
    if (is_closed() || is_in_transaction() || is_frozen()) {
23,956✔
1207
        return;
330✔
1208
    }
330✔
1209

10,366✔
1210
    verify_thread();
23,626✔
1211

10,366✔
1212
    // Any of the callbacks to user code below could drop the last remaining
10,366✔
1213
    // strong reference to `this`
10,366✔
1214
    auto retain_self = shared_from_this();
23,626✔
1215

10,366✔
1216
    if (m_binding_context) {
23,626✔
1217
        m_binding_context->before_notify();
198✔
1218
        if (is_closed() || is_in_transaction()) {
198✔
1219
            return;
×
1220
        }
×
1221
    }
23,626✔
1222

10,366✔
1223
    if (!m_coordinator->can_advance(*this)) {
23,626✔
1224
        CountGuard sending_notifications(m_is_sending_notifications);
16,632✔
1225
        m_coordinator->process_available_async(*this);
16,632✔
1226
        return;
16,632✔
1227
    }
16,632✔
1228

2,920✔
1229
    if (m_binding_context) {
6,994✔
1230
        m_binding_context->changes_available();
70✔
1231

35✔
1232
        // changes_available() may have advanced the read version, and if
35✔
1233
        // so we don't need to do anything further
35✔
1234
        if (!m_coordinator->can_advance(*this))
70✔
1235
            return;
4✔
1236
    }
6,990✔
1237

2,918✔
1238
    CountGuard sending_notifications(m_is_sending_notifications);
6,990✔
1239
    if (m_auto_refresh) {
6,990✔
1240
        if (m_transaction) {
6,987✔
1241
            try {
6,957✔
1242
                m_coordinator->advance_to_ready(*this);
6,957✔
1243
            }
6,957✔
1244
            catch (_impl::UnsupportedSchemaChange const&) {
2,900✔
1245
                translate_schema_error();
×
1246
            }
×
1247
            if (!is_closed())
6,959✔
1248
                cache_new_schema();
6,957✔
1249
        }
6,959✔
1250
        else {
30✔
1251
            if (m_binding_context) {
30✔
1252
                m_binding_context->did_change({}, {});
4✔
1253
            }
4✔
1254
            if (!is_closed()) {
30✔
1255
                m_coordinator->process_available_async(*this);
28✔
1256
            }
28✔
1257
        }
30✔
1258
    }
6,987✔
1259
}
6,990✔
1260

1261
bool Realm::refresh()
1262
{
64,498✔
1263
    verify_thread();
64,498✔
1264
    return do_refresh();
64,498✔
1265
}
64,498✔
1266

1267
bool Realm::do_refresh()
1268
{
119,676✔
1269
    // Frozen Realms never change.
59,124✔
1270
    if (is_frozen()) {
119,676✔
1271
        return false;
244✔
1272
    }
244✔
1273

59,002✔
1274
    if (m_config.immutable()) {
119,432✔
1275
        throw WrongTransactionState("Can't refresh an immutable Realm.");
4✔
1276
    }
4✔
1277

59,000✔
1278
    // can't be any new changes if we're in a write transaction
59,000✔
1279
    if (is_in_transaction()) {
119,428✔
1280
        return false;
30✔
1281
    }
30✔
1282
    // don't advance if we're already in the process of advancing as that just
58,985✔
1283
    // makes things needlessly complicated
58,985✔
1284
    if (m_is_sending_notifications) {
119,398✔
1285
        return false;
8✔
1286
    }
8✔
1287

58,981✔
1288
    // Any of the callbacks to user code below could drop the last remaining
58,981✔
1289
    // strong reference to `this`
58,981✔
1290
    auto retain_self = shared_from_this();
119,390✔
1291

58,981✔
1292
    CountGuard sending_notifications(m_is_sending_notifications);
119,390✔
1293
    if (m_binding_context) {
119,390✔
1294
        m_binding_context->before_notify();
138✔
1295
    }
138✔
1296
    if (m_transaction) {
119,390✔
1297
        try {
32,717✔
1298
            bool version_changed = m_coordinator->advance_to_latest(*this);
32,717✔
1299
            if (is_closed())
32,717✔
1300
                return false;
6✔
1301
            cache_new_schema();
32,711✔
1302
            return version_changed;
32,711✔
1303
        }
32,711✔
1304
        catch (_impl::UnsupportedSchemaChange const&) {
4✔
1305
            translate_schema_error();
4✔
1306
        }
4✔
1307
    }
32,717✔
1308

58,981✔
1309
    // No current read transaction, so just create a new one
58,981✔
1310
    read_group();
102,987✔
1311
    m_coordinator->process_available_async(*this);
86,673✔
1312
    return true;
86,673✔
1313
}
119,390✔
1314

1315
void Realm::set_auto_refresh(bool auto_refresh)
1316
{
16✔
1317
    if (is_frozen() && auto_refresh) {
16✔
1318
        throw WrongTransactionState("Auto-refresh cannot be enabled for frozen Realms.");
2✔
1319
    }
2✔
1320
    m_auto_refresh = auto_refresh;
14✔
1321
}
14✔
1322

1323

1324
bool Realm::can_deliver_notifications() const noexcept
1325
{
12,167✔
1326
    if (m_config.immutable() || !m_config.automatic_change_notifications) {
12,167✔
1327
        return false;
12,111✔
1328
    }
12,111✔
1329

28✔
1330
    if (!m_scheduler || !m_scheduler->can_invoke()) {
56✔
1331
        return false;
×
1332
    }
×
1333

28✔
1334
    return true;
56✔
1335
}
56✔
1336

1337
uint64_t Realm::get_schema_version(const Realm::Config& config)
1338
{
×
1339
    auto coordinator = RealmCoordinator::get_coordinator(config.path);
×
1340
    auto version = coordinator->get_schema_version();
×
1341
    if (version == ObjectStore::NotVersioned)
×
1342
        version = ObjectStore::get_schema_version(coordinator->get_realm(config, util::none)->read_group());
×
1343
    return version;
×
1344
}
×
1345

1346

1347
bool Realm::is_frozen() const
1348
{
376,526✔
1349
    bool result = bool(m_frozen_version);
376,526✔
1350
    REALM_ASSERT_DEBUG(!result || !m_transaction || m_transaction->is_frozen());
376,526✔
1351
    return result;
376,526✔
1352
}
376,526✔
1353

1354
SharedRealm Realm::freeze()
1355
{
623✔
1356
    read_group(); // Freezing requires a read transaction
623✔
1357
    return m_coordinator->freeze_realm(*this);
623✔
1358
}
623✔
1359

1360
void Realm::copy_schema_from(const Realm& source)
1361
{
619✔
1362
    REALM_ASSERT(is_frozen());
619✔
1363
    REALM_ASSERT(m_frozen_version == source.read_transaction_version());
619✔
1364
    m_schema = source.m_schema;
619✔
1365
    m_schema_version = source.m_schema_version;
619✔
1366
    m_schema_transaction_version = m_frozen_version->version;
619✔
1367
    m_dynamic_schema = false;
619✔
1368
}
619✔
1369

1370
void Realm::close()
1371
{
4,037✔
1372
    if (is_closed()) {
4,037✔
1373
        return;
20✔
1374
    }
20✔
1375
    if (m_coordinator) {
4,017✔
1376
        m_coordinator->unregister_realm(this);
4,017✔
1377
    }
4,017✔
1378

1,993✔
1379
    do_invalidate();
4,017✔
1380

1,993✔
1381
    m_binding_context = nullptr;
4,017✔
1382
    m_coordinator = nullptr;
4,017✔
1383
    m_scheduler = nullptr;
4,017✔
1384
    m_config = {};
4,017✔
1385
}
4,017✔
1386

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

1414
AuditInterface* Realm::audit_context() const noexcept
1415
{
179,949✔
1416
    return m_coordinator ? m_coordinator->audit_context() : nullptr;
179,949✔
1417
}
179,949✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc